#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)); } constexpr float SW = 800.0f; constexpr float SH = 600.0f; static SizeValue px(float v) { return {v, SizeUnit::Pixels}; } static SizeValue pct(float v) { return {v, SizeUnit::Percent}; } static Dimensions rootDims() { return {0.0f, 0.0f, 1.0f, 1.0f}; } // ============================================================ // PADDING // ============================================================ TEST_CASE("Padding: Kind startet nach padding.left/top", "[padding]") { NodeLayout root; root.layoutStyle.width = pct(1.0f); root.layoutStyle.height = pct(1.0f); root.layoutStyle.padding.left = px(20.0f); root.layoutStyle.padding.top = px(30.0f); root.layoutStyle.flexDirection = FlexDirection::Row; root.layoutStyle.alignItems = AlignItems::Start; NodeLayout child; child.layoutStyle.width = px(100.0f); child.layoutStyle.height = px(50.0f); root.children.push_back(child); auto result = compute(root, rootDims(), SW, SH); // padding.left = 20px / 800 = 0.025, padding.top = 30px / 600 = 0.05 REQUIRE_THAT(result.children[0].self.x, WithinAbs(20.0f / SW, EPS)); REQUIRE_THAT(result.children[0].self.y, WithinAbs(30.0f / SH, EPS)); } TEST_CASE("Padding: padding.right verkleinert innerArea, Kind zentriert darin", "[padding]") { NodeLayout root; root.layoutStyle.width = pct(1.0f); root.layoutStyle.height = pct(1.0f); root.layoutStyle.padding.left = px(50.0f); root.layoutStyle.padding.right = px(50.0f); root.layoutStyle.flexDirection = FlexDirection::Row; root.layoutStyle.justifyContent = JustifyContent::Center; NodeLayout child; child.layoutStyle.width = px(200.0f); child.layoutStyle.height = px(100.0f); root.children.push_back(child); auto result = compute(root, rootDims(), SW, SH); // innerArea.x = 50/800, innerArea.width = (800-100)/800 = 700/800 // childWidth = 200/800 // center: innerArea.x + (innerArea.width - childWidth) / 2 float innerX = 50.0f / SW; float innerW = 700.0f / SW; float childW = 200.0f / SW; float expectedX = innerX + (innerW - childW) / 2.0f; REQUIRE_THAT(result.children[0].self.x, WithinAbs(expectedX, EPS)); } TEST_CASE("Padding: AlignItems::Center respektiert padding in Querachse (Column)", "[padding]") { NodeLayout root; root.layoutStyle.width = pct(1.0f); root.layoutStyle.height = pct(1.0f); root.layoutStyle.flexDirection = FlexDirection::Column; root.layoutStyle.alignItems = AlignItems::Center; root.layoutStyle.padding.left = px(100.0f); root.layoutStyle.padding.right = px(100.0f); NodeLayout child; child.layoutStyle.width = px(200.0f); child.layoutStyle.height = px(100.0f); root.children.push_back(child); auto result = compute(root, rootDims(), SW, SH); // innerArea: x=100/800, width=600/800 // Center: innerArea.x + (innerArea.width - childWidth) / 2 float innerX = 100.0f / SW; float innerW = 600.0f / SW; float childW = 200.0f / SW; float expectedX = innerX + (innerW - childW) / 2.0f; REQUIRE_THAT(result.children[0].self.x, WithinAbs(expectedX, EPS)); } // ============================================================ // GAP // ============================================================ TEST_CASE("Gap: Zwei Kinder in Row haben gap Abstand", "[gap]") { NodeLayout root; root.layoutStyle.width = pct(1.0f); root.layoutStyle.height = pct(1.0f); root.layoutStyle.flexDirection = FlexDirection::Row; root.layoutStyle.gap = 20.0f / SW; NodeLayout child; child.layoutStyle.width = px(100.0f); child.layoutStyle.height = px(50.0f); root.children.push_back(child); root.children.push_back(child); auto result = compute(root, rootDims(), SW, SH); float child0Right = result.children[0].self.x + result.children[0].self.width; float child1Left = result.children[1].self.x; REQUIRE_THAT(child1Left - child0Right, WithinAbs(20.0f / SW, EPS)); } TEST_CASE("Gap: Drei Kinder in Column haben je gap Abstand", "[gap]") { NodeLayout root; root.layoutStyle.width = pct(1.0f); root.layoutStyle.height = pct(1.0f); root.layoutStyle.flexDirection = FlexDirection::Column; root.layoutStyle.gap = 10.0f / SH; NodeLayout child; child.layoutStyle.width = px(100.0f); child.layoutStyle.height = px(50.0f); root.children.push_back(child); root.children.push_back(child); root.children.push_back(child); auto result = compute(root, rootDims(), SW, SH); float gap = 10.0f / SH; float bottom0 = result.children[0].self.y + result.children[0].self.height; float bottom1 = result.children[1].self.y + result.children[1].self.height; REQUIRE_THAT(result.children[1].self.y - bottom0, WithinAbs(gap, EPS)); REQUIRE_THAT(result.children[2].self.y - bottom1, WithinAbs(gap, EPS)); } TEST_CASE("Gap: Erstes Kind bekommt keinen gap-Abstand davor", "[gap]") { NodeLayout root; root.layoutStyle.width = pct(1.0f); root.layoutStyle.height = pct(1.0f); root.layoutStyle.flexDirection = FlexDirection::Row; root.layoutStyle.gap = 50.0f / SW; NodeLayout child; child.layoutStyle.width = px(100.0f); child.layoutStyle.height = px(50.0f); root.children.push_back(child); auto result = compute(root, rootDims(), SW, SH); REQUIRE_THAT(result.children[0].self.x, WithinAbs(0.0f, EPS)); } // ============================================================ // MARGIN (alle 4 Seiten) // ============================================================ TEST_CASE("Margin: margin.left verschiebt Kind in Row", "[margin]") { NodeLayout root; root.layoutStyle.width = pct(1.0f); root.layoutStyle.height = pct(1.0f); root.layoutStyle.flexDirection = FlexDirection::Row; NodeLayout child; child.layoutStyle.width = px(100.0f); child.layoutStyle.height = px(50.0f); child.layoutStyle.margin.left = px(40.0f); root.children.push_back(child); auto result = compute(root, rootDims(), SW, SH); REQUIRE_THAT(result.children[0].self.x, WithinAbs(40.0f / SW, EPS)); } TEST_CASE("Margin: margin.top verschiebt Kind in Column", "[margin]") { NodeLayout root; root.layoutStyle.width = pct(1.0f); root.layoutStyle.height = pct(1.0f); root.layoutStyle.flexDirection = FlexDirection::Column; NodeLayout child; child.layoutStyle.width = px(100.0f); child.layoutStyle.height = px(50.0f); child.layoutStyle.margin.top = px(25.0f); root.children.push_back(child); auto result = compute(root, rootDims(), SW, SH); REQUIRE_THAT(result.children[0].self.y, WithinAbs(25.0f / SH, EPS)); } TEST_CASE("Margin: margin.right erhöht Abstand zum nächsten Kind in Row", "[margin]") { NodeLayout root; root.layoutStyle.width = pct(1.0f); root.layoutStyle.height = pct(1.0f); root.layoutStyle.flexDirection = FlexDirection::Row; NodeLayout child0; child0.layoutStyle.width = px(100.0f); child0.layoutStyle.height = px(50.0f); child0.layoutStyle.margin.right = px(30.0f); NodeLayout child1; child1.layoutStyle.width = px(100.0f); child1.layoutStyle.height = px(50.0f); root.children.push_back(child0); root.children.push_back(child1); auto result = compute(root, rootDims(), SW, SH); float child0Right = result.children[0].self.x + result.children[0].self.width; float child1Left = result.children[1].self.x; // Abstand = margin.right = 30/800 REQUIRE_THAT(child1Left - child0Right, WithinAbs(30.0f / SW, EPS)); } TEST_CASE("Margin: margin.bottom erhöht Abstand zum nächsten Kind in Column", "[margin]") { NodeLayout root; root.layoutStyle.width = pct(1.0f); root.layoutStyle.height = pct(1.0f); root.layoutStyle.flexDirection = FlexDirection::Column; NodeLayout child0; child0.layoutStyle.width = px(100.0f); child0.layoutStyle.height = px(50.0f); child0.layoutStyle.margin.bottom = px(20.0f); NodeLayout child1; child1.layoutStyle.width = px(100.0f); child1.layoutStyle.height = px(50.0f); root.children.push_back(child0); root.children.push_back(child1); auto result = compute(root, rootDims(), SW, SH); float child0Bottom = result.children[0].self.y + result.children[0].self.height; float child1Top = result.children[1].self.y; // Abstand = margin.bottom = 20/600 REQUIRE_THAT(child1Top - child0Bottom, WithinAbs(20.0f / SH, EPS)); }