244 lines
7.3 KiB
C++
244 lines
7.3 KiB
C++
#include "Element.h"
|
|
|
|
#include "util/Util.h"
|
|
#include "client/gui/Root.h"
|
|
#include "client/graph/Renderer.h"
|
|
|
|
Gui::Element::~Element() {
|
|
for (let& child : children) child->parent = nullptr;
|
|
}
|
|
|
|
const Gui::Props& Gui::Element::getProps() const {
|
|
return props;
|
|
}
|
|
|
|
void Gui::Element::setProps(const Props& props) {
|
|
this->props = props;
|
|
updateElement();
|
|
}
|
|
|
|
void Gui::Element::setProp(Gui::Prop prop, const any& value) {
|
|
props.props[prop] = value;
|
|
}
|
|
|
|
sptr<Gui::Element> Gui::Element::get(u32 ind) {
|
|
if (ind >= children.size()) return nullptr;
|
|
return children[ind];
|
|
}
|
|
|
|
sptr<Gui::Element> Gui::Element::get(const string& id) {
|
|
for (let& child : children) {
|
|
if (child->props.get<string>(Prop::ID) == id) return child;
|
|
}
|
|
for (let& child : children) {
|
|
let res = child->get(id);
|
|
if (res != nullptr) return res;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void Gui::Element::clear() {
|
|
children.clear();
|
|
}
|
|
|
|
void Gui::Element::remove() {
|
|
if (parent) {
|
|
for (let it = parent->children.begin(); it != parent->children.end(); it++) {
|
|
if (it->get() == this) {
|
|
parent->children.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
parent = nullptr;
|
|
}
|
|
}
|
|
|
|
void Gui::Element::onClick(const std::function<void(i32, bool)>& cb) {
|
|
clickCb = cb;
|
|
}
|
|
|
|
Gui::ExpressionInfo Gui::Element::getExpr() const {
|
|
return {
|
|
parent ? parent->getComputedSize() : vec2 {},
|
|
getComputedSize()
|
|
};
|
|
}
|
|
|
|
vec2 Gui::Element::getComputedSize() const {
|
|
let size = getStyleWithExpr<vec2, Type::LENGTH>(Prop::SIZE, vec2(nanf("")),
|
|
{ parent ? parent->computedSize : vec2 {}, {} });
|
|
if (std::isnan(size.x)) size.x = std::max(layoutSize.x, 0.f);
|
|
if (std::isnan(size.y)) size.y = std::max(layoutSize.y, 0.f);
|
|
computedSize = size;
|
|
return size;
|
|
}
|
|
|
|
vec2 Gui::Element::getComputedContentSize() const {
|
|
let size = getComputedSize();
|
|
let padding = getStyle<vec4, Type::LENGTH>(Prop::PADDING, {});
|
|
return glm::max(vec2 { size.x - padding.x - padding.z, size.y - padding.y - padding.w }, vec2(0));
|
|
}
|
|
|
|
vec2 Gui::Element::getExplicitSize() const {
|
|
return getStyle<vec2, Type::LENGTH>(Prop::SIZE, vec2(-1));
|
|
}
|
|
|
|
vec2 Gui::Element::getComputedPos() const {
|
|
return getStyle<vec2, Type::LENGTH>(Prop::POS, layoutPosition);
|
|
}
|
|
|
|
vec2 Gui::Element::getComputedScreenPos() const {
|
|
return getComputedPos() + parentOffset;
|
|
}
|
|
|
|
vec2 Gui::Element::getExplicitPos() const {
|
|
return getStyle<vec2, Type::LENGTH>(Prop::POS, vec2(nanf("")));
|
|
}
|
|
|
|
bool Gui::Element::handleMouseHover(ivec2 mousePos, bool& pointer) {
|
|
bool childIntersects = false;
|
|
for (let& child : children) if (child->handleMouseHover(mousePos, pointer)) childIntersects = true;
|
|
|
|
vec2 size = getComputedSize();
|
|
vec2 pos = getComputedScreenPos();
|
|
bool intersects = mousePos.x >= pos.x && mousePos.x <= pos.x + size.x &&
|
|
mousePos.y >= pos.y && mousePos.y <= pos.y + size.y;
|
|
|
|
let cursor = getStyle<string>(Prop::CURSOR);
|
|
if (intersects && cursor) pointer = *cursor == "pointer";
|
|
|
|
if (hovered != intersects) {
|
|
hovered = intersects;
|
|
updateElement();
|
|
}
|
|
|
|
return intersects || childIntersects;
|
|
}
|
|
|
|
bool Gui::Element::handleMouseClick(ivec2 mousePos, u32 button, bool down) {
|
|
for (i32 i = children.size() - 1; i >= 0; i--)
|
|
if (children[i]->handleMouseClick(mousePos, button, down)) return true;
|
|
|
|
vec2 size = getComputedSize();
|
|
vec2 pos = getComputedScreenPos();
|
|
bool intersects = mousePos.x >= pos.x && mousePos.x <= pos.x + size.x &&
|
|
mousePos.y >= pos.y && mousePos.y <= pos.y + size.y;
|
|
if (!intersects) return false;
|
|
|
|
if (clickCb) clickCb(button, down);
|
|
return clickCb != nullptr;
|
|
}
|
|
|
|
void Gui::Element::draw(Renderer& renderer) {
|
|
entity.draw(renderer);
|
|
for (let& child : children) child->draw(renderer);
|
|
}
|
|
|
|
void Gui::Element::updateElement() {
|
|
layoutChildren();
|
|
}
|
|
|
|
void Gui::Element::layoutChildren() {
|
|
const string& layout = getStyle<string>(Prop::LAYOUT, "");
|
|
|
|
switch (Util::hash(layout.data())) {
|
|
default:
|
|
case Util::hash("flex"): {
|
|
const string& direction = getStyle<string>(Prop::DIRECTION, "");
|
|
|
|
/**
|
|
* The primary flex direction. Stored as a bool but interpreted as an index into a vec2.
|
|
* 1 if the direction is column, 0 if it is row, indexes into `y` if column, `x` otherwise.
|
|
*/
|
|
|
|
const bool primary = direction != "row";
|
|
|
|
const string& hAlignRaw = getStyle<string>(Prop::H_ALIGN, "");
|
|
const string& vAlignRaw = getStyle<string>(Prop::V_ALIGN, "");
|
|
|
|
/**
|
|
* Parsed alignment of the horizontal and vertical axes.
|
|
* -1: Start, 0: Center, 1: End, 2: Stretch
|
|
*/
|
|
|
|
const i8vec2 align = {
|
|
hAlignRaw == "left" ? -1 : hAlignRaw == "right" ? 1 : hAlignRaw == "center" ? 0 : 2,
|
|
vAlignRaw == "top" ? -1 : vAlignRaw == "bottom" ? 1 : vAlignRaw == "center" ? 0 : 2
|
|
};
|
|
|
|
/**
|
|
* The element gap across the primary axis.
|
|
*/
|
|
|
|
const f32 gap = getStyle<vec2, Type::LENGTH>(Prop::GAP, vec2(0))[primary];
|
|
const vec4& padding = getStyle<vec4, Type::LENGTH>(Prop::PADDING, {});
|
|
|
|
/*
|
|
* Calculates the explicit spaced used up by children across the primary axis,
|
|
* i.e. the space that is defined using WIDTH or HEIGHT.
|
|
* Counts the number of elements without explicitely defined primary height.
|
|
*/
|
|
|
|
let selfSize = getComputedContentSize();
|
|
f32 explicitSize = gap * (children.size() - 1);
|
|
u32 implicitCount = 0;
|
|
|
|
for (const let& child : children) {
|
|
if (!std::isnan(child->getExplicitPos()[primary])) continue;
|
|
let childExplicitSize = child->getExplicitSize();
|
|
if (childExplicitSize[primary] != -1) explicitSize += childExplicitSize[primary];
|
|
else implicitCount++;
|
|
let childMargin = child->getStyle<vec4, Type::LENGTH>(Prop::MARGIN, {});
|
|
explicitSize += childMargin[primary] + childMargin[primary + 2];
|
|
}
|
|
|
|
/**
|
|
* The cumulative layout offset of the children across the x and y axis.
|
|
*/
|
|
|
|
vec2 offset = { padding.x, padding.y };
|
|
if (align[primary] == 1) offset[primary] += selfSize[primary] - explicitSize - (gap * (children.size() - 1));
|
|
else if (align[primary] == 0) offset[primary] += selfSize[primary] / 2 -
|
|
explicitSize / 2 - (gap * (children.size() - 1)) / 2;
|
|
|
|
/**
|
|
* The amount of size each implicitly sized element should occupy.
|
|
*/
|
|
|
|
f32 implicitElemSize = floor((selfSize[primary] - explicitSize) / (std::max)(implicitCount, u32(1)));
|
|
|
|
vec2 selfOffset = getComputedPos() + parentOffset;
|
|
|
|
/**
|
|
* Position each child according to `offset`, size implicitly sized elements using `implicitElemSize`.
|
|
*/
|
|
|
|
for (const let& child : children) {
|
|
vec2 childExplicitSize = child->getExplicitSize();
|
|
vec4 childMargin = child->getStyle<vec4, Type::LENGTH>(Prop::MARGIN, {});
|
|
|
|
child->layoutSize[primary] =
|
|
(childExplicitSize[primary] == -1 && align[primary] == 2) ? implicitElemSize : 0;
|
|
|
|
if (align[!primary] == 2) child->layoutSize[!primary] = selfSize[!primary];
|
|
else child->layoutSize[!primary] = -1;
|
|
|
|
if (align[!primary] == 2 || align[!primary] == -1) child->layoutPosition[!primary] = offset[!primary];
|
|
else if (align[!primary] == 0) child->layoutPosition[!primary] =
|
|
selfSize[!primary] / 2 - childExplicitSize[!primary] / 2;
|
|
else if (align[!primary] == 1) child->layoutPosition[!primary] =
|
|
selfSize[!primary] - childExplicitSize[!primary];
|
|
child->layoutPosition[primary] = offset[primary];
|
|
|
|
if (std::isnan(child->getExplicitPos()[primary]))
|
|
offset[primary] += ((childExplicitSize[primary] == -1 && align[primary] == 2)
|
|
? implicitElemSize : childExplicitSize[primary])
|
|
+ gap + childMargin[primary] + childMargin[primary + 2];
|
|
|
|
child->parentOffset = selfOffset;
|
|
child->updateElement();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} |