New Style API that throws if type is invalid.

master
Auri 2021-08-19 20:18:55 -07:00
parent e41459090f
commit 455ef5da6a
11 changed files with 322 additions and 256 deletions

View File

@ -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();
}

View File

@ -5,9 +5,9 @@ namespace Gui {
public:
using Element::Element;
protected:
virtual void updateElement() override;
virtual void layoutChildren() override;
protected:
optional<any> curBg;
};
}

View File

@ -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;
}
}
}

View File

@ -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();
};
}

View File

@ -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);
}
}

View File

@ -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;
};
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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;

View File

@ -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);

View File

@ -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{};
};