Dicewars-Siedler/tests/layout/LayoutEngineTest.cpp
sebastian 31d27a8243
All checks were successful
Tests / test (push) Successful in 2m31s
UPD: Box Modell vervollständigen, closes #9
2026-04-17 13:40:24 +02:00

363 lines
14 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#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));
}