basic flow layout
This commit is contained in:
@@ -4,16 +4,13 @@
|
||||
using namespace Seele;
|
||||
using namespace Seele::UI;
|
||||
|
||||
Array<RenderElement> Element::render() {
|
||||
RenderElement result;
|
||||
result.position = UVector2(0, 0);
|
||||
result.size = UVector2(style.width, style.height);
|
||||
result.backgroundColor = style.backgroundColor;
|
||||
result.fontSize = style.fontSize;
|
||||
result.fontFamily = style.fontFamily;
|
||||
return {result};
|
||||
Element::Element(Attributes attr, Array<Element*> _children) : attr(attr) {
|
||||
for (auto c : _children) {
|
||||
children.add(c);
|
||||
}
|
||||
for (auto& child : children) {
|
||||
child->setParent(this);
|
||||
}
|
||||
}
|
||||
|
||||
Element::Element(Attributes attr, Array<OElement> children) : attr(attr), children(std::move(children)) {}
|
||||
|
||||
Element::~Element() {}
|
||||
Element::~Element() {}
|
||||
|
||||
+125
-5
@@ -6,18 +6,138 @@
|
||||
|
||||
namespace Seele {
|
||||
namespace UI {
|
||||
struct RenderElement;
|
||||
struct UIRender {
|
||||
std::string text;
|
||||
PFontAsset font;
|
||||
uint32 fontSize;
|
||||
Vector textColor;
|
||||
Vector backgroundColor;
|
||||
Vector2 position;
|
||||
Vector2 dimensions;
|
||||
uint32 z;
|
||||
uint32 level;
|
||||
};
|
||||
DECLARE_REF(Element)
|
||||
class Element {
|
||||
public:
|
||||
Element(Attributes attr, Array<OElement> children);
|
||||
Element(Attributes attr, Array<Element*> children);
|
||||
virtual ~Element();
|
||||
virtual void calcStyle(Style parentStyle) = 0;
|
||||
Array<RenderElement> render();
|
||||
void setParent(PElement p) { parent = p; }
|
||||
// calculates the final style object taking into account inherited properties
|
||||
virtual void applyStyle(Style parentStyle) = 0;
|
||||
void calcStyle(Style parentStyle) {
|
||||
applyStyle(parentStyle);
|
||||
for (auto& child : children) {
|
||||
child->calcStyle(style);
|
||||
}
|
||||
}
|
||||
// calculates the relative positions of the child elements for the applied layout style
|
||||
virtual void layout(UVector2 parentSize) {
|
||||
if (style.maxWidthType == DimensionType::Auto) {
|
||||
maxDimensions.x = parentSize.x;
|
||||
}
|
||||
if (style.maxHeightType == DimensionType::Auto) {
|
||||
maxDimensions.y = parentSize.y;
|
||||
}
|
||||
if (style.widthType == DimensionType::Pixel) {
|
||||
dimensions.x = style.width;
|
||||
} else if (style.widthType == DimensionType::Percent) {
|
||||
dimensions.x = parentSize.x * style.width / 100.0f;
|
||||
}
|
||||
|
||||
if (style.heightType == DimensionType::Pixel) {
|
||||
dimensions.y = style.height;
|
||||
} else if (style.heightType == DimensionType::Percent) {
|
||||
dimensions.y = parentSize.y * style.height / 100.0f;
|
||||
}
|
||||
for (auto& child : children) {
|
||||
child->layout(maxDimensions);
|
||||
}
|
||||
switch (style.innerDisplay) {
|
||||
case InnerDisplayType::Flow:
|
||||
flowLayout();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// normal flow of the elements, inline elements go left to right, blocks get their own new lines
|
||||
void flowLayout() {
|
||||
// use a cursor going from left to right, top to bottom, starting at padding
|
||||
Vector2 cursor = Vector2(style.paddingLeft, style.paddingTop);
|
||||
uint32 lineHeight = 0;
|
||||
for (auto& child : children) {
|
||||
// inline elements flow left to right
|
||||
if (child->style.outerDisplay == OuterDisplayType::Inline) {
|
||||
// only static and relative elements actually reserve
|
||||
if (child->style.position == PositionType::Static || child->style.position == PositionType::Relative) {
|
||||
// create a new line in case the element doesnt fit on the current one anymore,
|
||||
// but keep in current line in case we are still at the start, as a new line wouldnt help here
|
||||
if (cursor.x + child->dimensions.x > dimensions.x - child->style.left && cursor.x != 0) {
|
||||
cursor.x = 0;
|
||||
cursor.y += lineHeight;
|
||||
}
|
||||
// todo: border
|
||||
child->position.x = cursor.x + child->style.marginLeft;
|
||||
child->position.y = cursor.y + child->style.marginTop;
|
||||
cursor.x = child->position.x + child->dimensions.x;
|
||||
cursor.y = child->position.y + child->dimensions.y;
|
||||
// apply offsets after updating the cursor for relative elements
|
||||
if (child->style.position == PositionType::Relative) {
|
||||
child->position.x += child->style.left;
|
||||
child->position.y += child->style.top;
|
||||
}
|
||||
lineHeight = std::max(lineHeight, child->style.lineHeight);
|
||||
}
|
||||
// block elements always get their own lines, like paragraphs
|
||||
} else if (child->style.outerDisplay == OuterDisplayType::Block) {
|
||||
// static and relative require space to be created for the elements
|
||||
if (child->style.position == PositionType::Static || child->style.position == PositionType::Relative) {
|
||||
// create a new line in case we are not already at one
|
||||
if (cursor.x != 0) {
|
||||
cursor.x = 0;
|
||||
cursor.y += lineHeight;
|
||||
}
|
||||
child->position.x = cursor.x + child->style.marginLeft;
|
||||
child->position.y = cursor.y + child->style.marginTop;
|
||||
// new line after block
|
||||
cursor.x = 0;
|
||||
cursor.y = child->position.y + child->dimensions.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (style.widthType == DimensionType::Auto) {
|
||||
dimensions.x = cursor.x + style.paddingRight;
|
||||
}
|
||||
if (style.heightType == DimensionType::Auto) {
|
||||
dimensions.y = cursor.y + style.paddingBottom;
|
||||
}
|
||||
}
|
||||
virtual Array<UIRender> render(Vector2 anchor, uint32 level) {
|
||||
Array<UIRender> result = {
|
||||
UIRender{
|
||||
.backgroundColor = style.backgroundColor,
|
||||
.position = position + anchor,
|
||||
.dimensions = dimensions,
|
||||
.z = style.z,
|
||||
.level = level,
|
||||
},
|
||||
};
|
||||
for (auto& child : children) {
|
||||
for (const auto& render : child->render(position + anchor, level+1)) {
|
||||
result.add(render);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Style style;
|
||||
Vector2 maxDimensions = Vector2(0);
|
||||
// calculated position relative to parent
|
||||
Vector2 position = Vector2(0);
|
||||
// calculated dimensions in pixels after layouting
|
||||
Vector2 dimensions = Vector2(0);
|
||||
|
||||
protected:
|
||||
Style style;
|
||||
Attributes attr;
|
||||
PElement parent = nullptr;
|
||||
Array<OElement> children;
|
||||
};
|
||||
DEFINE_REF(Element)
|
||||
|
||||
@@ -7,8 +7,15 @@ namespace UI {
|
||||
template<StyleClass... classes>
|
||||
class Div : public Element {
|
||||
public:
|
||||
Div(Attributes attr, std::initializer_list<OElement> children) : Element(attr, children) { style.displayType = DisplayType::Block; }
|
||||
Div(Attributes attr, Array<Element*> children) : Element(attr, std::move(children)) { }
|
||||
virtual ~Div() {}
|
||||
virtual void applyStyle(Style parentStyle) override {
|
||||
style = parentStyle;
|
||||
style.outerDisplay = OuterDisplayType::Block;
|
||||
style.widthType = DimensionType::Percent;
|
||||
style.width = 100;
|
||||
(classes::apply(style), ...);
|
||||
}
|
||||
private:
|
||||
};
|
||||
} // namespace UI
|
||||
|
||||
@@ -4,10 +4,36 @@
|
||||
|
||||
namespace Seele {
|
||||
namespace UI {
|
||||
class Text : public Element {
|
||||
template <StyleClass... classes> class Text : public Element {
|
||||
public:
|
||||
Text(std::string text) : Element({}, {}), text(text) {}
|
||||
virtual void calcStyle(Style parentStyle) override { style = parentStyle; }
|
||||
virtual void applyStyle(Style parentStyle) {
|
||||
style = parentStyle;
|
||||
style.outerDisplay = OuterDisplayType::Inline;
|
||||
(classes::apply(style), ...);
|
||||
}
|
||||
virtual void layout(UVector2 parentSize) override {
|
||||
size_t cursor = 0;
|
||||
for (uint32 i = 0; i < text.size() - 1; ++i) {
|
||||
dimensions.x += style.fontFamily->getGlyphData(text[i]).advance / 64.0f;
|
||||
}
|
||||
dimensions.x += style.fontFamily->getGlyphData(text[text.size() - 1]).size.x;
|
||||
dimensions.y = style.fontSize;
|
||||
}
|
||||
virtual Array<UIRender> render(Vector2 anchor, uint32 level) override {
|
||||
return {
|
||||
UIRender{
|
||||
.text = text,
|
||||
.font = style.fontFamily,
|
||||
.fontSize = style.fontSize,
|
||||
.position = position + anchor,
|
||||
.dimensions = dimensions,
|
||||
.z = style.z,
|
||||
.level = level,
|
||||
},
|
||||
};
|
||||
}
|
||||
// height = fontsize * 1.12
|
||||
|
||||
private:
|
||||
std::string text;
|
||||
|
||||
+30
-12
@@ -1,27 +1,45 @@
|
||||
#pragma once
|
||||
#include "MinimalEngine.h"
|
||||
#include "Style.h"
|
||||
#include "Asset/AssetRegistry.h"
|
||||
|
||||
namespace Seele {
|
||||
namespace UI {
|
||||
template <typename C>
|
||||
concept StyleClass = requires(C c, Style& s) { c.apply(s); };
|
||||
concept StyleClass = requires(C c, UI::Style& s) { c.apply(s); };
|
||||
|
||||
template <uint32 w, DimensionType widthType> struct WidthClass {
|
||||
static void apply(Style& s) {
|
||||
template <uint32 w, UI::DimensionType widthType> struct WidthClass {
|
||||
static void apply(UI::Style& s) {
|
||||
s.width = w;
|
||||
s.widthType = widthType;
|
||||
}
|
||||
};
|
||||
using W_Full = WidthClass<100, DimensionType::Percent>;
|
||||
using W_1 = WidthClass<2, DimensionType::Pixel>;
|
||||
using W_Full = WidthClass<100, UI::DimensionType::Percent>;
|
||||
using W_1 = WidthClass<2, UI::DimensionType::Pixel>;
|
||||
|
||||
template <DisplayType displayType> struct DisplayClass {
|
||||
static void apply(Style& s) { s.displayType = displayType; }
|
||||
template <UI::OuterDisplayType outer, UI::InnerDisplayType inner> struct DisplayClass {
|
||||
static void apply(UI::Style& s) {
|
||||
s.outerDisplay = outer;
|
||||
s.innerDisplay = inner;
|
||||
}
|
||||
};
|
||||
using Hidden = DisplayClass<DisplayType::Hidden>;
|
||||
using Block = DisplayClass<DisplayType::Block>;
|
||||
using Flex = DisplayClass<DisplayType::Flex>;
|
||||
using Hidden = DisplayClass<UI::OuterDisplayType::Hidden, UI::InnerDisplayType::Flow>;
|
||||
using Block = DisplayClass<UI::OuterDisplayType::Block, UI::InnerDisplayType::Flow>;
|
||||
using Inline = DisplayClass<UI::OuterDisplayType::Inline, UI::InnerDisplayType::Flow>;
|
||||
|
||||
} // namespace UI
|
||||
template <size_t N> struct StringLiteral {
|
||||
constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, value); }
|
||||
|
||||
char value[N];
|
||||
};
|
||||
template <StringLiteral lit> struct FontClass {
|
||||
static void apply(UI::Style& s) { s.fontFamily = AssetRegistry::findFont("", lit.value); }
|
||||
};
|
||||
|
||||
using Font_Arial = FontClass<"arial">;
|
||||
|
||||
template <Vector backgroundColor> struct BackgroundColorClass {
|
||||
static void apply(UI::Style& s) { s.backgroundColor = backgroundColor; }
|
||||
};
|
||||
|
||||
using BG_Red = BackgroundColorClass<Vector(1, 0, 0)>;
|
||||
} // namespace Seele
|
||||
+23
-10
@@ -8,32 +8,41 @@ namespace UI {
|
||||
enum class DimensionType {
|
||||
Pixel,
|
||||
Percent, // EM, PEM, etc...
|
||||
Auto,
|
||||
};
|
||||
enum class PositionType {
|
||||
Relative,
|
||||
Static,
|
||||
Relative,
|
||||
Absolute,
|
||||
Sticky,
|
||||
};
|
||||
enum class DisplayType {
|
||||
enum class OuterDisplayType {
|
||||
Inline,
|
||||
Hidden,
|
||||
Block,
|
||||
Hidden,
|
||||
};
|
||||
enum class InnerDisplayType {
|
||||
Flow,
|
||||
Flex,
|
||||
Grid,
|
||||
};
|
||||
struct Style {
|
||||
DimensionType widthType = DimensionType::Pixel;
|
||||
uint32 width = 0;
|
||||
DimensionType heightType = DimensionType::Pixel;
|
||||
uint32 height = 0;
|
||||
DimensionType widthType = DimensionType::Auto;
|
||||
float width = 0;
|
||||
DimensionType maxWidthType = DimensionType::Auto;
|
||||
float maxWidth = 0;
|
||||
DimensionType heightType = DimensionType::Auto;
|
||||
float height = 0;
|
||||
DimensionType maxHeightType = DimensionType::Auto;
|
||||
float maxHeight = 0;
|
||||
uint32 z = 0;
|
||||
Vector backgroundColor = Vector(1, 1, 1);
|
||||
DisplayType displayType = DisplayType::Inline;
|
||||
OuterDisplayType outerDisplay = OuterDisplayType::Inline;
|
||||
InnerDisplayType innerDisplay = InnerDisplayType::Flow;
|
||||
PositionType position = PositionType::Relative;
|
||||
PFontAsset fontFamily;
|
||||
uint32 lineHeight = 12;
|
||||
uint32 fontSize = 12;
|
||||
uint32 lineHeight = 48;
|
||||
uint32 fontSize = 48;
|
||||
uint32 fontWeight = 0;
|
||||
uint32 paddingTop = 0;
|
||||
uint32 paddingBottom = 0;
|
||||
@@ -44,6 +53,10 @@ struct Style {
|
||||
uint32 marginLeft = 0;
|
||||
uint32 marginRight = 0;
|
||||
uint32 gap = 0;
|
||||
uint32 top = 0;
|
||||
uint32 bottom = 0;
|
||||
uint32 left = 0;
|
||||
uint32 right = 0;
|
||||
};
|
||||
} // namespace UI
|
||||
} // namespace Seele
|
||||
+4
-17
@@ -3,26 +3,13 @@
|
||||
|
||||
namespace Seele {
|
||||
namespace UI {
|
||||
struct RenderElement {
|
||||
// position relative to target viewport in pixels
|
||||
UVector2 position;
|
||||
// size relative to target viewport in pixels
|
||||
UVector2 size;
|
||||
// background color of area
|
||||
Vector backgroundColor;
|
||||
// text to render
|
||||
std::string text;
|
||||
// font size in pixels
|
||||
uint32 fontSize;
|
||||
// font family
|
||||
PFontAsset fontFamily;
|
||||
};
|
||||
class System {
|
||||
public:
|
||||
System(OElement rootElement) : rootElement(std::move(rootElement)) {}
|
||||
~System();
|
||||
Array<RenderElement> render(UVector2 viewport) {
|
||||
}
|
||||
~System() {}
|
||||
void style() { rootElement->calcStyle(UI::Style()); }
|
||||
void layout(UVector2 viewport) { rootElement->layout(viewport); }
|
||||
Array<UIRender> render() { return rootElement->render(Vector2(0, 0), 1); }
|
||||
|
||||
private:
|
||||
OElement rootElement;
|
||||
|
||||
Reference in New Issue
Block a user