Zepha/src/client/gui/Element.h

252 lines
7.7 KiB
C++

#pragma once
#include <functional>
#include "client/gui/Gui.h"
#include "world/dim/ent/DrawableEntity.h"
#include "util/Types.h"
#include "client/gui/Style.h"
class Window;
class Renderer;
namespace Gui {
class Root;
/**
* Base class for all Gui Elements.
* Represents an element within a Gui Root, which may be drawn to the screen.
*/
class Element {
friend class Root;
friend class BoxElement;
friend class TextElement;
public:
Element(Root& root, vec<StyleSheet>& stylesheets): root(root), stylesheets(stylesheets) {}
~Element();
/** Gets a reference to the element's props. */
const Props& getProps() const;
/** Sets the element's props to the struct specified. */
virtual void setProps(const Props& props);
/** Sets a style rule on the element. */
virtual void setProp(Prop prop, const std::any& value);
/** Recalculates the element based on its props. Call when props or stylesheets change. */
virtual void updateElement();
/** Draws the element to the screen. */
virtual void draw(Renderer& renderer);
sptr<Element> get(u32 ind);
sptr<Element> get(const string& id);
template<typename E, std::enable_if_t<std::is_base_of_v<Element, E>, bool> = true>
sptr<E> get(u32 ind) {
return std::dynamic_pointer_cast<E>(get(ind));
}
/** Creates and prepends an element to this element. */
template<typename E, std::enable_if_t<std::is_base_of_v<Element, E>, bool> = true>
sptr<E> prepend(const Props& props = {}) {
const let elem = make_shared<E>(root, stylesheets);
elem->setProps(props);
prepend(elem);
return elem;
};
/** Prepends an existing element to this element. */
sptr<Element> prepend(sptr<Element> elem) {
children.insert(children.begin(), elem);
elem->parent = this;
updateElement();
return elem;
}
/** Creates and appends an element to this element. */
template<typename E, std::enable_if_t<std::is_base_of_v<Element, E>, bool> = true>
sptr<E> append(const Props& props = {}) {
const let elem = make_shared<E>(root, stylesheets);
elem->setProps(props);
append(elem);
return elem;
};
/** Appends an existing element to this element. */
sptr<Element> append(sptr<Element> elem) {
children.push_back(elem);
elem->parent = this;
updateElement();
return elem;
}
void clear();
void remove();
void onClick(const std::function<void(i32, bool)>& cb);
/** Returns the element's computed size. */
virtual vec2 getComputedSize() const;
/** Returns the element's computed content size, which is its size - padding. */
virtual vec2 getComputedContentSize() const;
/** Returns the element's explicit size. Unspecified dimensions are -1. */
virtual vec2 getExplicitSize() const;
/** Returns the element's computed position relative to its parent. */
virtual vec2 getComputedPos() const;
/** Returns the element's computed position relative to the screen. */
virtual vec2 getComputedScreenPos() const;
/** Returns the element's explicit position. Unspecified dimensions are nan. */
virtual vec2 getExplicitPos() const;
/** Gets a style value from the element's styles or the root's stylesheets. */
const optional<any> getStyle(Prop rule) const {
const optional<any> opt = props.get(rule);
if (opt) return *opt;
const let& classes = props.get<vec<string>>(Prop::CLASS);
if (!classes) return std::nullopt;
for (const let& ss : stylesheets) {
for (const string& className : *classes) {
const let& styles = ss.find(className);
if (styles == ss.end()) continue;
const optional<any> opt = styles->second.get(rule);
if (opt) return *opt;
}
}
return std::nullopt;
}
/** Gets a generic value from the element's styles or the root's stylesheets. */
template<typename V, Type T = Type::LITERAL, std::enable_if_t<T != Type::LENGTH, bool> = true>
const optional<V> getStyle(Prop rule) const {
const optional<V> opt = props.get<V, T>(rule);
if (opt) return *opt;
const let& classes = props.get<vec<string>>(Prop::CLASS);
if (!classes) return std::nullopt;
for (const let& ss : stylesheets) {
for (const string& className : *classes) {
const let& styles = ss.find(className);
if (styles == ss.end()) continue;
const optional<V> opt = styles->second.get<V, T>(rule);
if (opt) return *opt;
}
}
return std::nullopt;
}
/** Gets a LENGTH value from the element's styles or the root's stylesheets. */
template<typename V, Type T = Type::LITERAL, std::enable_if_t<T == Type::LENGTH, bool> = true>
const optional<V> getStyle(Prop rule) const {
ExpressionInfo info = getExpr();
const optional<V> opt = props.get<V, T>(rule, info);
if (opt) return *opt;
const let& classes = props.get<vec<string>>(Prop::CLASS);
if (!classes) return std::nullopt;
for (const let& ss : stylesheets) {
for (const string& className : *classes) {
const let& styles = ss.find(className);
if (styles == ss.end()) continue;
const optional<V> opt = styles->second.get<V, T>(rule, info);
if (opt) return *opt;
}
}
return std::nullopt;
}
/** Gets a LENGTH value from the element's styles or the root's stylesheets. */
template<typename V, Type T = Type::LITERAL, std::enable_if_t<T == Type::LENGTH, bool> = true>
const optional<V> getStyleWithExpr(Prop rule, const ExpressionInfo& expr) const {
const optional<V> opt = props.get<V, T>(rule, expr);
if (opt) return *opt;
const let& classes = props.get<vec<string>>(Prop::CLASS);
if (!classes) return std::nullopt;
for (const let& ss : stylesheets) {
for (const string& className : *classes) {
const let& styles = ss.find(className);
if (styles == ss.end()) continue;
const optional<V> opt = styles->second.get<V, T>(rule, expr);
if (opt) return *opt;
}
}
return std::nullopt;
}
/** Gets a style value from the element's styles or the root's stylesheets. */
template<typename V, Type T = Type::LITERAL>
const V getStyle(Prop rule, V def) const {
const optional<V> opt = getStyle<V, T>(rule);
if (opt) return *opt;
return def;
}
/** Gets a LENGTH value from the element's styles or the root's stylesheets, with a custom ExpressionInfo. */
template<typename V, Type T = Type::LITERAL, std::enable_if_t<T == Type::LENGTH, bool> = true>
const V getStyleWithExpr(Prop rule, V def, const ExpressionInfo& info) const {
const optional<V> opt = getStyleWithExpr<V, T>(rule, info);
if (opt) return *opt;
return def;
}
/** Returns an ExpressionInfo object for evaluating Lengths. */
virtual ExpressionInfo getExpr() const;
protected:
/**
* Called by the root when the mouse position changes.
* Returns a boolean if the element or its children are hovered.
*/
bool handleMouseHover(ivec2 mousePos, bool& pointer);
/**
* Called by the root when the mouse clicks.
* Triggers a click interaction on the hovered element.
*/
bool handleMouseClick(ivec2 mousePos, u32 button, bool down);
Root& root;
Props props;
vec<StyleSheet>& stylesheets;
DrawableEntity entity;
Element* parent = nullptr;
vec<sptr<Element>> children;
bool hovered = false;
std::function<void(u32, bool)> clickCb = nullptr;
/** The screen offset of the parent. */
vec2 parentOffset {};
/** The last computed size of the element. */
mutable vec2 computedSize {};
/** The element's implicit size, as defined by the parent layout. */
vec2 layoutSize { -1, -1 };
/** The element's implicit position, as defined by the parent layout. */
vec2 layoutPosition {};
/** Updates child sizes and offsets based on layout styles. */
virtual void layoutChildren();
};
}