Expression Parsing
parent
d8dac4d74e
commit
1ec7598ac0
|
@ -331,6 +331,6 @@ add_library(Zepha_Core
|
|||
client/gui/compound/GuiCellGraph.cpp
|
||||
client/gui/compound/GuiCellGraph.h
|
||||
client/gui/basic/GuiCells.cpp
|
||||
client/gui/basic/GuiCells.h client/gui/Gui.h client/gui/Root.cpp client/gui/Root.h client/gui/BoxElement.cpp client/gui/BoxElement.h client/gui/Gui.cpp client/gui/Style.h)
|
||||
client/gui/basic/GuiCells.h client/gui/Gui.h client/gui/Root.cpp client/gui/Root.h client/gui/BoxElement.cpp client/gui/BoxElement.h client/gui/Gui.cpp client/gui/Style.h client/gui/TextElement.cpp client/gui/TextElement.h client/gui/Expression.cpp client/gui/Expression.h)
|
||||
|
||||
target_include_directories(Zepha_Core PUBLIC .)
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "client/gui/Root.h"
|
||||
#include "client/graph/Model.h"
|
||||
#include "game/atlas/asset/AtlasRef.h"
|
||||
#include "client/graph/mesh/EntityMesh.h"
|
||||
|
||||
void Gui::BoxElement::updateElement() {
|
||||
|
@ -20,7 +19,6 @@ void Gui::BoxElement::updateElement() {
|
|||
}
|
||||
curBg = rawBg;
|
||||
|
||||
|
||||
if (isDirty) {
|
||||
const let bgColor = getStyle<vec4, ValueType::COLOR>(bgRule);
|
||||
const string bgImage = getStyle<string>(bgRule, "");
|
||||
|
@ -50,8 +48,10 @@ void Gui::BoxElement::updateElement() {
|
|||
entity.setModel(model);
|
||||
}
|
||||
|
||||
entity.setScale(vec3(getComputedSize() * static_cast<i32>(PX_SCALE), 0));
|
||||
entity.setPos(vec3(getComputedScreenPos() * static_cast<i32>(PX_SCALE), 0));
|
||||
let margin = getStyle<ivec4, ValueType::LENGTH>(StyleRule::MARGIN, {});
|
||||
|
||||
entity.setScale(vec3(getComputedSize(), 0));
|
||||
entity.setPos(vec3(getComputedScreenPos() + ivec2 { margin.x, margin.y }, 0));
|
||||
|
||||
Element::updateElement();
|
||||
}
|
|
@ -1,8 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "Element.h"
|
||||
|
||||
#include "game/atlas/asset/AtlasRef.h"
|
||||
|
||||
namespace Gui {
|
||||
|
||||
/**
|
||||
* A simple box element that may have background and/or children.
|
||||
*/
|
||||
|
||||
class BoxElement: public Element {
|
||||
public:
|
||||
using Element::Element;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "Element.h"
|
||||
|
||||
#include "util/Util.h"
|
||||
#include "client/gui/Root.h"
|
||||
#include "client/graph/Renderer.h"
|
||||
|
||||
Gui::Element::~Element() {
|
||||
|
@ -17,51 +18,55 @@ void Gui::Element::setStyle(StyleRule style, const std::any& value) {
|
|||
}
|
||||
|
||||
ivec2 Gui::Element::getComputedSize() {
|
||||
return {
|
||||
getStyle<i32, ValueType::LENGTH>(StyleRule::WIDTH, std::max(layoutSize.x, 0)),
|
||||
getStyle<i32, ValueType::LENGTH>(StyleRule::HEIGHT, std::max(layoutSize.y, 0))
|
||||
};
|
||||
let size = getStyle<ivec2, ValueType::LENGTH>(StyleRule::SIZE, glm::max(layoutSize, 0));
|
||||
return size;
|
||||
}
|
||||
|
||||
ivec2 Gui::Element::getComputedOuterSize() {
|
||||
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() {
|
||||
let size = getComputedSize();
|
||||
let padding = getStyle<ivec4>(StyleRule::PADDING, {});
|
||||
return glm::max(ivec2 { size.x - padding.x - padding.z, size.y - padding.y - padding.w }, ivec2 {});
|
||||
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() {
|
||||
return {
|
||||
getStyle<i32, ValueType::LENGTH>(StyleRule::WIDTH, -1),
|
||||
getStyle<i32, ValueType::LENGTH>(StyleRule::HEIGHT, -1)
|
||||
};
|
||||
return getStyle<ivec2, ValueType::LENGTH>(StyleRule::SIZE, ivec2(-1));
|
||||
}
|
||||
|
||||
ivec2 Gui::Element::getComputedPos() {
|
||||
return {
|
||||
getStyle<i32, ValueType::LENGTH>(StyleRule::LEFT, layoutPosition.x),
|
||||
getStyle<i32, ValueType::LENGTH>(StyleRule::TOP, layoutPosition.y)
|
||||
};
|
||||
return getStyle<ivec2, ValueType::LENGTH>(StyleRule::POS, layoutPosition);
|
||||
}
|
||||
|
||||
ivec2 Gui::Element::getComputedScreenPos() {
|
||||
return getComputedPos() + parentOffset;
|
||||
}
|
||||
|
||||
bool Gui::Element::handleMouseHover(ivec2 mousePos) {
|
||||
bool Gui::Element::handleMouseHover(ivec2 mousePos, bool& pointer) {
|
||||
bool childIntersects = false;
|
||||
for (let& child : children)
|
||||
if (child->handleMouseHover(mousePos)) childIntersects = true;
|
||||
if (child->handleMouseHover(mousePos, pointer))
|
||||
childIntersects = true;
|
||||
|
||||
if (childIntersects) {
|
||||
hovered = false;
|
||||
if (hovered) {
|
||||
hovered = false;
|
||||
updateElement();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ivec2 size = getComputedSize() * static_cast<i32>(PX_SCALE);
|
||||
ivec2 pos = getComputedScreenPos() * static_cast<i32>(PX_SCALE);
|
||||
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) pointer = getStyle<string>(StyleRule::CURSOR, "") == "pointer";
|
||||
|
||||
if (hovered != intersects) {
|
||||
hovered = intersects;
|
||||
updateElement();
|
||||
|
@ -116,8 +121,8 @@ void Gui::Element::layoutChildren() {
|
|||
* The element gap across the primary axis.
|
||||
*/
|
||||
|
||||
const i32 gap = getStyle<ivec2>(StyleRule::GAP, ivec2(0))[primary];
|
||||
const ivec4& padding = getStyle<ivec4>(StyleRule::PADDING, ivec4 {});
|
||||
const i32 gap = getStyle<ivec2, ValueType::LENGTH>(StyleRule::GAP, ivec2(0))[primary];
|
||||
const ivec4& padding = getStyle<ivec4, ValueType::LENGTH>(StyleRule::PADDING, ivec4 {});
|
||||
|
||||
/*
|
||||
* Calculates the explicit spaced used up by children across the primary axis,
|
||||
|
@ -133,6 +138,8 @@ void Gui::Element::layoutChildren() {
|
|||
let childExplicitSize = child->getExplicitSize();
|
||||
if (childExplicitSize[primary] != -1) explicitSize += childExplicitSize[primary];
|
||||
else implicitCount++;
|
||||
let childMargin = child->getStyle<ivec4, ValueType::LENGTH>(StyleRule::MARGIN, {});
|
||||
explicitSize += childMargin[primary] + childMargin[primary + 2];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,7 +150,7 @@ void Gui::Element::layoutChildren() {
|
|||
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.
|
||||
*/
|
||||
|
@ -159,6 +166,7 @@ void Gui::Element::layoutChildren() {
|
|||
|
||||
for (const let& child : children) {
|
||||
let childExplicitSize = child->getExplicitSize();
|
||||
let childMargin = child->getStyle<ivec4, ValueType::LENGTH>(StyleRule::MARGIN, {});
|
||||
|
||||
child->layoutSize[primary] =
|
||||
(childExplicitSize[primary] == -1 && align[primary] == 2) ? implicitElemSize : 0;
|
||||
|
@ -174,7 +182,8 @@ void Gui::Element::layoutChildren() {
|
|||
child->layoutPosition[primary] = offset[primary];
|
||||
|
||||
offset[primary] += ((childExplicitSize[primary] == -1 && align[primary] == 2)
|
||||
? implicitElemSize : childExplicitSize[primary]) + gap;
|
||||
? implicitElemSize : childExplicitSize[primary])
|
||||
+ gap + childMargin[primary] + childMargin[primary + 2];
|
||||
|
||||
child->parentOffset = selfOffset;
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <any>
|
||||
#include <list>
|
||||
|
||||
#include "client/gui/Gui.h"
|
||||
|
@ -15,6 +14,11 @@ 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 BoxElement;
|
||||
|
||||
|
@ -28,15 +32,20 @@ namespace Gui {
|
|||
Element(Root& root, vec<StyleSheet>& stylesheets): root(root), stylesheets(stylesheets) {}
|
||||
|
||||
~Element();
|
||||
|
||||
|
||||
/** Sets the element's props to the struct specified. */
|
||||
virtual void setProps(const Props& props);
|
||||
|
||||
|
||||
/** Sets a style rule on the element. */
|
||||
virtual void setStyle(StyleRule style, 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);
|
||||
|
||||
/** 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);
|
||||
|
@ -45,6 +54,7 @@ namespace Gui {
|
|||
return elem;
|
||||
};
|
||||
|
||||
/** Prepends an existing element to this element. */
|
||||
sptr<Element> prepend(sptr<Element> elem) {
|
||||
children.push_front(elem);
|
||||
elem->parent = this;
|
||||
|
@ -52,6 +62,7 @@ namespace Gui {
|
|||
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);
|
||||
|
@ -60,6 +71,7 @@ namespace Gui {
|
|||
return elem;
|
||||
};
|
||||
|
||||
/** Appends an existing element to this element. */
|
||||
sptr<Element> append(sptr<Element> elem) {
|
||||
children.push_back(elem);
|
||||
elem->parent = this;
|
||||
|
@ -67,12 +79,25 @@ namespace Gui {
|
|||
return elem;
|
||||
}
|
||||
|
||||
/** Returns the element's computed size. */
|
||||
virtual ivec2 getComputedSize();
|
||||
|
||||
/** Returns the element's computed size + margins. */
|
||||
virtual ivec2 getComputedOuterSize();
|
||||
|
||||
/** Returns the element's computed content size, which is its size - padding. */
|
||||
virtual ivec2 getComputedContentSize();
|
||||
|
||||
/** Returns the element's explicit size. Unspecified dimensions are -1. */
|
||||
virtual ivec2 getExplicitSize();
|
||||
|
||||
/** Returns the element's computed position relative to its parent. */
|
||||
virtual ivec2 getComputedPos();
|
||||
|
||||
/** Returns the element's computed position relative to the screen. */
|
||||
virtual ivec2 getComputedScreenPos();
|
||||
|
||||
/** Gets a style value from the element's styles or the root's stylesheets. */
|
||||
const optional<any> getStyle(StyleRule rule) const {
|
||||
const optional<any> opt = props.styles.get(rule);
|
||||
if (opt) return *opt;
|
||||
|
@ -86,6 +111,7 @@ 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>
|
||||
const optional<V> getStyle(StyleRule rule) const {
|
||||
const optional<V> opt = props.styles.get<V, T>(rule);
|
||||
|
@ -100,6 +126,7 @@ 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>
|
||||
const V getStyle(StyleRule rule, V def) const {
|
||||
const optional<V> opt = getStyle<V, T>(rule);
|
||||
|
@ -107,7 +134,18 @@ namespace Gui {
|
|||
return def;
|
||||
}
|
||||
|
||||
bool handleMouseHover(ivec2 mousePos);
|
||||
/**
|
||||
* 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(u32 button, bool down);
|
||||
|
||||
protected:
|
||||
|
@ -121,10 +159,16 @@ namespace Gui {
|
|||
|
||||
bool hovered = false;
|
||||
|
||||
/** The screen offset of the parent. */
|
||||
ivec2 parentOffset {};
|
||||
|
||||
/** The element's implicit size, as defined by the parent layout. */
|
||||
ivec2 layoutSize { -1, -1 };
|
||||
|
||||
/** The element's implicit position, as defined by the parent layout. */
|
||||
ivec2 layoutPosition {};
|
||||
|
||||
/** Updates child sizes and offsets based on layout styles. */
|
||||
virtual void layoutChildren();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
#include <stack>
|
||||
#include <math.h>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "client/gui/Expression.h"
|
||||
|
||||
#include "util/Util.h"
|
||||
#include "Gui.h"
|
||||
|
||||
Gui::Expression::Expression(const string& exp) {
|
||||
setExpression(exp);
|
||||
}
|
||||
|
||||
void Gui::Expression::setExpression(string exp) {
|
||||
// Avoid reparsing the same expression.
|
||||
usize newHash = std::hash<string>{}(exp);
|
||||
if (hash == newHash) return;
|
||||
hash = newHash;
|
||||
|
||||
// Sanitize expression
|
||||
exp.erase(std::remove_if(exp.begin(), exp.end(), isspace), exp.end());
|
||||
|
||||
// Process Infix into Postfix (RPN)
|
||||
infix = {};
|
||||
std::stack<char> operators {};
|
||||
|
||||
bool nextOperatorIsUnary = true;
|
||||
|
||||
String temp = {};
|
||||
|
||||
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.v += c;
|
||||
nextOperatorIsUnary = false;
|
||||
}
|
||||
// Binary Operator
|
||||
else if (!nextOperatorIsUnary && (c == '+' || c == '-' || c == '*' || c == '/' || c == '^')) {
|
||||
if (temp.v.size()) {
|
||||
infix.emplace(temp);
|
||||
temp = {};
|
||||
}
|
||||
|
||||
while (operators.size() && operators.top() != '(' &&
|
||||
((c != '^' && PRECEDENCE.at(operators.top()) >= PRECEDENCE.at(c)) ||
|
||||
PRECEDENCE.at(operators.top()) > PRECEDENCE.at(c))) {
|
||||
|
||||
infix.emplace(string(1, operators.top()));
|
||||
operators.pop();
|
||||
}
|
||||
|
||||
operators.emplace(c);
|
||||
nextOperatorIsUnary = true;
|
||||
}
|
||||
// Opening Parentheses
|
||||
else if (c == '(') {
|
||||
if (temp.v.size()) {
|
||||
infix.emplace(temp);
|
||||
temp = {};
|
||||
}
|
||||
|
||||
operators.push(c);
|
||||
nextOperatorIsUnary = true;
|
||||
}
|
||||
// Closing Parentheses
|
||||
else if (c == ')') {
|
||||
if (!temp.v.size()) throw std::logic_error("Empty or mismatched parentheses.");
|
||||
infix.emplace(temp);
|
||||
temp = {};
|
||||
|
||||
if (!operators.size()) throw std::logic_error("Mismatched parentheses.");
|
||||
while (operators.top() != '(') {
|
||||
infix.emplace(string(1, operators.top()));
|
||||
operators.pop();
|
||||
if (!operators.size()) throw std::logic_error("Mismatched parentheses.");
|
||||
}
|
||||
if (operators.top() != '(') throw std::logic_error("Mismatched parentheses.");
|
||||
operators.pop();
|
||||
nextOperatorIsUnary = false;
|
||||
}
|
||||
|
||||
exp.erase(0, 1);
|
||||
}
|
||||
|
||||
if (temp.v.size()) {
|
||||
infix.push(temp);
|
||||
temp = {};
|
||||
}
|
||||
|
||||
while (operators.size()) {
|
||||
if (operators.top() == '(') throw std::logic_error("Mismatched parentheses.");
|
||||
infix.emplace(string(1, operators.top()));
|
||||
operators.pop();
|
||||
}
|
||||
}
|
||||
|
||||
f32 Gui::Expression::eval() {
|
||||
let infix = this->infix;
|
||||
std::stack<String> eval {};
|
||||
|
||||
while (infix.size()) {
|
||||
let& t = infix.front();
|
||||
infix.pop();
|
||||
|
||||
if (!t.isOperator()) {
|
||||
eval.emplace(t);
|
||||
}
|
||||
else {
|
||||
if (eval.size() < 2) throw std::runtime_error("Eval stack has < 2 items! This is an engine error!");
|
||||
|
||||
String b = eval.top();
|
||||
eval.pop();
|
||||
String a = eval.top();
|
||||
eval.pop();
|
||||
|
||||
switch (t.v[0]) {
|
||||
case '+':
|
||||
eval.emplace(std::to_string(a.eval() + b.eval()));
|
||||
break;
|
||||
case '-':
|
||||
eval.emplace(std::to_string(a.eval() - b.eval()));
|
||||
break;
|
||||
case '*':
|
||||
eval.emplace(std::to_string(a.eval() * b.eval()));
|
||||
break;
|
||||
case '/':
|
||||
eval.emplace(std::to_string(a.eval() / b.eval()));
|
||||
break;
|
||||
case '^':
|
||||
eval.emplace(std::to_string(pow(a.eval(), b.eval())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!eval.size()) throw std::runtime_error("Eval stack is empty! This is an engine error!");
|
||||
return eval.top().eval();
|
||||
}
|
||||
|
||||
const std::unordered_map<char, u8> Gui::Expression::PRECEDENCE {
|
||||
{ '^', 4 },
|
||||
{ '*', 3 },
|
||||
{ '/', 3 },
|
||||
{ '+', 2 },
|
||||
{ '-', 2 }
|
||||
};
|
||||
|
||||
bool Gui::Expression::String::isOperator() {
|
||||
return v.size() == 1 && (v[0] == '+' || v[0] == '-' || v[0] == '*' || v[0] == '/' || v[0] == '^');
|
||||
}
|
||||
|
||||
f32 Gui::Expression::String::eval() {
|
||||
usize unitInd = -1;
|
||||
|
||||
f32 value = std::stof(v, &unitInd);
|
||||
string unit = v.substr(unitInd);
|
||||
|
||||
switch (Util::hash(unit.data())) {
|
||||
default:
|
||||
throw std::logic_error("Unknown unit '" + unit + "'.");
|
||||
|
||||
case Util::hash("dp"):
|
||||
return value * Gui::PX_SCALE;
|
||||
|
||||
case Util::hash("deg"):
|
||||
return value * M_PI / 180.f;
|
||||
|
||||
case Util::hash(""):
|
||||
case Util::hash("px"):
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "util/Types.h"
|
||||
|
||||
namespace Gui {
|
||||
enum class UnitOrOperator: u8 {
|
||||
DISPLAY_PIXEL,
|
||||
REAL_PIXEL,
|
||||
DEGREE,
|
||||
|
||||
ADD = 128,
|
||||
SUBTRACT,
|
||||
MULTIPLY,
|
||||
DIVIDE,
|
||||
EXPONENT
|
||||
};
|
||||
|
||||
class Expression {
|
||||
struct String {
|
||||
String() = default;
|
||||
explicit String(const string& v): v(v) {}
|
||||
|
||||
string v {};
|
||||
|
||||
bool isOperator();
|
||||
|
||||
f32 eval();
|
||||
};
|
||||
|
||||
struct Token {
|
||||
Token() = default;
|
||||
explicit Token(f32 val, UnitOrOperator unit): val(val), unit(unit);
|
||||
explicit Token(const string& str);
|
||||
|
||||
bool isOperator();
|
||||
f32 evalValue();
|
||||
|
||||
f32 val;
|
||||
UnitOrOperator unit;
|
||||
};
|
||||
public:
|
||||
Expression() = default;
|
||||
Expression(const string& exp);
|
||||
|
||||
void setExpression(string exp);
|
||||
|
||||
f32 eval();
|
||||
private:
|
||||
usize hash = 0;
|
||||
std::queue<String> infix {};
|
||||
|
||||
const static std::unordered_map<char, u8> PRECEDENCE;
|
||||
};
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
#include "Root.h"
|
||||
|
||||
#include "util/Types.h"
|
||||
#include "util/Timer.h"
|
||||
#include "client/gui/BoxElement.h"
|
||||
|
||||
|
@ -7,20 +8,19 @@ Gui::Root::Root(Window& window, TextureAtlas& atlas) :
|
|||
atlas(atlas),
|
||||
window(window),
|
||||
body(make_shared<BoxElement>(*this, stylesheets)) {
|
||||
const ivec2 size = glm::ceil(vec2(window.getSize()) / static_cast<f32>(Gui::PX_SCALE));
|
||||
const ivec2 size = window.getSize();
|
||||
|
||||
body->setProps({
|
||||
.id = "body",
|
||||
.styles = {{
|
||||
{ StyleRule::WIDTH, size.x },
|
||||
{ StyleRule::HEIGHT, size.y }
|
||||
{ StyleRule::SIZE, array<string, 2> {
|
||||
std::to_string(size.x) + "px", std::to_string(size.y) + "px" } }
|
||||
}}
|
||||
});
|
||||
|
||||
lock = window.onResize([&](ivec2 size) {
|
||||
size = glm::ceil(vec2(window.getSize()) / static_cast<f32>(Gui::PX_SCALE));
|
||||
body->setStyle(StyleRule::WIDTH, size.x);
|
||||
body->setStyle(StyleRule::HEIGHT, size.y);
|
||||
body->setStyle(StyleRule::SIZE, array<string, 2> {
|
||||
std::to_string(size.x) + "px", std::to_string(size.y) + "px" });
|
||||
Timer t("Resize UI");
|
||||
body->updateElement();
|
||||
t.printElapsedMs();
|
||||
|
@ -29,16 +29,26 @@ Gui::Root::Root(Window& window, TextureAtlas& atlas) :
|
|||
// window.input.bindMouseCallback()
|
||||
}
|
||||
|
||||
Gui::Root::~Root() {
|
||||
window.setCursorHand(false);
|
||||
}
|
||||
|
||||
void Gui::Root::addStylesheet(const std::unordered_map<string, Style>& sheet) {
|
||||
stylesheets.emplace_back(sheet);
|
||||
}
|
||||
|
||||
void Gui::Root::update() {
|
||||
const let pos = window.input.getMousePos();
|
||||
body->handleMouseHover(pos);
|
||||
bool pointer = false;
|
||||
body->handleMouseHover(pos, pointer);
|
||||
setCursorPointer(pointer);
|
||||
}
|
||||
|
||||
void Gui::Root::draw(Renderer& renderer) {
|
||||
if (!body) return;
|
||||
body->draw(renderer);
|
||||
}
|
||||
|
||||
void Gui::Root::setCursorPointer(bool hand) {
|
||||
window.setCursorHand(hand);
|
||||
}
|
|
@ -12,6 +12,8 @@ namespace Gui {
|
|||
public:
|
||||
Root(Window& window, TextureAtlas& atlas);
|
||||
|
||||
~Root();
|
||||
|
||||
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, stylesheets);
|
||||
|
@ -28,6 +30,8 @@ namespace Gui {
|
|||
|
||||
void draw(Renderer& renderer);
|
||||
|
||||
void setCursorPointer(bool hand);
|
||||
|
||||
vec<StyleSheet> stylesheets;
|
||||
const sptr<Element> body;
|
||||
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
#include "client/gui/Gui.h"
|
||||
|
||||
#include "client/gui/Expression.h"
|
||||
|
||||
namespace Gui {
|
||||
enum class StyleRule {
|
||||
POS,
|
||||
SIZE,
|
||||
MARGIN,
|
||||
PADDING,
|
||||
WIDTH,
|
||||
HEIGHT,
|
||||
TOP,
|
||||
LEFT,
|
||||
|
||||
GAP,
|
||||
LAYOUT,
|
||||
|
@ -16,10 +17,12 @@ namespace Gui {
|
|||
H_ALIGN,
|
||||
V_ALIGN,
|
||||
|
||||
CURSOR,
|
||||
OVERFLOW,
|
||||
|
||||
BACKGROUND,
|
||||
BACKGROUND_HOVER
|
||||
BACKGROUND_HOVER,
|
||||
|
||||
CONTENT
|
||||
};
|
||||
|
||||
enum class ValueType {
|
||||
|
@ -139,9 +142,30 @@ namespace Gui {
|
|||
L == ValueType::LENGTH, bool> = true>
|
||||
|
||||
optional<N> get(StyleRule rule) const {
|
||||
return get<N>(rule);
|
||||
let raw = get<string>(rule);
|
||||
if (!raw) return std::nullopt;
|
||||
return Gui::Expression(*raw).eval();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an optional of the specified Rule's value,
|
||||
* which is interpreted as a length.
|
||||
*/
|
||||
|
||||
template<typename VN, ValueType L, std::enable_if_t<
|
||||
(std::is_integral_v<typename VN::value_type> || std::is_floating_point_v<typename VN::value_type>) &&
|
||||
std::is_same_v<VN, glm::vec<VN::length(), typename VN::value_type>> &&
|
||||
L == ValueType::LENGTH, bool> = true>
|
||||
|
||||
optional<VN> get(StyleRule rule) const {
|
||||
let raw = get<array<string, VN::length()>>(rule);
|
||||
if (!raw) return std::nullopt;
|
||||
VN vec;
|
||||
for (usize i = 0; i < VN::length(); i++)
|
||||
vec[i] = Gui::Expression((*raw)[i]).eval();
|
||||
return vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified Rule's value as a V,
|
||||
* or the default value provided as the second parameter.
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
#include <functional>
|
||||
|
||||
#include "TextElement.h"
|
||||
|
||||
#include "client/gui/Root.h"
|
||||
#include "client/graph/Model.h"
|
||||
#include "client/graph/mesh/EntityMesh.h"
|
||||
|
||||
void Gui::TextElement::updateElement() {
|
||||
const string text = getStyle<string>(StyleRule::CONTENT, "");
|
||||
|
||||
if (!font) {
|
||||
font = std::make_unique<Font>(root.atlas, root.atlas["font"]);
|
||||
}
|
||||
|
||||
usize newHash = std::hash<string>{}(text);
|
||||
if (hash != newHash) {
|
||||
hash = newHash;
|
||||
|
||||
vec4 textColor = vec4(1);
|
||||
vec4 backgroundColor = vec4(0, 0, 0, 0.3);
|
||||
|
||||
u32 ind = 0;
|
||||
u32 width = 0;
|
||||
|
||||
vec<EntityVertex> vertices;
|
||||
vertices.reserve(text.length() * 8 + 200);
|
||||
vec<u32> indices;
|
||||
indices.reserve(text.length() * 12 + 240);
|
||||
|
||||
vec<string> lines;
|
||||
{
|
||||
std::stringstream textStream(text);
|
||||
string line;
|
||||
while (std::getline(textStream, line, '\n')) lines.emplace_back(line);
|
||||
}
|
||||
|
||||
vec3 offset = {};
|
||||
u32 h = Font::charHeight;
|
||||
|
||||
bool bold = false;
|
||||
bool italic = false;
|
||||
i32 underline = -1;
|
||||
i32 strikethrough = -1;
|
||||
u32 strikethroughVertStart = 0;
|
||||
vec4 color = textColor;
|
||||
|
||||
for (usize i = 0; i < lines.size(); i++) {
|
||||
let& line = lines[i];
|
||||
bool empty = line.find_first_not_of(" \t\n") == -1;
|
||||
|
||||
if (empty) {
|
||||
offset.x = 0;
|
||||
offset.y += h / 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 bgVertStart = 0;
|
||||
if (backgroundColor.w != 0) {
|
||||
bgVertStart = vertices.size();
|
||||
for (u32 i = 0; i < 4; i++) vertices.push_back({});
|
||||
for (u32 i : INDICES) indices.push_back(i + ind);
|
||||
ind += 4;
|
||||
}
|
||||
|
||||
for (usize j = 0; j < line.length() + 1; j++) {
|
||||
char c = j < line.length() ? line[j] : ' ';
|
||||
if (c == '\t') c = ' ';
|
||||
|
||||
if (c == '`') {
|
||||
bool flushDecorators = j == line.length();
|
||||
|
||||
char d = line[++j];
|
||||
if (d == '`') goto escape_formatting;
|
||||
else if (d == ' ') offset.x++;
|
||||
else if (d == 'b') bold = true;
|
||||
else if (d == 'i') italic = true;
|
||||
else if (d == 'u') underline = offset.x;
|
||||
else if (d == 's') {
|
||||
strikethrough = offset.x;
|
||||
strikethroughVertStart = vertices.size();
|
||||
for (u32 i = 0; i < 4; i++) vertices.push_back({});
|
||||
for (u32 i : INDICES) indices.push_back(i + ind);
|
||||
ind += 4;
|
||||
}
|
||||
else if (d == 'c') flushDecorators = true;
|
||||
else if (d == 'r') {
|
||||
bold = false;
|
||||
italic = false;
|
||||
flushDecorators = true;
|
||||
}
|
||||
|
||||
if (flushDecorators) {
|
||||
if (underline != -1) {
|
||||
TextElement::drawRect(
|
||||
{ underline, offset.y + h - 1, offset.x, offset.y + h },
|
||||
color, vertices, indices, ind);
|
||||
TextElement::drawRect(
|
||||
{ underline + 1, offset.y + h, offset.x + 1, offset.y + h + 1 },
|
||||
color * BG_MULTIPLE, vertices, indices, ind);
|
||||
underline = offset.x;
|
||||
}
|
||||
|
||||
if (strikethrough != -1) {
|
||||
TextElement::drawRect(
|
||||
{ strikethrough, offset.y + h / 2, offset.x, offset.y + h / 2 + 1 },
|
||||
color, vertices, indices, ind);
|
||||
TextElement::drawRect(
|
||||
{ strikethrough + 1, offset.y + h / 2 + 1, offset.x + 1, offset.y + h / 2 + 2 },
|
||||
color * BG_MULTIPLE, vertices, indices, ind, strikethroughVertStart);
|
||||
strikethrough = offset.x;
|
||||
}
|
||||
|
||||
if (d == 'r') {
|
||||
color = textColor;
|
||||
underline = -1;
|
||||
strikethrough = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (d == 'c') {
|
||||
char code = line[++j];
|
||||
if (code == 'r') color = textColor;
|
||||
else {
|
||||
u32 v;
|
||||
std::stringstream ss;
|
||||
ss << std::hex << code;
|
||||
ss >> v;
|
||||
color = COLORS[v];
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
escape_formatting:
|
||||
if (j == line.length()) continue;
|
||||
|
||||
u32 w = font->getCharWidth(c) + 1;
|
||||
vec4 UV = font->getCharUVs(c);
|
||||
|
||||
for (u32 k = 0; k < (bold ? 4 : 2); k++) {
|
||||
vec4 c = color;
|
||||
|
||||
if (k == 0 || (k == 1 && bold)) c *= BG_MULTIPLE;
|
||||
|
||||
if (k == 0) {
|
||||
offset.x += 1;
|
||||
offset.y += 1;
|
||||
}
|
||||
else if ((k == 1 || k == 3) && bold) {
|
||||
offset.x += 1;
|
||||
}
|
||||
else if ((k == 1 && !bold) || (k == 2 && bold)) {
|
||||
offset.x -= bold ? 2 : 1;
|
||||
offset.y -= 1;
|
||||
}
|
||||
|
||||
vertices.emplace_back(offset + vec3(italic ? 2 : 0, 0, 0), vec4 { UV.x, UV.y, 0, c.w },
|
||||
vec3(c), 1.f, vec3 {}, ivec4 {}, vec4 {});
|
||||
|
||||
vertices.emplace_back(offset + vec3(0, h, 0), vec4 { UV.x, UV.w, 0, c.w },
|
||||
vec3(c), 1.f, vec3 {}, ivec4 {}, vec4 {});
|
||||
|
||||
vertices.emplace_back(offset + vec3(w, h, 0), vec4 { UV.z, UV.w, 0, c.w },
|
||||
vec3(c), 1.f, vec3 {}, ivec4 {}, vec4 {});
|
||||
|
||||
vertices.emplace_back(offset + vec3(w + (italic ? 2 : 0), 0, 0), vec4 { UV.z, UV.y, 0, c.w },
|
||||
vec3(c), 1.f, vec3 {}, ivec4 {}, vec4 {});
|
||||
|
||||
for (u32 i : INDICES) indices.push_back(i + ind);
|
||||
ind += 4;
|
||||
}
|
||||
|
||||
offset.x += w;
|
||||
}
|
||||
|
||||
if (backgroundColor.w != 0) TextElement::drawRect({ -1, offset.y - 1, offset.x + 2, offset.y + h + 1 },
|
||||
backgroundColor, vertices, indices, ind, bgVertStart);
|
||||
|
||||
if (offset.x > width) width = offset.x;
|
||||
offset.x = 0;
|
||||
offset.y += h + 2;
|
||||
}
|
||||
|
||||
let mesh = make_unique<EntityMesh>();
|
||||
mesh->create(vertices, indices);
|
||||
|
||||
let model = make_shared<Model>();
|
||||
model->fromMesh(std::move(mesh));
|
||||
entity.setModel(model);
|
||||
|
||||
}
|
||||
|
||||
entity.setScale(PX_SCALE * (2/3.f));
|
||||
// entity.setScale(vec3(getComputedSize() * static_cast<i32>(PX_SCALE), 0));
|
||||
entity.setPos(vec3(getComputedScreenPos() * static_cast<i32>(PX_SCALE), 0));
|
||||
Element::updateElement();
|
||||
}
|
||||
|
||||
void Gui::TextElement::drawRect(const vec4 pos, const vec4 color,
|
||||
vec<EntityVertex>& vertices, vec<u32>& indices, u32& ind, const u32 insert) {
|
||||
|
||||
vec<EntityVertex> myVerts = {
|
||||
{ vec3 { pos.x, pos.y, 0 }, color, vec3(1), 0.f, vec3 {}, ivec4 {}, vec4 {} },
|
||||
{ vec3 { pos.x, pos.w, 0 }, color, vec3(1), 0.f, vec3 {}, ivec4 {}, vec4 {} },
|
||||
{ vec3 { pos.z, pos.w, 0 }, color, vec3(1), 0.f, vec3 {}, ivec4 {}, vec4 {} },
|
||||
{ vec3 { pos.z, pos.y, 0 }, color, vec3(1), 0.f, vec3 {}, ivec4 {}, vec4 {} }
|
||||
};
|
||||
|
||||
if (insert != -1) {
|
||||
vertices[insert] = myVerts[0];
|
||||
vertices[insert + 1] = myVerts[1];
|
||||
vertices[insert + 2] = myVerts[2];
|
||||
vertices[insert + 3] = myVerts[3];
|
||||
}
|
||||
else {
|
||||
for (EntityVertex& vert : myVerts) vertices.emplace_back(vert);
|
||||
for (u32 i : INDICES) indices.push_back(i + ind);
|
||||
ind += 4;
|
||||
}
|
||||
}
|
||||
|
||||
const array<vec4, 16> Gui::TextElement::COLORS = {
|
||||
Util::hexToColorVec("#ffffff"),
|
||||
Util::hexToColorVec("#aaaaaa"),
|
||||
Util::hexToColorVec("#666666"),
|
||||
Util::hexToColorVec("#000000"),
|
||||
|
||||
Util::hexToColorVec("#f53658"),
|
||||
Util::hexToColorVec("#ff9940"),
|
||||
Util::hexToColorVec("#fffb82"),
|
||||
Util::hexToColorVec("#9fff80"),
|
||||
Util::hexToColorVec("#0fa84f"),
|
||||
Util::hexToColorVec("#26d4d4"),
|
||||
Util::hexToColorVec("#7df4ff"),
|
||||
Util::hexToColorVec("#33a2f5"),
|
||||
Util::hexToColorVec("#2c58e8"),
|
||||
Util::hexToColorVec("#b05cff"),
|
||||
Util::hexToColorVec("#fd7dff"),
|
||||
Util::hexToColorVec("#ff739f"),
|
||||
};
|
||||
|
||||
const array<u32, 6> Gui::TextElement::INDICES = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
const vec4 Gui::TextElement::BG_MULTIPLE = { 0.3, 0.3, 0.35, 0.75 };
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "Element.h"
|
||||
|
||||
#include "client/graph/Font.h"
|
||||
//#include "game/atlas/asset/AtlasRef.h"
|
||||
|
||||
namespace Gui {
|
||||
|
||||
/**
|
||||
* Displays formatted text specified by the contact property.
|
||||
*/
|
||||
|
||||
class TextElement: public Element {
|
||||
public:
|
||||
using Element::Element;
|
||||
|
||||
virtual void updateElement() override;
|
||||
|
||||
protected:
|
||||
// sptr<AtlasRef> tex;
|
||||
// optional<any> curBg;
|
||||
|
||||
private:
|
||||
uptr<Font> font;
|
||||
usize hash = 0;
|
||||
|
||||
void drawRect(const vec4 pos, const vec4 color,
|
||||
vec<EntityVertex>& vertices, vec<u32>& indices, u32& ind, const u32 insert = -1);
|
||||
|
||||
static const vec4 BG_MULTIPLE;
|
||||
static const array<u32, 6> INDICES;
|
||||
static const array<vec4, 16> COLORS;
|
||||
};
|
||||
}
|
|
@ -8,8 +8,8 @@
|
|||
#include "util/Log.h"
|
||||
#include "ConnectScene.h"
|
||||
#include "client/Client.h"
|
||||
#include "client/gui/Gui.h"
|
||||
#include "client/gui/BoxElement.h"
|
||||
#include "client/gui/TextElement.h"
|
||||
#include "client/menu/SubgameDef.h"
|
||||
#include "client/gui/basic/GuiText.h"
|
||||
#include "game/atlas/asset/AtlasRef.h"
|
||||
|
@ -19,79 +19,48 @@
|
|||
MainMenuScene::MainMenuScene(Client& client) : Scene(client),
|
||||
root(client.renderer.window, client.game->textures) {
|
||||
|
||||
// components(make_unique<GuiContainer>()),
|
||||
// menuContainer(make_shared<GuiContainer>("__menu")),
|
||||
// sandbox(sandboxArea, client, menuContainer) {
|
||||
|
||||
client.renderer.setClearColor(0, 0, 0);
|
||||
client.renderer.window.input.setMouseLocked(false);
|
||||
|
||||
// Font f(client.game->textures, client.game->textures["font"]);
|
||||
// win = client.renderer.window.getSize();
|
||||
// sandboxArea = win - ivec2(0, 18 * GS);
|
||||
|
||||
// components->add(menuContainer);
|
||||
//
|
||||
// branding = make_shared<GuiContainer>("zephaBranding");
|
||||
// components->add(branding);
|
||||
// {
|
||||
// auto zephaText = make_shared<GuiText>("zephaText");
|
||||
// zephaText->create({ GS, GS }, {}, { 1, 1, 1, 1 }, {}, f);
|
||||
// zephaText->setText("Zepha");
|
||||
// branding->add(zephaText);
|
||||
//
|
||||
// auto alphaText = make_shared<GuiText>("alphaText");
|
||||
// alphaText->create({ GS, GS }, {}, { 1, 0.5, 0.7, 1 }, {}, f);
|
||||
// alphaText->setText("ALPHA");
|
||||
// alphaText->setPos({ 25 * GS, 0 });
|
||||
// branding->add(alphaText);
|
||||
// }
|
||||
|
||||
// root.body->setStyle(Gui::Style::Rule::DIRECTION, "row");
|
||||
// root.body->setStyle(Gui::Style::Rule::GAP_X, 8);
|
||||
// root.body->setStyle(Gui::Style::Rule::GAP_Y, 8);
|
||||
// root.body->setStyle(Gui::Style::Rule::H_ALIGN, "center");
|
||||
// root.body->setStyle(Gui::Style::Rule::V_ALIGN, "center");
|
||||
|
||||
root.body->setStyle(Gui::StyleRule::BACKGROUND, string("#123"));
|
||||
|
||||
root.addStylesheet({
|
||||
{ "sandbox", {{
|
||||
{ Gui::StyleRule::H_ALIGN, string("center") },
|
||||
{ Gui::StyleRule::V_ALIGN, string("center") }
|
||||
}}},
|
||||
{ "navigation", {{
|
||||
{ Gui::StyleRule::HEIGHT, 18 }
|
||||
{ Gui::StyleRule::SIZE, array<string, 2> { "-1", "18dp" } }
|
||||
}}},
|
||||
{ "navigationWrap", {{
|
||||
{ Gui::StyleRule::DIRECTION, string("row") },
|
||||
{ Gui::StyleRule::TOP, 0 },
|
||||
{ Gui::StyleRule::LEFT, 0 }
|
||||
{ Gui::StyleRule::POS, array<string, 2> { "0", "0" } }
|
||||
}}},
|
||||
{ "navigationBackground", {{
|
||||
{ Gui::StyleRule::WIDTH, 64 },
|
||||
{ Gui::StyleRule::HEIGHT, 18 },
|
||||
{ Gui::StyleRule::SIZE, array<string, 2> { "64dp", "18dp" } },
|
||||
{ Gui::StyleRule::BACKGROUND, string("menu_bar_bg") }
|
||||
}}},
|
||||
{ "navigationButton", {{
|
||||
{ Gui::StyleRule::WIDTH, 16 },
|
||||
{ Gui::StyleRule::HEIGHT, 16 }
|
||||
{ Gui::StyleRule::SIZE, array<string, 2> { "16dp", "16dp" } },
|
||||
{ Gui::StyleRule::CURSOR, string("pointer") }
|
||||
}}}
|
||||
});
|
||||
|
||||
let sandbox = root.body->append<Gui::BoxElement>({ .classes = { "sandbox" } });
|
||||
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++)
|
||||
navigationBG->append<Gui::BoxElement>({ .classes = { "navigationBackground" } });
|
||||
|
||||
|
||||
let navigationList = navigation->append<Gui::BoxElement>({
|
||||
.classes = { "navigationWrap" },
|
||||
.styles = {{
|
||||
{ Gui::StyleRule::PADDING, ivec4(1) },
|
||||
{ Gui::StyleRule::GAP, ivec2(1) }
|
||||
{ Gui::StyleRule::PADDING, array<string, 4> { "1dp", "1dp", "1dp", "1dp" } },
|
||||
{ Gui::StyleRule::GAP, array<string, 2> { "1dp", "1dp" } }
|
||||
}}
|
||||
});
|
||||
|
||||
|
||||
let serversButton = navigationList->append<Gui::BoxElement>({
|
||||
.classes = { "navigationButton" },
|
||||
.styles = {{
|
||||
|
@ -99,7 +68,7 @@ MainMenuScene::MainMenuScene(Client& client) : Scene(client),
|
|||
{ Gui::StyleRule::BACKGROUND_HOVER, string("crop(16, 0, 16, 16, menu_flag_multiplayer)") }
|
||||
}}
|
||||
});
|
||||
|
||||
|
||||
let contentButton = navigationList->append<Gui::BoxElement>({
|
||||
.classes = { "navigationButton" },
|
||||
.styles = {{
|
||||
|
@ -107,9 +76,46 @@ MainMenuScene::MainMenuScene(Client& client) : Scene(client),
|
|||
{ Gui::StyleRule::BACKGROUND_HOVER, string("crop(16, 0, 16, 16, menu_flag_content)") }
|
||||
}}
|
||||
});
|
||||
|
||||
navigationList->append<Gui::BoxElement>({});
|
||||
|
||||
|
||||
navigationList->append<Gui::BoxElement>({
|
||||
.styles = {{
|
||||
{ Gui::StyleRule::BACKGROUND, string("#fff5") },
|
||||
{ Gui::StyleRule::SIZE, array<string, 2> { "1dp", "10dp" } },
|
||||
{ Gui::StyleRule::MARGIN, array<string, 4> { "2dp", "3dp", "2dp", "3dp" } }
|
||||
}}
|
||||
});
|
||||
|
||||
findSubgames();
|
||||
|
||||
for (usize i = 0; i < subgames.size(); i++) {
|
||||
let& subgame = subgames[i];
|
||||
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);
|
||||
// });
|
||||
}
|
||||
|
||||
if (subgames.size() > 0) {
|
||||
selectedSubgame = &subgames[0];
|
||||
// sandbox.load(*selectedSubgame);
|
||||
}
|
||||
|
||||
navigationList->append<Gui::BoxElement>({
|
||||
.styles = {{
|
||||
{ Gui::StyleRule::BACKGROUND, string("#f006") },
|
||||
{ Gui::StyleRule::SIZE, array<string, 2> { "-1", "16dp" } }
|
||||
}}
|
||||
});
|
||||
|
||||
let settingsButton = navigationList->append<Gui::BoxElement>({
|
||||
.classes = { "navigationButton" },
|
||||
.styles = {{
|
||||
|
@ -117,7 +123,7 @@ MainMenuScene::MainMenuScene(Client& client) : Scene(client),
|
|||
{ Gui::StyleRule::BACKGROUND_HOVER, string("crop(16, 0, 16, 16, menu_flag_settings)") }
|
||||
}}
|
||||
});
|
||||
|
||||
|
||||
let closeButton = navigationList->append<Gui::BoxElement>({
|
||||
.classes = { "navigationButton" },
|
||||
.styles = {{
|
||||
|
@ -125,7 +131,7 @@ MainMenuScene::MainMenuScene(Client& client) : Scene(client),
|
|||
{ Gui::StyleRule::BACKGROUND_HOVER, string("crop(16, 0, 16, 16, menu_flag_quit)") }
|
||||
}}
|
||||
});
|
||||
|
||||
|
||||
// closeButton->setCallback(Element::CallbackType::PRIMARY,
|
||||
// [](bool down, ivec2) { if (down) exit(0); });
|
||||
|
||||
|
@ -134,47 +140,10 @@ MainMenuScene::MainMenuScene(Client& client) : Scene(client),
|
|||
// client.scene.setScene(make_unique<ConnectScene>(client, Address{ "127.0.0.1" }));
|
||||
// });
|
||||
|
||||
//
|
||||
// navigationBarIcons->add(contentButton);
|
||||
//
|
||||
// auto divider = make_shared<GuiRect>("divider");
|
||||
// divider->create({ GS, GS * 10 }, {}, { 1, 1, 1, 0.3 });
|
||||
// divider->setPos({ GS * 2 + GS * 18 * 2, GS * 4 });
|
||||
// navigationBarIcons->add(divider);
|
||||
//
|
||||
// findSubgames();
|
||||
//
|
||||
// for (usize i = 0; i < subgames.size(); i++) {
|
||||
// auto& subgame = subgames[i];
|
||||
// auto button = make_shared<GuiImageButton>(subgame.config.name);
|
||||
//
|
||||
// button->create({ 16 * GS, 16 * GS }, {},
|
||||
// client.game->textures["crop(0, 0, 16, 16, " + subgame.iconRef->name + ")"],
|
||||
// client.game->textures["crop(16, 0, 16, 16, " + subgame.iconRef->name + ")"]);
|
||||
//
|
||||
// button->setPos({ GS * 7 + GS * 18 * (i + 2), GS });
|
||||
// button->setCallback(Element::CallbackType::PRIMARY, [&](bool down, ivec2) {
|
||||
// if (!down) return;
|
||||
// selectedSubgame = &subgame;
|
||||
// sandbox.load(*selectedSubgame);
|
||||
// });
|
||||
//
|
||||
// navigationBarIcons->add(button);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (subgames.size() > 0) {
|
||||
// selectedSubgame = &subgames[0];
|
||||
// sandbox.load(*selectedSubgame);
|
||||
// }
|
||||
//
|
||||
// positionElements();
|
||||
//
|
||||
// lock = client.renderer.window.onResize([&](ivec2 newWin) {
|
||||
// win = newWin;
|
||||
// sandboxArea = newWin - ivec2(0, 18 * GS);
|
||||
// positionElements();
|
||||
// });
|
||||
}
|
||||
|
||||
void MainMenuScene::findSubgames() {
|
||||
|
@ -252,26 +221,6 @@ void MainMenuScene::findSubgames() {
|
|||
[](SubgameDef& a, SubgameDef& b) { return a.config.name < b.config.name; });
|
||||
}
|
||||
|
||||
void MainMenuScene::positionElements() {
|
||||
// sandbox.windowResized();
|
||||
//
|
||||
// branding->setPos({ win.x - 55 * GS, win.y - 30 * GS });
|
||||
//
|
||||
// navigationBar->setPos({ 0, win.y - 18 * GS });
|
||||
//
|
||||
// auto navigationBarBg = navigationBar->get<GuiContainer>("navigationBarBg");
|
||||
// for (usize i = 0; i < static_cast<f32>(win.x) / 64.f / GS; i++) {
|
||||
// auto segment = make_shared<GuiRect>("segment_" + std::to_string(i));
|
||||
// segment->create({ 64 * GS, 18 * GS }, {}, client.game->textures["menu_bar_bg"]);
|
||||
// segment->setPos({ i * 64 * GS, 0 });
|
||||
// navigationBarBg->add(segment);
|
||||
// }
|
||||
//
|
||||
// auto navigationBarIcons = navigationBar->get<GuiContainer>("navigationBarIcons");
|
||||
// navigationBarIcons->get<GuiImageButton>("closeButton")->setPos({ win.x - 16 * GS - GS, GS });
|
||||
// navigationBarIcons->get<GuiImageButton>("settingsButton")->setPos({ win.x - 16 * GS * 2 - GS * 3, GS });
|
||||
}
|
||||
|
||||
void MainMenuScene::update() {
|
||||
client.game->textures.update();
|
||||
root.update();
|
||||
|
@ -289,9 +238,4 @@ void MainMenuScene::draw() {
|
|||
renderer.beginGUIDrawCalls();
|
||||
renderer.enableTexture(&client.game->textures.atlasTexture);
|
||||
root.draw(renderer);
|
||||
// components->draw(client.renderer);
|
||||
}
|
||||
|
||||
void MainMenuScene::cleanup() {
|
||||
client.renderer.window.setCursorHand(false);
|
||||
}
|
|
@ -24,31 +24,11 @@ public:
|
|||
|
||||
void draw() override;
|
||||
|
||||
void cleanup() override;
|
||||
|
||||
private:
|
||||
|
||||
/** Repositions elements after a window resize. */
|
||||
void positionElements();
|
||||
|
||||
/** Finds valid subgames in the subgames folder. */
|
||||
/** Find valid subgames in the subgames folder. */
|
||||
void findSubgames();
|
||||
|
||||
/** The UI scaling, in pixels. */
|
||||
static constexpr f32 GS = 3;
|
||||
|
||||
/** The dimensions of the window. */
|
||||
ivec2 win {};
|
||||
|
||||
/** The dimensions of the sandbox area. */
|
||||
ivec2 sandboxArea {};
|
||||
|
||||
/** Element references. */
|
||||
// uptr<GuiContainer> components;
|
||||
// sptr<GuiContainer> branding;
|
||||
// sptr<GuiContainer> navigationBar;
|
||||
// sptr<GuiContainer> menuContainer;
|
||||
|
||||
/** Provides the API for menu mods. */
|
||||
// MenuSandbox sandbox;
|
||||
Gui::Root root;
|
||||
|
@ -58,7 +38,5 @@ private:
|
|||
|
||||
/** A reference to the currently selected subgame. */
|
||||
SubgameDef* selectedSubgame = nullptr;
|
||||
|
||||
Window::RCBLock lock;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
local menu = zepha.build_gui(function()
|
||||
return Gui.Body {
|
||||
background = "zeus_background_christmas_night",
|
||||
return Gui.Box {
|
||||
background = 'zeus_background_christmas_night',
|
||||
|
||||
Gui.Rect {
|
||||
key = "particle_wrap",
|
||||
size = { pc(100), pc(100) }
|
||||
Gui.Box {
|
||||
key = 'particle_wrap',
|
||||
size = { '100%', '100%' }
|
||||
},
|
||||
|
||||
Gui.Rect {
|
||||
key = "sidebar",
|
||||
position = { pc(20), 0 },
|
||||
position_anchor = { pc(50), 0 },
|
||||
size = { 102, pc(100) },
|
||||
background = "#0135",
|
||||
Gui.Box {
|
||||
pos = { '20% - 50s%', 0 },
|
||||
size = { 102, '100%' },
|
||||
|
||||
Gui.Rect {
|
||||
key = "logo",
|
||||
position = { 8, 8 },
|
||||
background = '#0135',
|
||||
|
||||
Gui.Box {
|
||||
pos = { 8, 8 },
|
||||
size = { 86, 30 },
|
||||
background = "zeus_logo"
|
||||
|
||||
background = 'zeus_logo'
|
||||
},
|
||||
|
||||
Gui.Button {
|
||||
key = "buttonPlay",
|
||||
id = 'button_play',
|
||||
|
||||
callbacks = {
|
||||
primary = function() zepha.start_game_local() end
|
||||
},
|
||||
-- callbacks = {
|
||||
-- primary = function() zepha.start_game_local() end
|
||||
-- },
|
||||
|
||||
position = { 6, 50 },
|
||||
pos = { 6, 50 },
|
||||
size = { 90, 20 },
|
||||
background = "crop(0, 0, 90, 20, zeus_button)",
|
||||
background_hover = "crop(0, 20, 90, 20, zeus_button)",
|
||||
content = "Local Play"
|
||||
|
||||
content = 'Local Play',
|
||||
background = 'crop(0, 0, 90, 20, zeus_button)',
|
||||
background_hover = 'crop(0, 20, 90, 20, zeus_button)'
|
||||
},
|
||||
|
||||
Gui.Button {
|
||||
key = "buttonServers",
|
||||
id = 'button_servers',
|
||||
|
||||
callbacks = {
|
||||
primary = function() zepha.start_game() end
|
||||
},
|
||||
-- callbacks = {
|
||||
-- primary = function() zepha.start_game() end
|
||||
-- },
|
||||
|
||||
position = { 6, 74 },
|
||||
pos = { 6, 74 },
|
||||
size = { 90, 20 },
|
||||
background = "crop(0, 0, 90, 20, zeus_button)",
|
||||
background_hover = "crop(0, 20, 90, 20, zeus_button)",
|
||||
content = "Browse Servers"
|
||||
content = 'Browse Servers',
|
||||
background = 'crop(0, 0, 90, 20, zeus_button)',
|
||||
background_hover = 'crop(0, 20, 90, 20, zeus_button)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,35 +62,37 @@ end)
|
|||
-- ) end)
|
||||
-- end, 1)
|
||||
|
||||
local particle_wrap = menu:get("particle_wrap")
|
||||
local particle_wrap = menu:get('particle_wrap')
|
||||
menu(function()
|
||||
for _ = 1, 20 do
|
||||
local scale = 6 + math.random() * 4
|
||||
particle_wrap:append(Gui.Rect {
|
||||
position = { math.floor(math.random() * 600), math.floor(math.random() * 320) },
|
||||
background = "particle_dark",
|
||||
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()
|
||||
local i = 1
|
||||
local part = particle_wrap:get(i)
|
||||
tick = tick + 0.012
|
||||
while part ~= nil do
|
||||
local xO = (-math.sin(tick) * 0.0125 - 0.0025) * part.size[1]
|
||||
local pos = { part.position[1] + xO, part.position[2] + 0.05 * part.size[1] }
|
||||
if pos[2] > 320 then pos[2] = -12 end
|
||||
if pos[1] < -12 then pos[1] = 600 end
|
||||
part.position = pos
|
||||
i = i + 1
|
||||
part = particle_wrap:get(i)
|
||||
end
|
||||
return true
|
||||
end, 0.016)
|
||||
|
||||
|
||||
-- local tick = 0
|
||||
-- zepha.after(function()
|
||||
-- local i = 1
|
||||
-- local part = particle_wrap:get(i)
|
||||
-- tick = tick + 0.012
|
||||
--
|
||||
-- while part ~= nil do
|
||||
-- local xO = (-math.sin(tick) * 0.0125 - 0.0025) * part.size[1]
|
||||
-- local pos = { part.pos[1] + xO, part.pos[2] + 0.05 * part.size[1] }
|
||||
--
|
||||
-- if pos[2] > 320 then pos[2] = -12 end
|
||||
-- if pos[1] < -12 then pos[1] = 600 end
|
||||
--
|
||||
-- i = i + 1
|
||||
-- part.pos = pos
|
||||
-- part = particle_wrap:get(i)
|
||||
-- end
|
||||
--
|
||||
-- return true
|
||||
-- end, 0.016)
|
||||
|
||||
zepha.set_gui(menu)
|
||||
|
|
Loading…
Reference in New Issue