Container and self widths, implementation is recursive and slow, fix getComputedSize() by storing elem size in a const.

master
Auri 2021-08-21 02:17:09 -07:00
parent 5bf82c4963
commit 6caef42565
13 changed files with 229 additions and 122 deletions

View File

@ -17,46 +17,56 @@ void Gui::Element::setStyle(StyleRule style, const std::any& value) {
props.styles.rules[style] = value;
}
ivec2 Gui::Element::getComputedSize() {
let size = getStyle<ivec2, ValueType::LENGTH>(StyleRule::SIZE, ivec2(-1));
if (size.x == -1) size.x = std::max(layoutSize.x, 0);
if (size.y == -1) size.y = std::max(layoutSize.y, 0);
return size;
}
void Gui::Element::clear() {
children.clear();
}
ivec2 Gui::Element::getComputedOuterSize() {
void Gui::Element::onClick(const std::function<void(i32, bool)>& cb) {
clickCb = cb;
}
Gui::ExpressionInfo Gui::Element::getExpr() const {
return {
parent ? parent->getComputedSize() : ivec2 {},
getComputedSize()
};
}
ivec2 Gui::Element::getComputedSize() const {
let size = getStyleWithExpr<vec2, ValueType::LENGTH>(StyleRule::SIZE, vec2(nanf("")),
{ parent ? parent->getComputedSize() : ivec2 {}, {} });
if (std::isnan(size.x)) size.x = std::max(layoutSize.x, 0);
if (std::isnan(size.y)) size.y = std::max(layoutSize.y, 0);
return size;
}
ivec2 Gui::Element::getComputedOuterSize() const {
let size = getComputedSize();
let margin = getStyle<ivec4, ValueType::LENGTH>(StyleRule::MARGIN, {});
return ivec2 { size.x + margin.x + margin.z, size.y + margin.y + margin.w };
}
ivec2 Gui::Element::getComputedContentSize() {
ivec2 Gui::Element::getComputedContentSize() const {
let size = getComputedSize();
let padding = getStyle<ivec4, ValueType::LENGTH>(StyleRule::PADDING, {});
return glm::max(ivec2 { size.x - padding.x - padding.z, size.y - padding.y - padding.w }, 0);
}
ivec2 Gui::Element::getExplicitSize() {
ivec2 Gui::Element::getExplicitSize() const {
return getStyle<ivec2, ValueType::LENGTH>(StyleRule::SIZE, ivec2(-1));
}
ivec2 Gui::Element::getComputedPos() {
ivec2 Gui::Element::getComputedPos() const {
return getStyle<ivec2, ValueType::LENGTH>(StyleRule::POS, layoutPosition);
}
ivec2 Gui::Element::getComputedScreenPos() {
ivec2 Gui::Element::getComputedScreenPos() const {
return getComputedPos() + parentOffset;
}
bool Gui::Element::handleMouseHover(ivec2 mousePos, bool& pointer) {
bool childIntersects = false;
for (let& child : children)
if (child->handleMouseHover(mousePos, pointer))
childIntersects = true;
for (let& child : children) if (child->handleMouseHover(mousePos, pointer)) childIntersects = true;
if (childIntersects) {
if (hovered) {
@ -81,9 +91,17 @@ bool Gui::Element::handleMouseHover(ivec2 mousePos, bool& pointer) {
return intersects;
}
bool Gui::Element::handleMouseClick(u32 button, bool down) {
for (let& child: children) if (child->handleMouseClick(button, down)) return true;
return false;
bool Gui::Element::handleMouseClick(ivec2 mousePos, u32 button, bool down) {
for (let& child : children) if (child->handleMouseClick(mousePos, button, down)) return true;
ivec2 size = getComputedSize();
ivec2 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) {
@ -160,8 +178,6 @@ void Gui::Element::layoutChildren() {
/**
* The amount of size each implicitly sized element should occupy.
*/
// std::cout << selfSize << ": " << (selfSize[primary] - explicitSize) << std::endl;
i32 implicitElemSize = floor((selfSize[primary] - explicitSize) / (std::max)(implicitCount, 1));
@ -193,10 +209,10 @@ void Gui::Element::layoutChildren() {
+ gap + childMargin[primary] + childMargin[primary + 2];
child->parentOffset = selfOffset;
child->updateElement();
}
break;
}
}
}
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <list>
#include <functional>
#include "client/gui/Gui.h"
#include "client/gui/Style.h"
@ -20,7 +21,9 @@ namespace Gui {
*/
class Element {
friend class Root;
friend class BoxElement;
friend class TextElement;
public:
struct Props {;
@ -81,23 +84,25 @@ namespace Gui {
void clear();
void onClick(const std::function<void(i32, bool)>& cb);
/** Returns the element's computed size. */
virtual ivec2 getComputedSize();
virtual ivec2 getComputedSize() const;
/** Returns the element's computed size + margins. */
virtual ivec2 getComputedOuterSize();
virtual ivec2 getComputedOuterSize() const;
/** Returns the element's computed content size, which is its size - padding. */
virtual ivec2 getComputedContentSize();
virtual ivec2 getComputedContentSize() const;
/** Returns the element's explicit size. Unspecified dimensions are -1. */
virtual ivec2 getExplicitSize();
virtual ivec2 getExplicitSize() const;
/** Returns the element's computed position relative to its parent. */
virtual ivec2 getComputedPos();
virtual ivec2 getComputedPos() const;
/** Returns the element's computed position relative to the screen. */
virtual ivec2 getComputedScreenPos();
virtual ivec2 getComputedScreenPos() const;
/** Gets a style value from the element's styles or the root's stylesheets. */
const optional<any> getStyle(StyleRule rule) const {
@ -114,8 +119,10 @@ namespace Gui {
return std::nullopt;
}
/** Gets a style value from the element's styles or the root's stylesheets. */
template<typename V, ValueType T = ValueType::LITERAL>
/** Gets a generic value from the element's styles or the root's stylesheets. */
template<typename V, ValueType T = ValueType::LITERAL,
std::enable_if_t<T != ValueType::LENGTH, bool> = true>
const optional<V> getStyle(StyleRule rule) const {
const optional<V> opt = props.styles.get<V, T>(rule);
if (opt) return *opt;
@ -129,14 +136,67 @@ namespace Gui {
}
return std::nullopt;
}
/** Gets a LENGTH value from the element's styles or the root's stylesheets. */
template<typename V, ValueType T = ValueType::LITERAL,
std::enable_if_t<T == ValueType::LENGTH, bool> = true>
const optional<V> getStyle(StyleRule rule) const {
ExpressionInfo info = getExpr();
const optional<V> opt = props.styles.get<V, T>(rule, info);
if (opt) return *opt;
for (const let& ss : stylesheets) {
for (const string& className : props.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, ValueType T = ValueType::LITERAL,
std::enable_if_t<T == ValueType::LENGTH, bool> = true>
const optional<V> getStyleWithExpr(StyleRule rule, const ExpressionInfo& expr) const {
const optional<V> opt = props.styles.get<V, T>(rule, expr);
if (opt) return *opt;
for (const let& ss : stylesheets) {
for (const string& className : props.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, ValueType T = ValueType::LITERAL>
const V getStyle(StyleRule 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, ValueType T = ValueType::LITERAL,
std::enable_if_t<T == ValueType::LENGTH, bool> = true>
const V getStyleWithExpr(StyleRule rule, V def, const ExpressionInfo& info) const {
const optional<V> opt = getStyleWithExpr<V, T>(rule, info);
if (opt) return *opt;
return def;
}
protected:
/** Returns an ExpressionInfo object for evaluating Lengths. */
virtual ExpressionInfo getExpr() const;
/**
* Called by the root when the mouse position changes.
@ -150,9 +210,8 @@ namespace Gui {
* Triggers a click interaction on the hovered element.
*/
bool handleMouseClick(u32 button, bool down);
bool handleMouseClick(ivec2 mousePos, u32 button, bool down);
protected:
Root& root;
Props props;
vec<StyleSheet>& stylesheets;
@ -162,6 +221,7 @@ namespace Gui {
std::list<sptr<Element>> children;
bool hovered = false;
std::function<void(u32, bool)> clickCb = nullptr;
/** The screen offset of the parent. */
ivec2 parentOffset {};

View File

@ -31,14 +31,8 @@ void Gui::Expression::setExpression(string exp) {
while (exp.size()) {
let& c = exp[0];
// Number or Unit or Keyword
if ((c >= '0' && c <= '9') || c == '.' || (c >= 97 && c <= 122) ||
(nextOperatorIsUnary && (c == '+' || c == '-'))) {
temp += c;
nextOperatorIsUnary = false;
}
// Binary Operator
else if (!nextOperatorIsUnary && (c == '+' || c == '-' || c == '*' || c == '/' || c == '^')) {
if (!nextOperatorIsUnary && (c == '+' || c == '-' || c == '*' || c == '/' || c == '^')) {
if (temp.size()) {
queue.emplace(temp);
temp = {};
@ -81,6 +75,11 @@ void Gui::Expression::setExpression(string exp) {
operators.pop();
nextOperatorIsUnary = false;
}
// Number or Unit or Keyword
else {
temp += c;
nextOperatorIsUnary = false;
}
exp.erase(0, 1);
}
@ -105,7 +104,11 @@ void Gui::Expression::setExpression(string exp) {
}
}
f32 Gui::Expression::eval() {
f32 Gui::Expression::eval(const ExpressionInfo& info) {
if (!expression.size()) {
return nanf("");
}
std::stack<Token> eval {};
for (usize i = 0; i < expression.size(); i++) {
@ -124,28 +127,28 @@ f32 Gui::Expression::eval() {
switch (t.unit) {
default:
throw std::logic_error("Tried to operate with a non-operator token.");
throw std::logic_error("Tried to operate with a non-operator token! This is an engine error!");
case UnitOrOperator::ADD:
eval.emplace(a.evalValue() + b.evalValue(), UnitOrOperator::REAL_PIXEL);
eval.emplace(a.eval(info) + b.eval(info), UnitOrOperator::RAW);
break;
case UnitOrOperator::SUBTRACT:
eval.emplace(a.evalValue() - b.evalValue(), UnitOrOperator::REAL_PIXEL);
eval.emplace(a.eval(info) - b.eval(info), UnitOrOperator::RAW);
break;
case UnitOrOperator::MULTIPLY:
eval.emplace(a.evalValue() * b.evalValue(), UnitOrOperator::REAL_PIXEL);
eval.emplace(a.eval(info) * b.eval(info), UnitOrOperator::RAW);
break;
case UnitOrOperator::DIVIDE:
eval.emplace(a.evalValue() / b.evalValue(), UnitOrOperator::REAL_PIXEL);
eval.emplace(a.eval(info) / b.eval(info), UnitOrOperator::RAW);
break;
case UnitOrOperator::EXPONENT:
eval.emplace(pow(a.evalValue(), b.evalValue()), UnitOrOperator::REAL_PIXEL);
eval.emplace(pow(a.eval(info), b.eval(info)), UnitOrOperator::RAW);
break;
}
}
}
if (!eval.size()) throw std::runtime_error("Eval stack is empty! This is an engine error!");
return eval.top().evalValue();
return eval.top().eval(info);
}
const std::unordered_map<char, u8> Gui::Expression::PRECEDENCE {
@ -178,9 +181,13 @@ Gui::Expression::Token::Token(const string& str) {
switch (Util::hash(unitStr.data())) {
default: throw std::logic_error("Unknown unit '" + unitStr + "'.");
case Util::hash("dp"): unit = UnitOrOperator::DISPLAY_PIXEL; return;
case Util::hash(""):
case Util::hash("px"): unit = UnitOrOperator::REAL_PIXEL; return;
case Util::hash("px"): unit = UnitOrOperator::RAW; return;
case Util::hash("dp"): unit = UnitOrOperator::DISPLAY_PIXEL; return;
case Util::hash("cw"): unit = UnitOrOperator::CONTAINER_WIDTH; return;
case Util::hash("ch"): unit = UnitOrOperator::CONTAINER_HEIGHT; return;
case Util::hash("sw"): unit = UnitOrOperator::SELF_WIDTH; return;
case Util::hash("sh"): unit = UnitOrOperator::SELF_HEIGHT; return;
case Util::hash("deg"): unit = UnitOrOperator::DEGREE; return;
}
}
@ -189,12 +196,19 @@ bool Gui::Expression::Token::isOperator() {
return static_cast<u8>(unit) >= 128;
}
f32 Gui::Expression::Token::evalValue() {
f32 Gui::Expression::Token::eval(const ExpressionInfo& info) {
switch (unit) {
default: throw std::logic_error("Tried to evalValue() on an Operator token.");
default: throw std::logic_error("Tried to eval() on an Operator token! This is an engine error!");
case UnitOrOperator::DISPLAY_PIXEL: return val * Gui::PX_SCALE;
case UnitOrOperator::REAL_PIXEL: return val;
case UnitOrOperator::RAW: return val;
case UnitOrOperator::CONTAINER_WIDTH: {
// std::cout << info.containerSize << ":" << val << std::endl;
return (val / 100.f) * info.containerSize.x;
}
case UnitOrOperator::CONTAINER_HEIGHT: return (val / 100.f) * info.containerSize.y;
case UnitOrOperator::SELF_WIDTH: return (val / 100.f) * info.selfSize.x;
case UnitOrOperator::SELF_HEIGHT: return (val / 100.f) * info.selfSize.y;
case UnitOrOperator::DEGREE: return val * M_PI / 180.f;
}
}

View File

@ -6,19 +6,31 @@
#include "util/Types.h"
namespace Gui {
enum class UnitOrOperator: u8 {
DISPLAY_PIXEL,
REAL_PIXEL,
DEGREE,
struct ExpressionInfo {
ExpressionInfo() = default;
ExpressionInfo(ivec2 containerSize, ivec2 selfSize): containerSize(containerSize), selfSize(selfSize) {}
ADD = 128,
SUBTRACT,
MULTIPLY,
DIVIDE,
EXPONENT
ivec2 containerSize;
ivec2 selfSize;
};
class Expression {
enum class UnitOrOperator: u8 {
RAW,
DISPLAY_PIXEL,
CONTAINER_WIDTH,
CONTAINER_HEIGHT,
SELF_WIDTH,
SELF_HEIGHT,
DEGREE,
ADD = 128,
SUBTRACT,
MULTIPLY,
DIVIDE,
EXPONENT
};
struct Token {
Token() = default;
explicit Token(const string& str);
@ -26,7 +38,7 @@ namespace Gui {
bool isOperator();
f32 evalValue();
f32 eval(const ExpressionInfo& info);
f32 val = 0;
UnitOrOperator unit;
@ -38,12 +50,12 @@ namespace Gui {
void setExpression(string exp);
f32 eval();
f32 eval(const ExpressionInfo& info);
private:
usize hash = 0;
std::vector<Token> expression;
vec<Token> expression;
const static std::unordered_map<char, u8> PRECEDENCE;
};

View File

@ -26,7 +26,10 @@ Gui::Root::Root(Window& window, TextureAtlas& atlas) :
t.printElapsedMs();
});
// window.input.bindMouseCallback()
window.input.bindMouseCallback([&](u32 button, i32 state) {
let pos = window.input.getMousePos();
body->handleMouseClick(pos, button, state == GLFW_PRESS);
});
}
Gui::Root::~Root() {

View File

@ -5,7 +5,7 @@ const std::unordered_map<string, Gui::StyleRule> Gui::Style::RULE_STRINGS_TO_ENU
{ "size", StyleRule::SIZE },
{ "margin", StyleRule::MARGIN },
{ "padding", StyleRule::PADDING },
{ "GAP", StyleRule::GAP },
{ "gap", StyleRule::GAP },
{ "layout", StyleRule::LAYOUT },
{ "direction", StyleRule::DIRECTION },
{ "h_align", StyleRule::H_ALIGN },

View File

@ -143,10 +143,10 @@ namespace Gui {
(std::is_integral_v<N> || std::is_floating_point_v<N>) &&
L == ValueType::LENGTH, bool> = true>
optional<N> get(StyleRule rule) const {
optional<N> get(StyleRule rule, const ExpressionInfo& info) const {
let raw = get<Gui::Expression>(rule);
if (!raw) return std::nullopt;
return raw->eval();
return raw->eval(info);
}
/**
@ -159,11 +159,11 @@ namespace Gui {
std::is_same_v<VN, glm::vec<VN::length(), typename VN::value_type>> &&
L == ValueType::LENGTH, bool> = true>
optional<VN> get(StyleRule rule) const {
optional<VN> get(StyleRule rule, const ExpressionInfo& info) const {
let raw = get<array<Gui::Expression, VN::length()>>(rule);
if (!raw) return std::nullopt;
VN vec;
for (usize i = 0; i < VN::length(); i++) vec[i] = (*raw)[i].eval();
for (usize i = 0; i < VN::length(); i++) vec[i] = (*raw)[i].eval(info);
return vec;
}

View File

@ -23,7 +23,6 @@ MenuSandbox::MenuSandbox(Client& client, Gui::Root& root, sptr<Gui::Element> san
sandboxRoot(sandboxRoot) {}
void MenuSandbox::reset() {
// container->remove("error");
sandboxRoot->clear();
core = {};
mod = {};
@ -58,22 +57,12 @@ void MenuSandbox::loadApi() {
void MenuSandbox::load(const SubgameDef& subgame) {
reset();
subgameName = subgame.config.name;
// try {
loadAndRunMod(subgame.subgamePath + "/../../assets/base");
loadAndRunMod(subgame.subgamePath + "/menu");
// }
// catch (const std::runtime_error& e) {
// showError(e.what(), subgame.config.name);
// }
}
void MenuSandbox::windowResized() {
// builder.build(win);
}
void MenuSandbox::update(double delta) {
// builder.update();
core["__builtin"]["update_delayed_functions"]();
}

View File

@ -25,8 +25,6 @@ public:
void update(double delta) override;
void windowResized();
using LuaParser::update;
private:

View File

@ -87,19 +87,20 @@ MainMenuScene::MainMenuScene(Client& client) : Scene(client),
for (usize i = 0; i < subgames.size(); i++) {
let& subgame = subgames[i];
navigationList->append<Gui::BoxElement>({
let elem = navigationList->append<Gui::BoxElement>({
.classes = { "navigationButton" },
.styles = {{
{ Gui::StyleRule::BACKGROUND, string("crop(0, 0, 16, 16, " + subgame.iconRef->name + ")") },
{ Gui::StyleRule::BACKGROUND_HOVER, string("crop(16, 0, 16, 16, " + subgame.iconRef->name + ")") }
}}
});
//
// button->setCallback(Element::CallbackType::PRIMARY, [&](bool down, ivec2) {
// if (!down) return;
// selectedSubgame = &subgame;
// sandbox.load(*selectedSubgame);
// });
elem->onClick([&](u32 button, bool down) {
if (button != GLFW_MOUSE_BUTTON_1) return;
selectedSubgame = &subgame;
sandbox.load(*selectedSubgame);
});
}
if (subgames.size() > 0) {

View File

@ -7,6 +7,7 @@
#include "client/gui/TextElement.h"
static const Gui::Expression parseObjectToExpr(sol::object value) {
if (!value.valid()) return Gui::Expression("");
if (value.is<f32>()) return Gui::Expression(std::to_string(value.as<f32>()) + "dp");
if (value.is<string>()) return Gui::Expression(value.as<string>());
throw std::invalid_argument("Object cannot be converted to an expression.");
@ -21,7 +22,8 @@ static const array<Gui::Expression, D> parseLengthTableVal(sol::object value) {
vec<Gui::Expression> exprs {};
exprs.reserve(t.size());
for (let& v : t) exprs.emplace_back(parseObjectToExpr(v.second));
for (usize i = 1; i <= t.size(); i++)
exprs.emplace_back(parseObjectToExpr(t.get<sol::object>(i)));
for (usize i = 0; i < arr.size() / exprs.size(); i++)
for (usize j = 0; j < exprs.size(); j++)

View File

@ -6,6 +6,7 @@ zepha.set_gui(zepha.gui(function()
Gui.Text {
size = { 64, 4 },
pos = { "50cw", 50 },
content = "Parentheses"
}
}

View File

@ -1,52 +1,63 @@
local menu = zepha.build_gui(function()
local menu = zepha.gui(function()
return Gui.Box {
background = 'zeus_background_christmas_night',
Gui.Box {
key = 'particle_wrap',
size = { '100%', '100%' }
-- id = 'particle_wrap',
size = { '100cw', '100ch' }
},
Gui.Box {
pos = { '20% - 50s%', 0 },
size = { 102, '100%' },
gap = 4,
padding = 8,
size = { 102, '100ch' },
pos = { '20cw - 50sw', 0 },
background = '#0135',
Gui.Box {
pos = { 8, 8 },
size = { 86, 30 },
size = { nil, 30 },
margin = { 0, 0, 0, 8 },
background = 'zeus_logo'
},
Gui.Button {
id = 'button_play',
Gui.Box {
-- id = 'button_play',
-- callbacks = {
-- primary = function() zepha.start_game_local() end
-- },
pos = { 6, 50 },
size = { 90, 20 },
padding = 5,
size = { nil, 20 },
content = 'Local Play',
cursor = "pointer",
background = 'crop(0, 0, 90, 20, zeus_button)',
background_hover = 'crop(0, 20, 90, 20, zeus_button)'
background_hover = 'crop(0, 20, 90, 20, zeus_button)',
Gui.Text {
content = 'Local Play'
}
},
Gui.Button {
id = 'button_servers',
Gui.Box {
-- id = 'button_servers',
-- callbacks = {
-- primary = function() zepha.start_game() end
-- },
pos = { 6, 74 },
size = { 90, 20 },
content = 'Browse Servers',
padding = 5,
size = { nil, 20 },
cursor = "pointer",
background = 'crop(0, 0, 90, 20, zeus_button)',
background_hover = 'crop(0, 20, 90, 20, zeus_button)'
background_hover = 'crop(0, 20, 90, 20, zeus_button)',
Gui.Text {
content = 'Browse Servers'
}
}
}
}
@ -62,17 +73,17 @@ end)
-- ) end)
-- end, 1)
local particle_wrap = menu:get('particle_wrap')
menu(function()
for _ = 1, 20 do
local scale = 6 + math.random() * 4
particle_wrap:append(Gui.Rect {
pos = { math.floor(math.random() * 600), math.floor(math.random() * 320) },
background = 'particle_dark',
size = { scale, scale }
})
end
end)
-- local particle_wrap = menu:get('particle_wrap')
-- menu(function()
-- for _ = 1, 20 do
-- local scale = 6 + math.random() * 4
-- particle_wrap:append(Gui.Rect {
-- pos = { math.floor(math.random() * 600), math.floor(math.random() * 320) },
-- background = 'particle_dark',
-- size = { scale, scale }
-- })
-- end
-- end)
-- local tick = 0
-- zepha.after(function()