UPD: Box Modell vervollständigen, closes #9
All checks were successful
Tests / test (push) Successful in 2m31s

This commit is contained in:
sebastian 2026-04-17 13:40:24 +02:00
parent 00d10e4f0e
commit 31d27a8243
3 changed files with 285 additions and 20 deletions

View File

@ -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<float>(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:

View File

@ -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;

View File

@ -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));
}