diff --git a/CMakeLists.txt b/CMakeLists.txt index c2524c1..f055304 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,28 @@ target_include_directories(glad PUBLIC find_package(Freetype REQUIRED) include_directories(${FREETYPE_INCLUDE_DIRS}) +include(FetchContent) +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.7.1 +) +FetchContent_MakeAvailable(Catch2) + +add_executable(LayoutEngineTests + tests/layout/LayoutEngineTest.cpp +) + +target_include_directories(LayoutEngineTests PRIVATE + src + lib/glm +) + +target_link_libraries(LayoutEngineTests PRIVATE Catch2::Catch2WithMain) + +include(CTest) +include(Catch) +catch_discover_tests(LayoutEngineTests) add_executable(Dicewars_Siedler src/main.cpp src/engine/core/Window.cpp diff --git a/src/engine/core/gui/uiComponent/layout/UiPositioner.cpp b/src/engine/core/gui/uiComponent/layout/UiPositioner.cpp index 548c486..d62aa9c 100644 --- a/src/engine/core/gui/uiComponent/layout/UiPositioner.cpp +++ b/src/engine/core/gui/uiComponent/layout/UiPositioner.cpp @@ -18,7 +18,7 @@ void UiPositioner::applyLayout(const ::LayoutEngine::ComputedLayout &layout) { void UiPositioner::compute(const Dimensions &parent) { LayoutEngine::NodeLayout node = buildNodeLayout(); - LayoutEngine::ComputedLayout result = LayoutEngine::compute(node, parent); + LayoutEngine::ComputedLayout result = LayoutEngine::compute(node, parent, Application::getInstance().getWindow().GetWidth(), Application::getInstance().getWindow().GetHeight()); applyLayout(result); } diff --git a/tests/layout/LayoutEngineTest.cpp b/tests/layout/LayoutEngineTest.cpp new file mode 100644 index 0000000..2fcf22b --- /dev/null +++ b/tests/layout/LayoutEngineTest.cpp @@ -0,0 +1,127 @@ +#include +#include +#include "engine/core/gui/uiComponent/layout/LayoutEngine.h" + +using namespace LayoutEngine; +using Catch::Matchers::WithinAbs; +const float EPS = 0.001f; + +static NodeLayout makeNode(float x, float y, float w, float h) { + NodeLayout n; + n.layoutStyle.width = { w, SizeUnit::Percent }; + n.layoutStyle.height = { h, SizeUnit::Percent }; + n.layoutStyle.margin.left = { x, SizeUnit::Percent }; + n.layoutStyle.margin.top = { y, SizeUnit::Percent }; + return n; +} + +static Dimensions screen800x600() { + return { 0.f, 0.f, 1.0f, 1.0f }; +} + +// ── Basisfälle ─────────────────────────────────────────────────────────────── + +TEST_CASE("SizeUnit::Pixels wird korrekt normalisiert", "[layout][pixels]") { + NodeLayout root; + root.layoutStyle.width = { 400.f, SizeUnit::Pixels }; + root.layoutStyle.height = { 300.f, SizeUnit::Pixels }; + + auto result = compute(root, screen800x600(), 800.f, 600.f); + + REQUIRE_THAT(result.self.width, WithinAbs(0.5f, EPS)); // 400/800 + REQUIRE_THAT(result.self.height, WithinAbs(0.5f, EPS)); // 300/600 +} + +TEST_CASE("Eigene Dimensionen werden korrekt berechnet", "[layout][self]") { + NodeLayout root = makeNode(0.1f, 0.2f, 0.5f, 0.4f); + auto result = compute(root, screen800x600(), 800.f, 600.f); + + REQUIRE_THAT(result.self.x, WithinAbs(0.1f, EPS)); + REQUIRE_THAT(result.self.y, WithinAbs(0.2f, EPS)); + REQUIRE_THAT(result.self.width, WithinAbs(0.5f, EPS)); + REQUIRE_THAT(result.self.height, WithinAbs(0.4f, EPS)); +} + +TEST_CASE("Kein Kind → leere children-Liste", "[layout][children]") { + NodeLayout root = makeNode(0.f, 0.f, 0.5f, 0.5f); + auto result = compute(root, screen800x600(), 800.f, 600.f); + REQUIRE(result.children.empty()); +} + +// ── JustifyContent ─────────────────────────────────────────────────────────── + +TEST_CASE("JustifyContent::Start – Kinder beginnen bei 0", "[layout][justify]") { + NodeLayout root = makeNode(0.f, 0.f, 1.0f, 1.0f); + root.layoutStyle.flexDirection = FlexDirection::Row; + root.layoutStyle.justifyContent = JustifyContent::Start; + root.children.push_back(makeNode(0.f, 0.f, 0.2f, 1.0f)); + root.children.push_back(makeNode(0.f, 0.f, 0.2f, 1.0f)); + + auto result = compute(root, screen800x600(), 800.f, 600.f); + REQUIRE_THAT(result.children[0].self.x, WithinAbs(0.0f, EPS)); + REQUIRE_THAT(result.children[1].self.x, WithinAbs(0.2f, EPS)); +} + +TEST_CASE("JustifyContent::Center – Kind horizontal zentriert", "[layout][justify]") { + NodeLayout root = makeNode(0.f, 0.f, 1.0f, 1.0f); + root.layoutStyle.flexDirection = FlexDirection::Row; + root.layoutStyle.justifyContent = JustifyContent::Center; + root.children.push_back(makeNode(0.f, 0.f, 0.4f, 1.0f)); + + auto result = compute(root, screen800x600(), 800.f, 600.f); + // remaining = 1.0 - 0.4 = 0.6 → offset = 0.3 + REQUIRE_THAT(result.children[0].self.x, WithinAbs(0.3f, EPS)); +} + +TEST_CASE("JustifyContent::SpaceBetween – Gap korrekt verteilt", "[layout][justify]") { + NodeLayout root = makeNode(0.f, 0.f, 1.0f, 1.0f); + root.layoutStyle.flexDirection = FlexDirection::Row; + root.layoutStyle.justifyContent = JustifyContent::SpaceBetween; + root.children.push_back(makeNode(0.f, 0.f, 0.2f, 1.0f)); + root.children.push_back(makeNode(0.f, 0.f, 0.2f, 1.0f)); + root.children.push_back(makeNode(0.f, 0.f, 0.2f, 1.0f)); + + auto result = compute(root, screen800x600(), 800.f, 600.f); + // remaining = 1.0 - 0.6 = 0.4, gap = 0.2 + REQUIRE_THAT(result.children[0].self.x, WithinAbs(0.0f, EPS)); + REQUIRE_THAT(result.children[1].self.x, WithinAbs(0.4f, EPS)); + REQUIRE_THAT(result.children[2].self.x, WithinAbs(0.8f, EPS)); +} + +// ── AlignItems ─────────────────────────────────────────────────────────────── + +TEST_CASE("AlignItems::Center – Kind vertikal zentriert (Row)", "[layout][align]") { + NodeLayout root = makeNode(0.f, 0.f, 1.0f, 1.0f); + root.layoutStyle.flexDirection = FlexDirection::Row; + root.layoutStyle.alignItems = AlignItems::Center; + root.children.push_back(makeNode(0.f, 0.f, 0.5f, 0.4f)); + + auto result = compute(root, screen800x600(), 800.f, 600.f); + // remaining = 1.0 - 0.4 = 0.6 → y = 0.3 + REQUIRE_THAT(result.children[0].self.y, WithinAbs(0.3f, EPS)); +} + +TEST_CASE("AlignItems::End – Kind am unteren Rand (Row)", "[layout][align]") { + NodeLayout root = makeNode(0.f, 0.f, 1.0f, 1.0f); + root.layoutStyle.flexDirection = FlexDirection::Row; + root.layoutStyle.alignItems = AlignItems::End; + root.children.push_back(makeNode(0.f, 0.f, 0.5f, 0.4f)); + + auto result = compute(root, screen800x600(), 800.f, 600.f); + // remaining = 1.0 - 0.4 = 0.6 → y = 0.6 + REQUIRE_THAT(result.children[0].self.y, WithinAbs(0.6f, EPS)); +} + +// ── FlexDirection::Column ──────────────────────────────────────────────────── + +TEST_CASE("Column: Kinder werden vertikal gestapelt", "[layout][column]") { + NodeLayout root = makeNode(0.f, 0.f, 1.0f, 1.0f); + root.layoutStyle.flexDirection = FlexDirection::Column; + root.layoutStyle.justifyContent = JustifyContent::Start; + root.children.push_back(makeNode(0.f, 0.f, 1.0f, 0.3f)); + root.children.push_back(makeNode(0.f, 0.f, 1.0f, 0.3f)); + + auto result = compute(root, screen800x600(), 800.f, 600.f); + REQUIRE_THAT(result.children[0].self.y, WithinAbs(0.0f, EPS)); + REQUIRE_THAT(result.children[1].self.y, WithinAbs(0.3f, EPS)); +} \ No newline at end of file