diff --git a/CMakeLists.txt b/CMakeLists.txt index 1327b8f..c2524c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -262,6 +262,7 @@ add_executable(Dicewars_Siedler src/main.cpp src/game/hexWorld/ecs/components/BuildingPreviewComponent.h src/game/GameWorldSystems.cpp src/game/GameWorldSystems.h + src/engine/core/gui/uiComponent/layout/LayoutEngine.h ) target_compile_options(Dicewars_Siedler PRIVATE diff --git a/src/engine/core/gui/uiComponent/layout/LayoutEngine.h b/src/engine/core/gui/uiComponent/layout/LayoutEngine.h new file mode 100644 index 0000000..fa5beb1 --- /dev/null +++ b/src/engine/core/gui/uiComponent/layout/LayoutEngine.h @@ -0,0 +1,160 @@ +// +// Created by sebastian on 17.04.26. +// + +#ifndef DICEWARS_SIEDLER_LAYOUTENGINE_H +#define DICEWARS_SIEDLER_LAYOUTENGINE_H +#include + +#include "LayoutStyle.h" +#include "../Dimensions.h" + +#endif //DICEWARS_SIEDLER_LAYOUTENGINE_H +namespace LayoutEngine { + struct NodeLayout { + LayoutStyle layoutStyle; + std::vector children; + }; + + struct ComputedLayout { + Dimensions self; + std::vector children; + }; + + inline float resolveDim(const SizeValue size, float parentSize, float axisLength) { + switch (size.unit) { + case SizeUnit::Percent: + return size.value * parentSize; + case SizeUnit::Pixels: + return size.value / axisLength; // has to be normalized! + default: + return 0.0f; + } + } + + inline Dimensions computeSelfDimensions(const NodeLayout &node, const Dimensions &parent, float screenWidth, float screenHeight) { + Dimensions d = {}; + d.x = parent.x + resolveDim(node.layoutStyle.margin.left, parent.width, screenWidth); + d.y = parent.y + resolveDim(node.layoutStyle.margin.top, parent.height, screenHeight); + d.width = resolveDim(node.layoutStyle.width, parent.width, screenWidth); + d.height = resolveDim(node.layoutStyle.height, parent.height, screenHeight); + return d; + } + + struct JustifyResult { + float offset; + float gap; + }; + + inline JustifyResult computeJustify(JustifyContent justify, float remainingSpace, std::size_t childCount) { + switch (justify) { + case JustifyContent::Start: return {0.0f, 0.0f}; + case JustifyContent::Center: return {remainingSpace / 2.0f, 0.0f}; + case JustifyContent::End: return {remainingSpace, 0.0f}; + case JustifyContent::SpaceBetween: + if (childCount > 1) + return {0.0f, remainingSpace / (static_cast(childCount) - 1.0f)}; + return {0.0f, 0.0f}; + default: return {0.0f, 0.0f}; + } + } + + inline void offsetLayout(ComputedLayout& layout, float dx, float dy) { + layout.self.x += dx; + layout.self.y += dy; + for (auto& child : layout.children) { + offsetLayout(child, dx, dy); + } + } + + inline void offsetChildLayouts(ComputedLayout& layout, float dx, float dy) { + for (auto& child : layout.children) { + offsetLayout(child, dx, dy); + } + } + + inline ComputedLayout compute(const NodeLayout &node, const Dimensions &parent, float screenWidth, float screenHeight) { + const bool isColumn = (node.layoutStyle.flexDirection == FlexDirection::Column); + + ComputedLayout result; + result.self = computeSelfDimensions(node, parent, screenWidth, screenHeight); + result.children.resize(node.children.size()); + + for (size_t i = 0; i < node.children.size(); i++) { + result.children[i] = compute(node.children[i], result.self, screenWidth, screenHeight); + } + + float totalMainSize = 0.0f; + for (size_t i = 0; i < node.children.size(); i++) { + const auto& child = node.children[i]; + const auto& childLayout = result.children[i]; + + 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); + + totalMainSize += mainSize + marginMain; + } + + float mainExtent = isColumn ? result.self.height : result.self.width; + float remainingMain = mainExtent - totalMainSize; + auto [justifyOffset, gap] = computeJustify( + node.layoutStyle.justifyContent, remainingMain, node.children.size()); + + float cursor = justifyOffset; + for (size_t i = 0; i < node.children.size(); i++) { + const auto& child = node.children[i]; + auto& childLayout = result.children[i]; + + if (i > 0 && node.layoutStyle.justifyContent == JustifyContent::SpaceBetween) + cursor += gap; + + if (isColumn) { + float marginTop = resolveDim(child.layoutStyle.margin.top, result.self.height, screenHeight); + offsetLayout(childLayout, 0.0f, cursor); + cursor += childLayout.self.height + marginTop; + } else { + float marginLeft = resolveDim(child.layoutStyle.margin.left, result.self.width, screenWidth); + offsetLayout(childLayout, cursor, 0.0f); + cursor += childLayout.self.width + marginLeft; + } + } + + for (size_t i = 0; i < node.children.size(); i++) { + const auto& child = node.children[i]; + 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 oldX = childLayout.self.x; + switch (node.layoutStyle.alignItems) { + case AlignItems::Start: + childLayout.self.x = result.self.x + marginLeft; break; + case AlignItems::Center: + childLayout.self.x = result.self.x + remainingCross / 2.0f + marginLeft; break; + case AlignItems::End: + childLayout.self.x = result.self.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 oldY = childLayout.self.y; + switch (node.layoutStyle.alignItems) { + case AlignItems::Start: + childLayout.self.y = result.self.y + marginTop; break; + case AlignItems::Center: + childLayout.self.y += remainingCross / 2.0f + marginTop; break; + case AlignItems::End: + childLayout.self.y += remainingCross + marginTop; break; + default: break; + } + offsetChildLayouts(childLayout, 0.0f, childLayout.self.y - oldY); + } + } + return result; + } +} diff --git a/src/engine/core/gui/uiComponent/layout/UiPositioner.cpp b/src/engine/core/gui/uiComponent/layout/UiPositioner.cpp index 3f3f277..548c486 100644 --- a/src/engine/core/gui/uiComponent/layout/UiPositioner.cpp +++ b/src/engine/core/gui/uiComponent/layout/UiPositioner.cpp @@ -5,145 +5,22 @@ #include "UiPositioner.h" #include "../UiComponent.h" -#include "../UiText.h" #include "../../../Application.h" -#include "../../../../../game/ui/components/UiRessourceWidget.h" -#include "../../text/Font.h" + +void UiPositioner::applyLayout(const ::LayoutEngine::ComputedLayout &layout) { + screenSpace = layout.self; + + for (size_t i = 0; i < uiComponent.children.size(); i++) { + uiComponent.children[i]->uiPositioner.applyLayout(layout.children[i]); + } +} void UiPositioner::compute(const Dimensions &parent) { - const float screenWidth = static_cast(Application::getInstance().getWindow().GetWidth()); - const float screenHeight = static_cast(Application::getInstance().getWindow().GetHeight()); - - screenSpace.x = parent.x + resolve(style.margin.left, parent.width, screenWidth); - screenSpace.y = parent.y + resolve(style.margin.top, parent.height, screenHeight); - screenSpace.width = resolve(style.width, parent.width, screenWidth); - - if (auto text = dynamic_cast(&uiComponent)) { - screenSpace.height = text->getFont().getLineHeight() / screenHeight; - } else { - screenSpace.height = resolve(style.height, parent.height, screenHeight); - } - - - // 3. Kinder rekursiv positionieren mit JustifyContent - - for (auto& child : uiComponent.children) { - child->uiPositioner.compute(screenSpace); - } - - float totalChildrenMainSize = 0.0f; - // 3a. Gesamtgröße der Kinder berechnen (inkl. Top/Left Margins) - for (auto& child : uiComponent.children) { - float childMainSize = 0.0f; - float marginMain = 0.0; - if (style.flexDirection == FlexDirection::Column) { - childMainSize = child->uiPositioner.screenSpace.height; - marginMain = child->uiPositioner.resolve(child->uiPositioner.style.margin.top, screenSpace.height, screenHeight); - } else { - childMainSize = child->uiPositioner.screenSpace.width; - marginMain = child->uiPositioner.resolve(child->uiPositioner.style.margin.left, screenSpace.width, screenWidth); - } - - totalChildrenMainSize += childMainSize + marginMain; - } - - // 3b JustifyContent-offset berechnen - float remainingSpace = ((style.flexDirection == FlexDirection::Column) ? screenSpace.height : screenSpace.width) - totalChildrenMainSize; - float gap =0.0f; - - float justifyOffset = 0.0f; - switch (style.justifyContent) { - case JustifyContent::Start: justifyOffset = 0.0f; break; - case JustifyContent::Center: justifyOffset = remainingSpace / 2.0f; break; - case JustifyContent::End: justifyOffset = remainingSpace; break; - case JustifyContent::SpaceBetween: - justifyOffset = 0.0; - if (uiComponent.children.size() > 1) { - gap = remainingSpace / (static_cast(uiComponent.children.size()) - 1.0f); - } - break; - default: break; // Space between later - } - - - - // 3. Kinder rekursiv positionieren - float currentMainOffset = justifyOffset; - bool first = true; - for (auto& child : uiComponent.children) { - if (!first && style.justifyContent == JustifyContent::SpaceBetween) { - currentMainOffset += gap; - } - - if (style.flexDirection == FlexDirection::Column) { - child->uiPositioner.offsetPosition(0.f,currentMainOffset); - currentMainOffset += child->uiPositioner.screenSpace.height; - currentMainOffset += child->uiPositioner.resolve(child->uiPositioner.style.margin.top, screenSpace.height, screenHeight); - } else { - child->uiPositioner.offsetPosition(currentMainOffset,0.f); - currentMainOffset += child->uiPositioner.screenSpace.width; - currentMainOffset += child->uiPositioner.resolve(child->uiPositioner.style.margin.left, screenSpace.width, screenWidth); - } - - first = false; - } - - // AlignItems - for (auto& child : uiComponent.children) { - float oldX = child->uiPositioner.screenSpace.x; - float oldY = child->uiPositioner.screenSpace.y; - - if (style.flexDirection == FlexDirection::Column) { - float marginLeft = child->uiPositioner.resolve(child->uiPositioner.style.margin.left,screenSpace.width,screenWidth); - float remainingCrossSpace = screenSpace.width - child->uiPositioner.screenSpace.width ; - switch (style.alignItems) { - case AlignItems::Start: - child->uiPositioner.screenSpace.x = screenSpace.x + marginLeft; - break; - - case AlignItems::Center: - child->uiPositioner.screenSpace.x = screenSpace.x + remainingCrossSpace / 2.0f + marginLeft; - break; - - case AlignItems::End: - child->uiPositioner.screenSpace.x = screenSpace.x + remainingCrossSpace + marginLeft; - break; - default: - break; - } - - float dx = child->uiPositioner.screenSpace.x - oldX; - for (auto& grandchild : child->children) { - grandchild->uiPositioner.offsetPosition(dx,0.0f); - } - } else { - float remainingCrossSpace = screenSpace.height - child->uiPositioner.screenSpace.height; - float marginTop = child->uiPositioner.resolve(child->uiPositioner.style.margin.top,screenSpace.height,screenHeight); - switch (style.alignItems) { - case AlignItems::Start: - child->uiPositioner.screenSpace.y = screenSpace.y + marginTop; - break; - - case AlignItems::Center: - child->uiPositioner.screenSpace.y += remainingCrossSpace / 2.0f + marginTop; - break; - - case AlignItems::End: - child->uiPositioner.screenSpace.y += remainingCrossSpace + marginTop; - break; - - default: - break; - } - - float dy = child->uiPositioner.screenSpace.y - oldY; - for (auto& grandchild : child->children) { - grandchild->uiPositioner.offsetPosition(0.0f,dy); - } - } - } + LayoutEngine::NodeLayout node = buildNodeLayout(); + LayoutEngine::ComputedLayout result = LayoutEngine::compute(node, parent); + applyLayout(result); } LayoutStyle & UiPositioner::getLayout() { @@ -155,6 +32,18 @@ void UiPositioner::setLayout(const LayoutStyle &style) { } +LayoutEngine::NodeLayout UiPositioner::buildNodeLayout() { + if (uiComponent.children.empty()) { + return {getLayout(), std::vector()}; + } + + std::vector childrenLayouts; + for (const auto& child : uiComponent.children) { + childrenLayouts.push_back(child->uiPositioner.buildNodeLayout()); + } + return {getLayout(), childrenLayouts}; +} + float UiPositioner::resolve(const SizeValue &size, float parentSize, float axisLength) { switch (size.unit) { case SizeUnit::Percent: diff --git a/src/engine/core/gui/uiComponent/layout/UiPositioner.h b/src/engine/core/gui/uiComponent/layout/UiPositioner.h index 7610f8d..02b6d06 100644 --- a/src/engine/core/gui/uiComponent/layout/UiPositioner.h +++ b/src/engine/core/gui/uiComponent/layout/UiPositioner.h @@ -7,7 +7,9 @@ #include "../Dimensions.h" #include +#include "LayoutEngine.h" #include "LayoutStyle.h" + class UiComponent; // forward declaration class UiPositioner { @@ -17,6 +19,8 @@ public: UiPositioner(UiComponent& uiComponent, const LayoutStyle& style): uiComponent(uiComponent), style(style) {} + void applyLayout(const LayoutEngine::ComputedLayout & result); + void compute(const Dimensions& parentDimensions); LayoutStyle & getLayout(); @@ -25,6 +29,7 @@ public: private: UiComponent& uiComponent; LayoutStyle style; + LayoutEngine::NodeLayout buildNodeLayout(); float resolve(const SizeValue &size, float parentSize, float axisLength); void offsetPosition(float dx, float dy);