New Style API that throws if type is invalid.
parent
e41459090f
commit
455ef5da6a
|
@ -6,130 +6,49 @@
|
|||
#include "client/graph/mesh/EntityMesh.h"
|
||||
|
||||
void Gui::BoxElement::updateElement() {
|
||||
const ivec2 pos = getComputedPos() * static_cast<i32>(PX_SCALE);
|
||||
const ivec2 size = getComputedSize() * static_cast<i32>(PX_SCALE);
|
||||
const string& backgroundImage = getStyle<string>(Style::Rule::BACKGROUND_IMAGE, "");
|
||||
const vec4 backgroundColor = getStyle<vec4, Style::Type::COLOR>(Style::Rule::BACKGROUND_COLOR, {{}});
|
||||
let rawBg = getStyle(Style::Rule::BACKGROUND);
|
||||
|
||||
auto mesh = std::make_unique<EntityMesh>();
|
||||
if (!backgroundImage.empty()) {
|
||||
const let& tex = *root.atlas[backgroundImage];
|
||||
mesh->create({
|
||||
{ { 0, 0, 0 }, { tex.uv.x, tex.uv.y, 0, 1 }, vec3(1), true, {}, {}, {} },
|
||||
{ { 0, size.y, 0 }, { tex.uv.x, tex.uv.w, 0, 1 }, vec3(1), true, {}, {}, {} },
|
||||
{ { size.x, size.y, 0 }, { tex.uv.z, tex.uv.w, 0, 1 }, vec3(1), true, {}, {}, {} },
|
||||
{ { size.x, 0, 0 }, { tex.uv.z, tex.uv.y, 0, 1 }, vec3(1), true, {}, {}, {} }
|
||||
}, { 0, 1, 2, 2, 3, 0 });
|
||||
}
|
||||
else if (backgroundColor.a != 0) {
|
||||
mesh->create({
|
||||
{ { 0, 0, 0 }, backgroundColor, vec3(1), false, {}, {}, {} },
|
||||
{ { 0, size.y, 0 }, backgroundColor, vec3(1), false, {}, {}, {} },
|
||||
{ { size.x, size.y, 0 }, backgroundColor, vec3(1), false, {}, {}, {} },
|
||||
{ { size.x, 0, 0 }, backgroundColor, vec3(1), false, {}, {}, {} }
|
||||
}, { 0, 1, 2, 2, 3, 0 });
|
||||
bool isDirty = false;
|
||||
if ((!rawBg && curBg) || (rawBg && !curBg)) isDirty = true;
|
||||
else if (rawBg && curBg) {
|
||||
if (rawBg->type() != curBg->type()) isDirty = true;
|
||||
else if (rawBg->type() == typeid(vec4)) isDirty = any_cast<vec4>(*rawBg) != any_cast<vec4>(*curBg);
|
||||
else if (rawBg->type() == typeid(string)) isDirty = any_cast<string>(*rawBg) != any_cast<string>(*curBg);
|
||||
}
|
||||
curBg = rawBg;
|
||||
|
||||
auto model = make_shared<Model>();
|
||||
model->fromMesh(std::move(mesh));
|
||||
entity.setModel(model);
|
||||
entity.setPos(vec3(pos, 0) + (parent ? parent->entity.getPos() : vec3 {}));
|
||||
|
||||
layoutChildren();
|
||||
}
|
||||
|
||||
void Gui::BoxElement::layoutChildren() {
|
||||
const string& layout = getStyle<string>(Style::Rule::LAYOUT, "");
|
||||
|
||||
switch (Util::hash(layout.data())) {
|
||||
default:
|
||||
case Util::hash("flex"): {
|
||||
const string& direction = getStyle<string>(Style::Rule::DIRECTION, "");
|
||||
if (isDirty) {
|
||||
std::cout << "dirty" << std::endl;
|
||||
const let bgColor = getStyle<vec4, Style::Type::COLOR>(Style::Rule::BACKGROUND);
|
||||
const string bgImage = getStyle<string>(Style::Rule::BACKGROUND, "");
|
||||
|
||||
/**
|
||||
* 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>(Style::Rule::H_ALIGN, "");
|
||||
const string& vAlignRaw = getStyle<string>(Style::Rule::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 i32 gap = getStyle<ivec2>(Style::Rule::GAP, ivec2(0))[primary];
|
||||
const ivec4& padding = getStyle<ivec4>(Style::Rule::PADDING, ivec4 {});
|
||||
|
||||
/*
|
||||
* 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();
|
||||
i32 explicitSize = gap * (children.size() - 1);
|
||||
usize implicitCount = 0;
|
||||
|
||||
for (const let& child : children) {
|
||||
let childExplicitSize = child->getExplicitSize();
|
||||
if (childExplicitSize[primary] != -1) explicitSize += childExplicitSize[primary];
|
||||
else implicitCount++;
|
||||
let mesh = std::make_unique<EntityMesh>();
|
||||
if (bgColor && bgColor->a != 0) {
|
||||
mesh->create({
|
||||
{ { 0, 0, 0 }, *bgColor, vec3(1), false, {}, {}, {} },
|
||||
{ { 0, 1, 0 }, *bgColor, vec3(1), false, {}, {}, {} },
|
||||
{ { 1, 1, 0 }, *bgColor, vec3(1), false, {}, {}, {} },
|
||||
{ { 1, 0, 0 }, *bgColor, vec3(1), false, {}, {}, {} }
|
||||
}, { 0, 1, 2, 2, 3, 0 });
|
||||
}
|
||||
|
||||
/**
|
||||
* The cumulative layout offset of the children across the x and y axis.
|
||||
*/
|
||||
|
||||
ivec2 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.
|
||||
*/
|
||||
|
||||
i32 implicitElemSize = floor((selfSize[primary] - explicitSize) /
|
||||
(std::max)(implicitCount, static_cast<usize>(1)));
|
||||
|
||||
/**
|
||||
* Position each child according to `offset`, size implicitly sized elements using `implicitElemSize`.
|
||||
*/
|
||||
|
||||
for (const let& child : children) {
|
||||
let childExplicitSize = child->getExplicitSize();
|
||||
|
||||
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];
|
||||
|
||||
offset[primary] += ((childExplicitSize[primary] == -1 && align[primary] == 2)
|
||||
? implicitElemSize : childExplicitSize[primary]) + gap;
|
||||
|
||||
child->updateElement();
|
||||
else if (!bgImage.empty()) {
|
||||
const let& tex = *root.atlas[bgImage];
|
||||
mesh->create({
|
||||
{ { 0, 0, 0 }, { tex.uv.x, tex.uv.y, 0, 1 }, vec3(1), true, {}, {}, {} },
|
||||
{ { 0, 1, 0 }, { tex.uv.x, tex.uv.w, 0, 1 }, vec3(1), true, {}, {}, {} },
|
||||
{ { 1, 1, 0 }, { tex.uv.z, tex.uv.w, 0, 1 }, vec3(1), true, {}, {}, {} },
|
||||
{ { 1, 0, 0 }, { tex.uv.z, tex.uv.y, 0, 1 }, vec3(1), true, {}, {}, {} }
|
||||
}, { 0, 1, 2, 2, 3, 0 });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let model = make_shared<Model>();
|
||||
model->fromMesh(std::move(mesh));
|
||||
entity.setModel(model);
|
||||
}
|
||||
|
||||
entity.setScale(vec3(getComputedSize(), 0) * static_cast<f32>(PX_SCALE));
|
||||
entity.setPos((vec3(getComputedPos(), 0) + vec3(parentOffset, 0)) * static_cast<f32>(PX_SCALE));
|
||||
|
||||
Element::updateElement();
|
||||
}
|
|
@ -5,9 +5,9 @@ namespace Gui {
|
|||
public:
|
||||
using Element::Element;
|
||||
|
||||
protected:
|
||||
virtual void updateElement() override;
|
||||
|
||||
virtual void layoutChildren() override;
|
||||
|
||||
protected:
|
||||
optional<any> curBg;
|
||||
};
|
||||
}
|
|
@ -12,13 +12,8 @@ void Gui::Element::setProps(const Props& props) {
|
|||
updateElement();
|
||||
}
|
||||
|
||||
const std::any& Gui::Element::getStyle(Style::Rule style) const {
|
||||
return props.styles.get(style);
|
||||
}
|
||||
|
||||
void Gui::Element::setStyle(Style::Rule style, const std::any& value) {
|
||||
props.styles.rules[style] = value;
|
||||
updateElement();
|
||||
}
|
||||
|
||||
ivec2 Gui::Element::getComputedSize() {
|
||||
|
@ -30,7 +25,7 @@ ivec2 Gui::Element::getComputedSize() {
|
|||
|
||||
ivec2 Gui::Element::getComputedContentSize() {
|
||||
let size = getComputedSize();
|
||||
let padding = props.styles.get<ivec4>(Style::Rule::PADDING, ivec4 {});
|
||||
let padding = getStyle<ivec4>(Style::Rule::PADDING, {});
|
||||
return glm::max(ivec2 { size.x - padding.x - padding.z, size.y - padding.y - padding.w }, ivec2 {});
|
||||
}
|
||||
|
||||
|
@ -60,4 +55,109 @@ bool Gui::Element::handleMouseClick(u32 button, bool down) {
|
|||
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>(Style::Rule::LAYOUT, "");
|
||||
|
||||
switch (Util::hash(layout.data())) {
|
||||
default:
|
||||
case Util::hash("flex"): {
|
||||
const string& direction = getStyle<string>(Style::Rule::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>(Style::Rule::H_ALIGN, "");
|
||||
const string& vAlignRaw = getStyle<string>(Style::Rule::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 i32 gap = getStyle<ivec2>(Style::Rule::GAP, ivec2(0))[primary];
|
||||
const ivec4& padding = getStyle<ivec4>(Style::Rule::PADDING, ivec4 {});
|
||||
|
||||
/*
|
||||
* 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();
|
||||
i32 explicitSize = gap * (children.size() - 1);
|
||||
usize implicitCount = 0;
|
||||
|
||||
for (const let& child : children) {
|
||||
let childExplicitSize = child->getExplicitSize();
|
||||
if (childExplicitSize[primary] != -1) explicitSize += childExplicitSize[primary];
|
||||
else implicitCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cumulative layout offset of the children across the x and y axis.
|
||||
*/
|
||||
|
||||
ivec2 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.
|
||||
*/
|
||||
|
||||
i32 implicitElemSize = floor((selfSize[primary] - explicitSize) /
|
||||
(std::max)(implicitCount, static_cast<usize>(1)));
|
||||
|
||||
ivec2 selfOffset = getComputedPos() + parentOffset;
|
||||
|
||||
/**
|
||||
* Position each child according to `offset`, size implicitly sized elements using `implicitElemSize`.
|
||||
*/
|
||||
|
||||
for (const let& child : children) {
|
||||
let childExplicitSize = child->getExplicitSize();
|
||||
|
||||
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];
|
||||
|
||||
offset[primary] += ((childExplicitSize[primary] == -1 && align[primary] == 2)
|
||||
? implicitElemSize : childExplicitSize[primary]) + gap;
|
||||
|
||||
child->parentOffset = selfOffset;
|
||||
|
||||
child->updateElement();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,18 +25,21 @@ namespace Gui {
|
|||
Style styles {};
|
||||
};
|
||||
|
||||
Element(Root& root): root(root) {}
|
||||
Element(Root& root, vec<StyleSheet>& stylesheets): root(root), stylesheets(stylesheets) {}
|
||||
|
||||
~Element();
|
||||
|
||||
void setProps(const Props& props);
|
||||
virtual void setProps(const Props& props);
|
||||
|
||||
const std::any& getStyle(Style::Rule style) const;
|
||||
void setStyle(Style::Rule style, const std::any& value);
|
||||
virtual void setStyle(Style::Rule style, const std::any& value);
|
||||
|
||||
virtual void updateElement();
|
||||
|
||||
virtual void draw(Renderer& renderer);
|
||||
|
||||
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);
|
||||
const let elem = make_shared<E>(root, stylesheets);
|
||||
elem->setProps(props);
|
||||
prepend(elem);
|
||||
return elem;
|
||||
|
@ -45,12 +48,13 @@ namespace Gui {
|
|||
sptr<Element> prepend(sptr<Element> elem) {
|
||||
children.push_front(elem);
|
||||
elem->parent = this;
|
||||
updateElement();
|
||||
return elem;
|
||||
}
|
||||
|
||||
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);
|
||||
const let elem = make_shared<E>(root, stylesheets);
|
||||
elem->setProps(props);
|
||||
append(elem);
|
||||
return elem;
|
||||
|
@ -59,6 +63,7 @@ namespace Gui {
|
|||
sptr<Element> append(sptr<Element> elem) {
|
||||
children.push_back(elem);
|
||||
elem->parent = this;
|
||||
updateElement();
|
||||
return elem;
|
||||
}
|
||||
|
||||
|
@ -67,29 +72,58 @@ namespace Gui {
|
|||
virtual ivec2 getExplicitSize();
|
||||
virtual ivec2 getComputedPos();
|
||||
|
||||
const optional<any> getStyle(Style::Rule rule) const {
|
||||
const optional<any> opt = props.styles.get(rule);
|
||||
if (opt) return *opt;
|
||||
for (const let& ss : stylesheets) {
|
||||
for (const string& className : props.classes) {
|
||||
const let& styles = ss.find(className);
|
||||
const optional<any> opt = styles->second.get(rule);
|
||||
if (opt) return *opt;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename V, Style::Type T = Style::Type::LITERAL>
|
||||
const V getStyle(Style::Rule rule, std::optional<V> def = std::nullopt) const {
|
||||
return props.styles.get<V, T>(rule, def);
|
||||
const optional<V> getStyle(Style::Rule rule) const {
|
||||
const optional<V> opt = props.styles.get<V, T>(rule);
|
||||
if (opt) return *opt;
|
||||
for (const let& ss : stylesheets) {
|
||||
for (const string& className : props.classes) {
|
||||
const let& styles = ss.find(className);
|
||||
const optional<V> opt = styles->second.get<V, T>(rule);
|
||||
if (opt) return *opt;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename V, Style::Type T = Style::Type::LITERAL>
|
||||
const V getStyle(Style::Rule rule, V def) const {
|
||||
const optional<V> opt = getStyle<V, T>(rule);
|
||||
if (opt) return *opt;
|
||||
return def;
|
||||
}
|
||||
|
||||
void handleMouseMove(ivec2 mousePos);
|
||||
bool handleMouseClick(u32 button, bool down);
|
||||
|
||||
virtual void draw(Renderer& renderer);
|
||||
|
||||
protected:
|
||||
Root& root;
|
||||
Props props;
|
||||
vec<StyleSheet>& stylesheets;
|
||||
|
||||
DrawableEntity entity;
|
||||
|
||||
Element* parent = nullptr;
|
||||
std::list<sptr<Element>> children;
|
||||
|
||||
bool hovered = false;
|
||||
|
||||
ivec2 parentOffset {};
|
||||
ivec2 layoutSize { -1, -1 };
|
||||
ivec2 layoutPosition {};
|
||||
|
||||
virtual void updateElement() {};
|
||||
|
||||
virtual void layoutChildren() {};
|
||||
virtual void layoutChildren();
|
||||
};
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
#include "Root.h"
|
||||
|
||||
#include "BoxElement.h"
|
||||
#include "util/Timer.h"
|
||||
#include "client/gui/BoxElement.h"
|
||||
|
||||
Gui::Root::Root(Window& window, TextureAtlas& atlas) :
|
||||
atlas(atlas),
|
||||
window(window),
|
||||
body(make_shared<BoxElement>(*this)) {
|
||||
body(make_shared<BoxElement>(*this, stylesheets)) {
|
||||
const ivec2 size = glm::ceil(vec2(window.getSize()) / static_cast<f32>(Gui::PX_SCALE));
|
||||
|
||||
body->setProps({
|
||||
|
@ -20,18 +21,25 @@ Gui::Root::Root(Window& window, TextureAtlas& atlas) :
|
|||
size = glm::ceil(vec2(window.getSize()) / static_cast<f32>(Gui::PX_SCALE));
|
||||
body->setStyle(Style::Rule::WIDTH, size.x);
|
||||
body->setStyle(Style::Rule::HEIGHT, size.y);
|
||||
body->setStyle(Style::Rule::BACKGROUND, vec4(rand(), 0, 0, 1));
|
||||
Timer t("Resize UI");
|
||||
body->updateElement();
|
||||
t.printElapsedMs();
|
||||
});
|
||||
|
||||
// window.input.bindMouseCallback()
|
||||
}
|
||||
|
||||
void Gui::Root::addStylesheet(const std::unordered_map<string, Style>& sheet) {
|
||||
stylesheets.emplace_back(sheet);
|
||||
}
|
||||
|
||||
const vec<std::unordered_map<string, Gui::Style>>& Gui::Root::getStylesheets() {
|
||||
return stylesheets;
|
||||
void Gui::Root::update() {
|
||||
const let pos = window.input.getMousePos();
|
||||
|
||||
}
|
||||
|
||||
void Gui::Root::draw(Renderer& renderer) {
|
||||
if (!body) return;
|
||||
body->draw(renderer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Gui {
|
|||
|
||||
template<typename E, std::enable_if_t<std::is_base_of_v<Element, E>, bool> = true>
|
||||
sptr<E> create(const Element::Props& props = {}, const vec<sptr<Element>>& children = {}) {
|
||||
let elem = make_shared<E>(*this);
|
||||
let elem = make_shared<E>(*this, stylesheets);
|
||||
elem->setProps(props);
|
||||
|
||||
for (const let& child : children) elem->append(child);
|
||||
|
@ -24,10 +24,11 @@ namespace Gui {
|
|||
|
||||
void addStylesheet(const std::unordered_map<string, Style>& sheet);
|
||||
|
||||
const vec<std::unordered_map<string, Style>>& getStylesheets();
|
||||
void update();
|
||||
|
||||
void draw(Renderer& renderer);
|
||||
|
||||
vec<StyleSheet> stylesheets;
|
||||
const sptr<Element> body;
|
||||
|
||||
TextureAtlas& atlas;
|
||||
|
@ -35,8 +36,6 @@ namespace Gui {
|
|||
private:
|
||||
Window& window;
|
||||
Window::RCBLock lock;
|
||||
|
||||
vec<std::unordered_map<string, Style>> stylesheets;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,7 @@ namespace Gui {
|
|||
|
||||
OVERFLOW,
|
||||
|
||||
BACKGROUND_COLOR,
|
||||
BACKGROUND_IMAGE
|
||||
BACKGROUND
|
||||
};
|
||||
|
||||
enum class Type {
|
||||
|
@ -31,9 +30,7 @@ namespace Gui {
|
|||
};
|
||||
|
||||
Style() = default;
|
||||
Style(const std::unordered_map<Rule, std::any>& rules): rules(rules) {}
|
||||
|
||||
private:
|
||||
Style(const std::unordered_map<Rule, any>& rules): rules(rules) {}
|
||||
|
||||
/**
|
||||
* Simple get. Returns an optional containing an any of
|
||||
|
@ -45,103 +42,127 @@ namespace Gui {
|
|||
if (it == rules.end()) return std::nullopt;
|
||||
return optional<any>(it->second);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
|
||||
/**
|
||||
* Returns an optional of type T of the specified Rule's value,
|
||||
* Returns an optional of type O of the specified Rule's value,
|
||||
* or an empty optional if the rule is not defined.
|
||||
* Throws if the rule is defined to a different type.
|
||||
*/
|
||||
|
||||
template<typename O, Type T = Type::LITERAL, std::enable_if<
|
||||
std::is_same_v<O, optional<typename O::value_type>> &&
|
||||
template<typename V, Type T = Type::LITERAL, std::enable_if_t<
|
||||
!(std::is_integral_v<V> ||
|
||||
std::is_floating_point_v<V> ||
|
||||
std::is_same_v<V, string>) &&
|
||||
T == Type::LITERAL, bool> = true>
|
||||
|
||||
const O get(Rule rule) const {
|
||||
const let raw = get(rule);
|
||||
if (!raw) return raw;
|
||||
if (raw->type() == typeid(typename O::value_type)) return raw;
|
||||
const optional<V> get(Rule rule) const {
|
||||
const optional<any> raw = get(rule);
|
||||
if (!raw) return std::nullopt;
|
||||
if (raw->type() == typeid(V)) return any_cast<V>(*raw);
|
||||
throw std::runtime_error("Rule value is of an incorrect type.");
|
||||
}
|
||||
|
||||
template<typename V, Type T = Type::LITERAL, std::enable_if_t<
|
||||
std::is_same_v<V, optional<typename V::value_type>>, bool> = true>
|
||||
const any& get(Rule rule) const {
|
||||
const any& v = get<typename V::value_type, T>(rule);
|
||||
if (&v == &ANY_MISSING) return V();
|
||||
if (v.type())
|
||||
return V(v)
|
||||
const let it = rules.find(rule);
|
||||
if (it == rules.end()) return ANY_MISSING;
|
||||
return it->second;
|
||||
}
|
||||
/**
|
||||
* get<O, T> specialization for string-like values.
|
||||
*/
|
||||
|
||||
template<typename V, Type T = Type::LITERAL, std::enable_if_t<(!std::is_same_v<V, string> &&
|
||||
!std::is_integral_v<V> && !std::is_floating_point_v<V>) && T == Type::LITERAL, bool> = true>
|
||||
|
||||
const V get(Rule rule, std::optional<V> def = std::nullopt) const {
|
||||
const let it = rules.find(rule);
|
||||
if (it == rules.end() && def) return *def;
|
||||
if (it == rules.end()) throw std::runtime_error("Field is missing.");
|
||||
if (it->second.type() == typeid(V)) return std::any_cast<V>(it->second);
|
||||
throw std::runtime_error("Field is incorrect type.");
|
||||
}
|
||||
|
||||
template<typename Str, Type T = Type::LITERAL, std::enable_if_t<
|
||||
std::is_same_v<Str, string> && T == Type::LITERAL, bool> = true>
|
||||
template<typename S, Type T = Type::LITERAL, std::enable_if_t<
|
||||
std::is_same_v<S, string> &&
|
||||
T == Type::LITERAL, bool> = true>
|
||||
|
||||
const Str get(Rule rule, std::optional<Str> def = std::nullopt) const {
|
||||
const let it = rules.find(rule);
|
||||
if (it == rules.end() && def) return *def;
|
||||
if (it == rules.end()) throw std::runtime_error("Field is missing.");
|
||||
if (it->second.type() == typeid(string)) return std::any_cast<string>(it->second);
|
||||
if (it->second.type() == typeid(const char*)) return std::any_cast<const char*>(it->second);
|
||||
throw std::runtime_error("Field is incorrect type.");
|
||||
const optional<S> get(Rule rule) const {
|
||||
const optional<any> raw = get(rule);
|
||||
if (!raw) return std::nullopt;
|
||||
if (raw->type() == typeid(string)) return any_cast<string>(*raw);
|
||||
// if (raw->type() == typeid(const char*)) return any_cast<const char*>(*raw);
|
||||
throw std::runtime_error("Rule value is of an incorrect type.");
|
||||
}
|
||||
|
||||
/**
|
||||
* get<O, T> specialization for numeric values.
|
||||
*/
|
||||
|
||||
template<typename N, Type T = Type::LITERAL, std::enable_if_t<
|
||||
(std::is_integral_v<N> || std::is_floating_point_v<N>) && T == Type::LITERAL, bool> = true>
|
||||
(std::is_integral_v<N> ||
|
||||
std::is_floating_point_v<N>) &&
|
||||
T == Type::LITERAL, bool> = true>
|
||||
|
||||
const N get(Rule rule, std::optional<N> def = std::nullopt) const {
|
||||
const let it = rules.find(rule);
|
||||
if (it == rules.end() && def) return *def;
|
||||
if (it == rules.end()) throw std::runtime_error("Field is missing.");
|
||||
if (it->second.type() == typeid(N)) return std::any_cast<N>(it->second);
|
||||
if (it->second.type() == typeid(i8)) return static_cast<N>(std::any_cast<i8>(it->second));
|
||||
if (it->second.type() == typeid(i16)) return static_cast<N>(std::any_cast<i16>(it->second));
|
||||
if (it->second.type() == typeid(i32)) return static_cast<N>(std::any_cast<i32>(it->second));
|
||||
if (it->second.type() == typeid(i64)) return static_cast<N>(std::any_cast<i64>(it->second));
|
||||
if (it->second.type() == typeid(f32)) return static_cast<N>(std::any_cast<f32>(it->second));
|
||||
if (it->second.type() == typeid(f64)) return static_cast<N>(std::any_cast<f64>(it->second));
|
||||
if (it->second.type() == typeid(u8)) return static_cast<N>(std::any_cast<u8>(it->second));
|
||||
if (it->second.type() == typeid(u16)) return static_cast<N>(std::any_cast<u16>(it->second));
|
||||
if (it->second.type() == typeid(u32)) return static_cast<N>(std::any_cast<u32>(it->second));
|
||||
if (it->second.type() == typeid(u64)) return static_cast<N>(std::any_cast<u64>(it->second));
|
||||
if (it->second.type() == typeid(usize)) return static_cast<N>(std::any_cast<usize>(it->second));
|
||||
throw std::runtime_error("Field is incorrect type.");
|
||||
const optional<N> get(Rule rule) const {
|
||||
const optional<any> raw = get(rule);
|
||||
if (!raw) return std::nullopt;
|
||||
if (raw->type() == typeid(N)) return any_cast<N>(*raw);
|
||||
// if (raw->type() == typeid(i8)) return static_cast<N>(any_cast<i8>(*raw));
|
||||
// if (raw->type() == typeid(i16)) return static_cast<N>(any_cast<i16>(*raw));
|
||||
// if (raw->type() == typeid(i32)) return static_cast<N>(any_cast<i32>(*raw));
|
||||
// if (raw->type() == typeid(i64)) return static_cast<N>(any_cast<i64>(*raw));
|
||||
// if (raw->type() == typeid(f32)) return static_cast<N>(any_cast<f32>(*raw));
|
||||
// if (raw->type() == typeid(f64)) return static_cast<N>(any_cast<f64>(*raw));
|
||||
// if (raw->type() == typeid(u8)) return static_cast<N>(any_cast<u8>(*raw));
|
||||
// if (raw->type() == typeid(u16)) return static_cast<N>(any_cast<u16>(*raw));
|
||||
// if (raw->type() == typeid(u32)) return static_cast<N>(any_cast<u32>(*raw));
|
||||
// if (raw->type() == typeid(u64)) return static_cast<N>(any_cast<u64>(*raw));
|
||||
// if (raw->type() == typeid(usize)) return static_cast<N>(any_cast<usize>(*raw));
|
||||
throw std::runtime_error("Rule value is of an incorrect type.");
|
||||
}
|
||||
|
||||
template<typename V, Type C, std::enable_if_t<C == Type::COLOR, bool> = true>
|
||||
/**
|
||||
* Returns an optional of the specified Rule's value,
|
||||
* which is interpreted as a vec4 color from several different formats.
|
||||
* Throws if the rule is defined to a different type.
|
||||
*/
|
||||
|
||||
const V get(Rule rule, std::optional<V> def = std::nullopt) const {
|
||||
const std::any& v = get(rule);
|
||||
if (v.type() == typeid(void) && def) return *def;
|
||||
if (v.type() == typeid(void)) throw std::runtime_error("Field is missing with no default.");
|
||||
if (v.type() == typeid(vec4)) return std::any_cast<vec4>(v);
|
||||
if (v.type() == typeid(string)) return Util::hexToColorVec(std::any_cast<string>(v));
|
||||
if (v.type() == typeid(const char*)) return Util::hexToColorVec(string(std::any_cast<const char*>(v)));
|
||||
throw std::runtime_error("Field is incorrect type.");
|
||||
template<typename V, Type C, std::enable_if_t<
|
||||
std::is_same_v<V, vec4> &&
|
||||
C == Type::COLOR, bool> = true>
|
||||
|
||||
const optional<V> get(Rule rule, optional<V> def = std::nullopt) const {
|
||||
const optional<any> raw = get(rule);
|
||||
if (!raw) return std::nullopt;
|
||||
if (raw->type() == typeid(void) && def) return *def;
|
||||
if (raw->type() == typeid(void)) throw std::runtime_error("Field is missing with no default.");
|
||||
if (raw->type() == typeid(vec4)) return any_cast<vec4>(*raw);
|
||||
try {
|
||||
if (raw->type() == typeid(string)) return Util::hexToColorVec(any_cast<string>(*raw));
|
||||
// if (raw->type() == typeid(const char*)) return Util::hexToColorVec(string(any_cast<const char*>(*raw)));
|
||||
}
|
||||
catch (std::exception) {
|
||||
return std::nullopt;
|
||||
}
|
||||
throw std::runtime_error("Rule value is of an incorrect type.");
|
||||
}
|
||||
|
||||
template<typename I, Type L, std::enable_if_t<L == Type::LENGTH, bool> = true>
|
||||
/**
|
||||
* Returns an optional of the specified Rule's value,
|
||||
* which is interpreted as a length.
|
||||
* Throws if the rule is defined to a different type.
|
||||
*/
|
||||
|
||||
I get(Rule rule, std::optional<I> def = std::nullopt) const {
|
||||
return get<i32>(rule, def);
|
||||
template<typename N, Type L, std::enable_if_t<
|
||||
(std::is_integral_v<N> || std::is_floating_point_v<N>) &&
|
||||
L == Type::LENGTH, bool> = true>
|
||||
|
||||
optional<N> get(Rule rule) const {
|
||||
return get<N>(rule);
|
||||
}
|
||||
|
||||
std::unordered_map<Rule, std::any> rules {};
|
||||
/**
|
||||
* Returns the specified Rule's value as a V,
|
||||
* or the default value provided as the second parameter.
|
||||
* Throws if the rule is defined to a different type.
|
||||
*/
|
||||
|
||||
template<typename V, Type T = Type::LITERAL>
|
||||
|
||||
const V get(Rule rule, V def) const {
|
||||
const optional<V> raw = get<V, T>(rule);
|
||||
if (!raw) return def;
|
||||
return *raw;
|
||||
}
|
||||
|
||||
std::unordered_map<Rule, any> rules {};
|
||||
|
||||
const static any ANY_MISSING;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<string, Style> StyleSheet;
|
||||
}
|
|
@ -53,22 +53,23 @@ MainMenuScene::MainMenuScene(Client& client) : Scene(client),
|
|||
// root.body->setStyle(Gui::Style::Rule::H_ALIGN, "center");
|
||||
// root.body->setStyle(Gui::Style::Rule::V_ALIGN, "center");
|
||||
|
||||
root.body->setStyle(Gui::Style::Rule::BACKGROUND_COLOR, "#123");
|
||||
// root.body->setStyle(Gui::Style::Rule::BACKGROUND, string("#123"));
|
||||
|
||||
root.addStylesheet({
|
||||
{ "sandbox", {}},
|
||||
{ "sandbox", {{
|
||||
}}},
|
||||
{ "navigation", {{
|
||||
{ Gui::Style::Rule::HEIGHT, 18 }
|
||||
}}},
|
||||
{ "navigationWrap", {{
|
||||
{ Gui::Style::Rule::DIRECTION, "row" },
|
||||
{ Gui::Style::Rule::DIRECTION, string("row") },
|
||||
{ Gui::Style::Rule::TOP, 0 },
|
||||
{ Gui::Style::Rule::LEFT, 0 }
|
||||
}}},
|
||||
{ "navigationBackground", {{
|
||||
{ Gui::Style::Rule::WIDTH, 64 },
|
||||
{ Gui::Style::Rule::HEIGHT, 18 },
|
||||
{ Gui::Style::Rule::BACKGROUND_IMAGE, "menu_bar_bg" }
|
||||
{ Gui::Style::Rule::BACKGROUND, string("menu_bar_bg") }
|
||||
}}},
|
||||
{ "navigationButton", {{
|
||||
{ Gui::Style::Rule::WIDTH, 16 },
|
||||
|
@ -76,15 +77,8 @@ MainMenuScene::MainMenuScene(Client& client) : Scene(client),
|
|||
}}}
|
||||
});
|
||||
|
||||
let sandbox = root.body->append<Gui::BoxElement>({
|
||||
.classes = { "sandbox" }
|
||||
// .styles = {{
|
||||
// { Gui::Style::Rule::BACKGROUND_COLOR, "#700" }
|
||||
// }}
|
||||
});
|
||||
|
||||
let sandbox = root.body->append<Gui::BoxElement>({ .classes = { "sandbox" } });
|
||||
let navigation = root.body->append<Gui::BoxElement>({ .classes = { "navigation" } });
|
||||
|
||||
let navigationBG = navigation->append<Gui::BoxElement>({ .classes = { "navigationWrap" } });
|
||||
|
||||
for (usize i = 0; i < 2000 / Gui::PX_SCALE / 64; i++)
|
||||
|
@ -100,32 +94,24 @@ MainMenuScene::MainMenuScene(Client& client) : Scene(client),
|
|||
|
||||
let serversButton = navigationList->append<Gui::BoxElement>({
|
||||
.classes = { "navigationButton" },
|
||||
.styles = {{
|
||||
{ Gui::Style::Rule::BACKGROUND_IMAGE, "crop(0, 0, 16, 16, menu_flag_multiplayer)" }
|
||||
}}
|
||||
.styles = {{ { Gui::Style::Rule::BACKGROUND, string("crop(0, 0, 16, 16, menu_flag_multiplayer)") } }}
|
||||
});
|
||||
|
||||
let contentButton = navigationList->append<Gui::BoxElement>({
|
||||
.classes = { "navigationButton" },
|
||||
.styles = {{
|
||||
{ Gui::Style::Rule::BACKGROUND_IMAGE, "crop(0, 0, 16, 16, menu_flag_content)" }
|
||||
}}
|
||||
.styles = {{ { Gui::Style::Rule::BACKGROUND, string("crop(0, 0, 16, 16, menu_flag_content)") } }}
|
||||
});
|
||||
|
||||
navigationList->append<Gui::BoxElement>({});
|
||||
|
||||
let settingsButton = navigationList->append<Gui::BoxElement>({
|
||||
.classes = { "navigationButton" },
|
||||
.styles = {{
|
||||
{ Gui::Style::Rule::BACKGROUND_IMAGE, "crop(0, 0, 16, 16, menu_flag_settings)" }
|
||||
}}
|
||||
.styles = {{ { Gui::Style::Rule::BACKGROUND, string("crop(0, 0, 16, 16, menu_flag_settings)") } }}
|
||||
});
|
||||
|
||||
let closeButton = navigationList->append<Gui::BoxElement>({
|
||||
.classes = { "navigationButton" },
|
||||
.styles = {{
|
||||
{ Gui::Style::Rule::BACKGROUND_IMAGE, "crop(0, 0, 16, 16, menu_flag_quit)" }
|
||||
}}
|
||||
.styles = {{ { Gui::Style::Rule::BACKGROUND, string("crop(0, 0, 16, 16, menu_flag_quit)") } }}
|
||||
});
|
||||
|
||||
// closeButton->setCallback(Element::CallbackType::PRIMARY,
|
||||
|
|
|
@ -95,7 +95,7 @@ namespace Util {
|
|||
vec4 color {};
|
||||
|
||||
if (hex[0] == '#') hex.erase(0, 1);
|
||||
else std::cout << Log::err << "Color string does not begin with hash!" << Log::endl;
|
||||
else throw std::runtime_error("Color string '" + hex + "' is ill-formed. (missing #)");
|
||||
|
||||
string r, g, b, a;
|
||||
|
||||
|
@ -116,8 +116,7 @@ namespace Util {
|
|||
a = (hex.length() == 8) ? hex.substr(6, 2) : "ff";
|
||||
}
|
||||
else {
|
||||
std::cout << Log::err << "Color string \"" + hex + "\" is of incorrect length!" << Log::endl;
|
||||
return color;
|
||||
throw std::runtime_error("Color string '" + hex + "' is ill-formed. (invalid length)");
|
||||
}
|
||||
|
||||
color.r = intFromHexSegment(r) / 255.f;
|
||||
|
|
|
@ -102,7 +102,7 @@ void DrawableEntity::interpScale(glm::vec3 scale) {
|
|||
}
|
||||
|
||||
void DrawableEntity::draw(Renderer& renderer) {
|
||||
if (visible) {
|
||||
if (visible && model) {
|
||||
renderer.setModelMatrix(getModelMatrix());
|
||||
|
||||
model->getTransformsByFrame(animation.getFrame(), animation.getBounds(), transforms);
|
||||
|
|
|
@ -75,7 +75,7 @@ protected:
|
|||
glm::vec3 visualRotation{};
|
||||
glm::vec3 visualScale{ 1, 1, 1 };
|
||||
|
||||
std::shared_ptr<Model> model;
|
||||
std::shared_ptr<Model> model = nullptr;
|
||||
std::vector<glm::mat4> transforms{};
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue