363 lines
14 KiB
C++
363 lines
14 KiB
C++
#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));
|
||
} |