ADD: LayoutEngine
This commit is contained in:
parent
309398d76c
commit
4137db0c1f
@ -262,6 +262,7 @@ add_executable(Dicewars_Siedler src/main.cpp
|
|||||||
src/game/hexWorld/ecs/components/BuildingPreviewComponent.h
|
src/game/hexWorld/ecs/components/BuildingPreviewComponent.h
|
||||||
src/game/GameWorldSystems.cpp
|
src/game/GameWorldSystems.cpp
|
||||||
src/game/GameWorldSystems.h
|
src/game/GameWorldSystems.h
|
||||||
|
src/engine/core/gui/uiComponent/layout/LayoutEngine.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_options(Dicewars_Siedler PRIVATE
|
target_compile_options(Dicewars_Siedler PRIVATE
|
||||||
|
|||||||
160
src/engine/core/gui/uiComponent/layout/LayoutEngine.h
Normal file
160
src/engine/core/gui/uiComponent/layout/LayoutEngine.h
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
//
|
||||||
|
// Created by sebastian on 17.04.26.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef DICEWARS_SIEDLER_LAYOUTENGINE_H
|
||||||
|
#define DICEWARS_SIEDLER_LAYOUTENGINE_H
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "LayoutStyle.h"
|
||||||
|
#include "../Dimensions.h"
|
||||||
|
|
||||||
|
#endif //DICEWARS_SIEDLER_LAYOUTENGINE_H
|
||||||
|
namespace LayoutEngine {
|
||||||
|
struct NodeLayout {
|
||||||
|
LayoutStyle layoutStyle;
|
||||||
|
std::vector<NodeLayout> children;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ComputedLayout {
|
||||||
|
Dimensions self;
|
||||||
|
std::vector<ComputedLayout> 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<float>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,145 +5,22 @@
|
|||||||
#include "UiPositioner.h"
|
#include "UiPositioner.h"
|
||||||
|
|
||||||
#include "../UiComponent.h"
|
#include "../UiComponent.h"
|
||||||
#include "../UiText.h"
|
|
||||||
#include "../../../Application.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) {
|
void UiPositioner::compute(const Dimensions &parent) {
|
||||||
const float screenWidth = static_cast<float>(Application::getInstance().getWindow().GetWidth());
|
LayoutEngine::NodeLayout node = buildNodeLayout();
|
||||||
const float screenHeight = static_cast<float>(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<UiText*>(&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<float>(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::ComputedLayout result = LayoutEngine::compute(node, parent);
|
||||||
|
|
||||||
|
applyLayout(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
LayoutStyle & UiPositioner::getLayout() {
|
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<LayoutEngine::NodeLayout>()};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<LayoutEngine::NodeLayout> 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) {
|
float UiPositioner::resolve(const SizeValue &size, float parentSize, float axisLength) {
|
||||||
switch (size.unit) {
|
switch (size.unit) {
|
||||||
case SizeUnit::Percent:
|
case SizeUnit::Percent:
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
#include "../Dimensions.h"
|
#include "../Dimensions.h"
|
||||||
#include <glm/vec2.hpp>
|
#include <glm/vec2.hpp>
|
||||||
|
|
||||||
|
#include "LayoutEngine.h"
|
||||||
#include "LayoutStyle.h"
|
#include "LayoutStyle.h"
|
||||||
|
|
||||||
class UiComponent; // forward declaration
|
class UiComponent; // forward declaration
|
||||||
|
|
||||||
class UiPositioner {
|
class UiPositioner {
|
||||||
@ -17,6 +19,8 @@ public:
|
|||||||
UiPositioner(UiComponent& uiComponent, const LayoutStyle& style): uiComponent(uiComponent), style(style) {}
|
UiPositioner(UiComponent& uiComponent, const LayoutStyle& style): uiComponent(uiComponent), style(style) {}
|
||||||
|
|
||||||
|
|
||||||
|
void applyLayout(const LayoutEngine::ComputedLayout & result);
|
||||||
|
|
||||||
void compute(const Dimensions& parentDimensions);
|
void compute(const Dimensions& parentDimensions);
|
||||||
|
|
||||||
LayoutStyle & getLayout();
|
LayoutStyle & getLayout();
|
||||||
@ -25,6 +29,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
UiComponent& uiComponent;
|
UiComponent& uiComponent;
|
||||||
LayoutStyle style;
|
LayoutStyle style;
|
||||||
|
LayoutEngine::NodeLayout buildNodeLayout();
|
||||||
|
|
||||||
float resolve(const SizeValue &size, float parentSize, float axisLength);
|
float resolve(const SizeValue &size, float parentSize, float axisLength);
|
||||||
void offsetPosition(float dx, float dy);
|
void offsetPosition(float dx, float dy);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user