diff --git a/src/engine/core/gui/uiComponent/layout/LayoutEngine.h b/src/engine/core/gui/uiComponent/layout/LayoutEngine.h index fa5beb1..8892779 100644 --- a/src/engine/core/gui/uiComponent/layout/LayoutEngine.h +++ b/src/engine/core/gui/uiComponent/layout/LayoutEngine.h @@ -80,8 +80,22 @@ namespace LayoutEngine { result.self = computeSelfDimensions(node, parent, screenWidth, screenHeight); result.children.resize(node.children.size()); + //Padding auflösen + float padLeft = resolveDim(node.layoutStyle.padding.left, result.self.width, screenWidth); + float padRight = resolveDim(node.layoutStyle.padding.right, result.self.width, screenWidth); + float padTop = resolveDim(node.layoutStyle.padding.top, result.self.height, screenHeight); + float padBottom = resolveDim(node.layoutStyle.padding.bottom, result.self.height, screenHeight); + + // Available space for children + Dimensions innerArea = { + result.self.x + padLeft, + result.self.y + padTop, + result.self.width - padLeft - padRight, + result.self.height - padTop - padBottom + }; + for (size_t i = 0; i < node.children.size(); i++) { - result.children[i] = compute(node.children[i], result.self, screenWidth, screenHeight); + result.children[i] = compute(node.children[i], innerArea, screenWidth, screenHeight); } float totalMainSize = 0.0f; @@ -91,13 +105,16 @@ namespace LayoutEngine { const float mainSize = isColumn ? childLayout.self.height : childLayout.self.width; const float marginMain = isColumn - ? resolveDim(child.layoutStyle.margin.top, parent.height, screenHeight) - : resolveDim(child.layoutStyle.margin.left, parent.width, screenWidth); + ? resolveDim(child.layoutStyle.margin.top, parent.height, screenHeight) + resolveDim(child.layoutStyle.margin.bottom, parent.height, screenHeight) + : resolveDim(child.layoutStyle.margin.left, parent.width, screenWidth) + resolveDim(child.layoutStyle.margin.right, parent.width, screenWidth); totalMainSize += mainSize + marginMain; } + if (node.children.size() > 1) { + totalMainSize += node.layoutStyle.gap * (static_cast(node.children.size()) - 1.0f); + } - float mainExtent = isColumn ? result.self.height : result.self.width; + float mainExtent = isColumn ? innerArea.height : innerArea.width; float remainingMain = mainExtent - totalMainSize; auto [justifyOffset, gap] = computeJustify( node.layoutStyle.justifyContent, remainingMain, node.children.size()); @@ -111,13 +128,19 @@ namespace LayoutEngine { cursor += gap; if (isColumn) { - float marginTop = resolveDim(child.layoutStyle.margin.top, result.self.height, screenHeight); + float marginTop = resolveDim(child.layoutStyle.margin.top, innerArea.height, screenHeight); + float marginBottom = resolveDim(child.layoutStyle.margin.bottom, innerArea.height, screenHeight); offsetLayout(childLayout, 0.0f, cursor); - cursor += childLayout.self.height + marginTop; + cursor += childLayout.self.height + marginTop + marginBottom; + if (i + 1 < node.children.size()) + cursor += node.layoutStyle.gap; } else { - float marginLeft = resolveDim(child.layoutStyle.margin.left, result.self.width, screenWidth); + float marginLeft = resolveDim(child.layoutStyle.margin.left, innerArea.width, screenWidth); + float marginRight = resolveDim(child.layoutStyle.margin.right, innerArea.width, screenWidth); offsetLayout(childLayout, cursor, 0.0f); - cursor += childLayout.self.width + marginLeft; + cursor += childLayout.self.width + marginLeft + marginRight; + if (i + 1 < node.children.size()) + cursor += node.layoutStyle.gap; } } @@ -126,26 +149,26 @@ namespace LayoutEngine { auto& childLayout = result.children[i]; if (isColumn) { - float marginLeft = resolveDim(child.layoutStyle.margin.left, result.self.width, screenWidth); - float remainingCross = result.self.width - childLayout.self.width; + float marginLeft = resolveDim(child.layoutStyle.margin.left, innerArea.width, screenWidth); + float remainingCross = innerArea.width - childLayout.self.width; float oldX = childLayout.self.x; switch (node.layoutStyle.alignItems) { case AlignItems::Start: - childLayout.self.x = result.self.x + marginLeft; break; + childLayout.self.x = innerArea.x + marginLeft; break; case AlignItems::Center: - childLayout.self.x = result.self.x + remainingCross / 2.0f + marginLeft; break; + childLayout.self.x = innerArea.x + remainingCross / 2.0f + marginLeft; break; case AlignItems::End: - childLayout.self.x = result.self.x + remainingCross + marginLeft; break; + childLayout.self.x = innerArea.x + remainingCross + marginLeft; break; default: break; } offsetChildLayouts(childLayout, childLayout.self.x - oldX, 0.0f); } else { - float marginTop = resolveDim(child.layoutStyle.margin.top, result.self.height, screenHeight); - float remainingCross = result.self.height - childLayout.self.height; + float marginTop = resolveDim(child.layoutStyle.margin.top, innerArea.height, screenHeight); + float remainingCross = innerArea.height - childLayout.self.height; float oldY = childLayout.self.y; switch (node.layoutStyle.alignItems) { case AlignItems::Start: - childLayout.self.y = result.self.y + marginTop; break; + childLayout.self.y = innerArea.y + marginTop; break; case AlignItems::Center: childLayout.self.y += remainingCross / 2.0f + marginTop; break; case AlignItems::End: diff --git a/src/engine/core/gui/uiComponent/layout/LayoutStyle.h b/src/engine/core/gui/uiComponent/layout/LayoutStyle.h index a94d7b6..7bb9da6 100644 --- a/src/engine/core/gui/uiComponent/layout/LayoutStyle.h +++ b/src/engine/core/gui/uiComponent/layout/LayoutStyle.h @@ -39,14 +39,20 @@ enum class AlignItems { Stretch }; +struct EdgeInsets { + SizeValue top = {0, SizeUnit::Pixels}; + SizeValue left = {0, SizeUnit::Pixels}; + SizeValue bottom = {0, SizeUnit::Pixels}; + SizeValue right = {0, SizeUnit::Pixels}; +}; + struct LayoutStyle { SizeValue width = {1.0f, SizeUnit::Percent}; SizeValue height = {1.0f, SizeUnit::Percent}; - Margin margin = { - .left = {0.0f, SizeUnit::Percent}, - .top = {0.0f, SizeUnit::Percent}, - }; + EdgeInsets margin; + EdgeInsets padding; + float gap = 0.0f; FlexDirection flexDirection = FlexDirection::Column; JustifyContent justifyContent = JustifyContent::Start; diff --git a/tests/layout/LayoutEngineTest.cpp b/tests/layout/LayoutEngineTest.cpp index 2fcf22b..2acd14b 100644 --- a/tests/layout/LayoutEngineTest.cpp +++ b/tests/layout/LayoutEngineTest.cpp @@ -124,4 +124,240 @@ TEST_CASE("Column: Kinder werden vertikal gestapelt", "[layout][column]") { 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)); } \ No newline at end of file