Files
panopainter/engine/layout.h

689 lines
20 KiB
C++

#pragma once
#include "shape.h"
#include "util.h"
#include "shader.h"
#include "font.h"
enum class kAttribute : uint16_t
{
id = const_hash("id"),
Width = const_hash("width"),
MinWidth = const_hash("max-width"),
MaxWidth = const_hash("min-width"),
Height = const_hash("height"),
MinHeight = const_hash("min-height"),
MaxHeight = const_hash("max-height"),
Divisions = const_hash("divisions"),
InnerRadius = const_hash("inner-radius"),
OuterRadius = const_hash("outer-radius"),
Grow = const_hash("grow"),
Shrink = const_hash("shrink"),
FlexDir = const_hash("dir"),
FlexWrap = const_hash("wrap"),
Padding = const_hash("pad"),
Margin = const_hash("margin"),
Color = const_hash("color"),
Thickness = const_hash("thickness"),
BorderColor = const_hash("border-color"),
Type = const_hash("type"),
Text = const_hash("text"),
FontFace = const_hash("font-face"),
FontSize = const_hash("font-size"),
Justify = const_hash("justify"),
Align = const_hash("align"),
Path = const_hash("path"),
Region = const_hash("region"),
Position = const_hash("position"),
Positioning = const_hash("positioning"),
};
enum class kWidget : uint16_t
{
Border = const_hash("border"),
Shape = const_hash("shape"),
Text = const_hash("text"),
Image = const_hash("image"),
Button = const_hash("button"),
ButtonCustom = const_hash("button-custom"),
PopupMenu = const_hash("popup-menu"),
Ref = const_hash("ref"),
};
enum class kShapeType : uint16_t
{
Quad = const_hash("rect"),
Poly = const_hash("poly"),
RoundRect = const_hash("round-rect"),
Slice9 = const_hash("slice9"),
};
enum class kEventResult : uint8_t
{
Consumed,
Available,
};
enum class kEventCategory : uint8_t
{
MouseEvent,
};
enum class kEventType : uint8_t
{
MouseDownL,
MouseDownR,
MouseMove,
MouseUpL,
MouseUpR,
MouseEnter,
MouseLeave,
};
class Event
{
public:
kEventCategory m_cat;
kEventType m_type;
};
class MouseEvent : public Event
{
public:
MouseEvent() { m_cat = kEventCategory::MouseEvent; }
glm::vec2 m_pos;
};
class LayoutManager
{
std::map<uint16_t, std::unique_ptr<class Node>> m_layouts;
std::string m_path;
struct stat m_file_info { 0 };
public:
std::function<void()> on_loaded;
bool load(const char* path);
bool reload();
class Node& operator[](uint16_t id) { return *m_layouts[id]; }
//Node& operator[](const char* ids) { return m_layouts[const_hash(ids)]; }
};
class Node
{
friend class LayoutManager;
public:
Node* parent{ nullptr };
YGNodeRef y_node{ nullptr };
class LayoutManager* m_manager;
uint16_t m_nodeID;
std::string m_nodeID_s;
std::vector<std::unique_ptr<Node>> m_children;
glm::mat4 m_proj;
glm::mat4 m_mvp;
bool m_mouse_inside = false;
bool m_flood_events = false;
bool m_destroyed = false;
glm::vec2 m_pos;
glm::vec2 m_size;
glm::vec4 m_clip;
std::string m_name;
bool m_display = true;
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
Node&& operator=(Node&& o) { return std::forward<Node>(o); }
Node(Node&& o)
{
m_name = std::move(o.m_name);
m_nodeID_s = std::move(o.m_nodeID_s);
m_children = std::move(o.m_children);
for (auto& c : m_children)
c->parent = this;
m_nodeID = o.m_nodeID;
m_display = o.m_display;
parent = o.parent;
y_node = o.y_node;
m_pos = o.m_pos;
m_size = o.m_size;
m_clip = o.m_clip;
o.y_node = nullptr;
o.parent = nullptr;
}
Node() { y_node = YGNodeNew(); }
~Node()
{
m_children.clear();
if (y_node)
YGNodeFree(y_node);
}
void SetWidth(float value) { YGNodeStyleSetWidth(y_node, value); }
void SetWidthP(float value) { YGNodeStyleSetWidthPercent(y_node, value); }
void SetHeight(float value) { YGNodeStyleSetHeight(y_node, value); }
void SetHeightP(float value) { YGNodeStyleSetHeightPercent(y_node, value); }
void SetSize(glm::vec2 value) { SetWidth(value.x); SetHeight(value.y); }
void SetPadding(float t, float r, float b, float l)
{
YGNodeStyleSetPadding(y_node, YGEdgeTop, t);
YGNodeStyleSetPadding(y_node, YGEdgeRight, r);
YGNodeStyleSetPadding(y_node, YGEdgeBottom, b);
YGNodeStyleSetPadding(y_node, YGEdgeLeft, l);
}
void SetPosition(float l, float t, float r, float b)
{
YGNodeStyleSetPosition(y_node, YGEdgeTop, t);
YGNodeStyleSetPosition(y_node, YGEdgeRight, r);
YGNodeStyleSetPosition(y_node, YGEdgeBottom, b);
YGNodeStyleSetPosition(y_node, YGEdgeLeft, l);
}
void SetPosition(float l, float t)
{
YGNodeStyleSetPosition(y_node, YGEdgeTop, t);
YGNodeStyleSetPosition(y_node, YGEdgeLeft, l);
}
void SetFlexGrow(float value) { YGNodeStyleSetFlexGrow(y_node, value); }
void SetFlexShrink(float value) { YGNodeStyleSetFlexShrink(y_node, value); }
void SetFlexDir(YGFlexDirection value) { YGNodeStyleSetFlexDirection(y_node, value); }
void SetFlexWrap(YGWrap value) { YGNodeStyleSetFlexWrap(y_node, value); }
void SetJustify(YGJustify value) { YGNodeStyleSetJustifyContent(y_node, value); }
void SetAlign(YGAlign value) { YGNodeStyleSetAlignItems(y_node, value); }
void SetPositioning(YGPositionType value) { YGNodeStyleSetPositionType(y_node, value); }
glm::vec4 rect_intersection(glm::vec4 a, glm::vec4 b)
{
// convert from [x,y,w,h] to [x1,y1,x2,y1]
a = glm::vec4(a.xy(), a.xy() + a.zw());
b = glm::vec4(b.xy(), b.xy() + b.zw());
auto o = glm::vec4(glm::max(a.xy(), b.xy()), glm::min(a.zw(), b.zw()));
o = glm::vec4(o.xy(), glm::max({ 0, 0 }, o.zw() - o.xy()));
return o;
}
void update(float width, float height);
void update();
void update_internal(const glm::vec2& origin, const glm::mat4& proj);
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr);
void load_internal(const tinyxml2::XMLElement* x_node);
virtual void draw() { }
Node* clone();
virtual Node* clone_instantiate() const;
virtual void clone_copy(Node* dest) const;
virtual void clone_children(Node* dest) const;
void watch(std::function<void(Node*)> observer)
{
observer(this);
for (auto& c : m_children)
c->watch(observer);
}
void destroy() { m_destroyed = true; }
template<class T = Node> T* find(const char* ids)
{
uint16_t id = const_hash(ids);
if (id == m_nodeID)
return static_cast<T*>(this);
for (auto& c : m_children)
if (auto found = c->find(ids))
return static_cast<T*>(found);
return nullptr;
}
virtual kEventResult on_event(Event* e);
virtual kEventResult handle_event(Event* e) { return kEventResult::Available; }
virtual void create() { }
virtual void init() { }
virtual void loaded() { }
void add_child(Node* n);
void remove_child(Node* n);
// class iterator
// {
// std::stack<Node*> m_nodes;
// Node* m_current;
// public:
// iterator(Node* root)
// {
// m_current = root;
// if (root)
// m_nodes.push(root);
// }
// iterator& operator++()
// {
// m_nodes.pop();
// for (auto& c : m_current->m_children)
// m_nodes.push(c.get());
// m_current = m_nodes.size() ? m_nodes.top() : nullptr;
// return *this;
// }
// Node& operator*() { return *m_current; }
// Node* operator->() { return m_current; }
// bool operator==(const iterator& rhs) { return m_current == rhs.m_current; }
// bool operator!=(const iterator& rhs) { return m_current != rhs.m_current; }
// };
// iterator begin()
// {
// return this;
// }
// iterator end()
// {
// return nullptr;
// }
};
class NodeBorder : public Node
{
public:
static Plane m_plane;
glm::vec4 m_color{ 0, 0, 0, 1 };
glm::vec4 m_border_color{ 1, 1, 1, 1 };
float m_thinkness{ 0 };
static void static_init()
{
m_plane.create<1>(1, 1);
}
virtual Node* clone_instantiate() const override { return new NodeBorder(); }
virtual void clone_copy(Node* dest) const override
{
Node::clone_copy(dest);
NodeBorder* n = static_cast<NodeBorder*>(dest);
n->m_color = m_color;
n->m_border_color = m_border_color;
n->m_thinkness = m_thinkness;
}
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override
{
Node::parse_attributes(ka, attr);
switch (ka)
{
case kAttribute::Color:
{
glm::vec4 pad;
int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
if (n == 1)
m_color = glm::vec4(pad.x);
else
m_color = pad;
break;
}
case kAttribute::BorderColor:
{
glm::vec4 pad;
int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
if (n == 1)
m_border_color = glm::vec4(pad.x);
else
m_border_color = pad;
break;
}
case kAttribute::Thickness:
m_thinkness = attr->FloatValue();
break;
default:
break;
}
}
virtual void draw() override
{
ShaderManager::use(kShader::Color);
ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
if (m_color.a != 1.f)
glEnable(GL_BLEND);
ShaderManager::u_vec4(kShaderUniform::Col, m_color);
m_plane.draw_fill();
if (m_thinkness > 0)
{
glLineWidth(m_thinkness);
ShaderManager::u_vec4(kShaderUniform::Col, m_border_color);
m_plane.draw_stroke();
}
if (m_color.a != 1.f)
glDisable(GL_BLEND);
}
};
class NodeText : public Node
{
public:
TextMesh m_text_mesh;
std::string m_text;
std::string m_font;
glm::vec4 m_color{ 1, 1, 1, 1 };
int m_font_size;
virtual Node* clone_instantiate() const override { return new NodeText(); }
virtual void clone_copy(Node* dest) const override
{
Node::clone_copy(dest);
NodeText* n = static_cast<NodeText*>(dest);
n->m_text_mesh = m_text_mesh;
n->m_text = m_text;
n->m_font = m_font;
n->m_color = m_color;
n->m_font_size = m_font_size;
}
virtual void create() override
{
char font[64];
sprintf(font, "%s-%d", m_font.c_str(), m_font_size);
kFont font_id = (kFont)const_hash(font);
m_text_mesh.create();
m_text_mesh.update(font_id, m_text.c_str());
SetSize(m_text_mesh.bb);
}
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override
{
Node::parse_attributes(ka, attr);
switch (ka)
{
case kAttribute::Text:
m_text = attr->Value();
break;
case kAttribute::FontFace:
m_font = attr->Value();
break;
case kAttribute::FontSize:
m_font_size = attr->IntValue();
break;
case kAttribute::Color:
{
glm::vec4 pad;
int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
if (n == 1)
m_color = glm::vec4(pad.x);
else
m_color = pad;
break;
}
default:
break;
}
}
virtual void draw() override
{
glm::mat4 pos = glm::translate(glm::vec3(glm::floor(m_pos), 0));
m_mvp = m_proj * pos;
ShaderManager::use(kShader::Font);
ShaderManager::u_int(kShaderUniform::Tex, 0);
ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
ShaderManager::u_vec4(kShaderUniform::Col, m_color);
glEnable(GL_BLEND);
m_text_mesh.draw();
glDisable(GL_BLEND);
}
};
class NodeImage : public Node
{
public:
static Plane m_plane;
static Sampler m_sampler;
bool m_use_atlas;
glm::vec4 m_region;
glm::vec2 m_off;
glm::vec2 m_sz;
std::string m_path;
uint16_t m_tex_id;
static void static_init()
{
m_plane.create<1>(1, 1);
m_sampler.create();
}
virtual Node* clone_instantiate() const override { return new NodeImage(); }
virtual void clone_copy(Node* dest) const override
{
Node::clone_copy(dest);
NodeImage* n = static_cast<NodeImage*>(dest);
n->m_use_atlas = m_use_atlas;
n->m_region = m_region;
n->m_off = m_off;
n->m_sz = m_sz;
n->m_path = m_path;
n->m_tex_id = m_tex_id;
}
virtual void create() override
{
if (TextureManager::load(m_path.c_str()))
{
auto tex_sz = TextureManager::get(m_tex_id).size();
m_off = m_region.xy / tex_sz;
m_sz = (m_region.zw - m_region.xy) / tex_sz;
}
}
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override
{
Node::parse_attributes(ka, attr);
switch (ka)
{
case kAttribute::Path:
m_path = attr->Value();
m_tex_id = const_hash(attr->Value());
break;
case kAttribute::Region:
{
glm::vec4 v;
int n = sscanf(attr->Value(), "%f %f %f %f", &v.x, &v.y, &v.z, &v.w);
if (n == 4)
{
m_region = v;
m_use_atlas = true;
}
break;
}
default:
break;
}
}
virtual void draw() override
{
TextureManager::get(m_tex_id).bind();
m_sampler.bind(0);
glEnable(GL_BLEND);
if (m_use_atlas)
{
ShaderManager::use(kShader::Atlas);
ShaderManager::u_vec2(kShaderUniform::Tof, m_off);
ShaderManager::u_vec2(kShaderUniform::Tsz, m_sz);
}
else
{
ShaderManager::use(kShader::Texture);
}
ShaderManager::u_int(kShaderUniform::Tex, 0);
ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
m_plane.draw_fill();
m_sampler.unbind();
TextureManager::get(m_tex_id).unbind();
glDisable(GL_BLEND);
}
};
class NodeButton : public Node
{
public:
NodeBorder* m_border;
NodeText* m_text;
glm::vec4 color_normal{ .1, .1, .1, 1 };
glm::vec4 color_hover{ .2, .2, .2, 1 };
glm::vec4 color_down{ .3, .3, .3, 1 };
std::function<void()> on_click;
virtual Node* clone_instantiate() const override { return new NodeButton(); }
virtual void clone_children(Node* dest) const override
{
Node::clone_children(dest);
NodeButton* n = static_cast<NodeButton*>(dest);
n->m_border = (NodeBorder*)n->m_children[0].get();
n->m_text = (NodeText*)n->m_border->m_children[0].get();
}
virtual void clone_copy(Node* dest) const override
{
Node::clone_copy(dest);
NodeButton* n = static_cast<NodeButton*>(dest);
//n->m_border = (NodeBorder*)m_border->clone();
//n->m_text = (NodeText*)m_text->clone();
n->color_normal = color_normal;
n->color_hover = color_hover;
n->color_down = color_down;
//n->on_click = on_click;
}
virtual void init() override
{
m_border = new NodeBorder();
m_text = new NodeText();
add_child(m_border);
m_border->add_child(m_text);
m_border->init();
m_border->m_color = color_normal;
m_text->init();
m_text->m_font = "arial";
m_text->m_font_size = 11;
m_border->SetAlign(YGAlignCenter);
m_border->SetJustify(YGJustifyCenter);
}
virtual void create() override
{
m_border->create();
m_text->create();
}
virtual void loaded() override
{
m_border->m_thinkness = 1;
m_border->m_border_color = glm::vec4(0, 0, 0, 1);
m_border->m_color = color_normal;
}
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override
{
switch (ka)
{
case kAttribute::Padding:
case kAttribute::Width:
case kAttribute::Height:
case kAttribute::Color:
case kAttribute::Thickness:
case kAttribute::BorderColor:
m_border->parse_attributes(ka, attr);
break;
case kAttribute::Text:
case kAttribute::FontFace:
case kAttribute::FontSize:
m_text->parse_attributes(ka, attr);
break;
default:
Node::parse_attributes(ka, attr);
break;
}
// m_border->parse_attributes(ka, attr);
// m_text->parse_attributes(ka, attr);
}
virtual kEventResult handle_event(Event* e) override
{
switch (e->m_type)
{
case kEventType::MouseEnter:
m_border->m_color = color_hover;
break;
case kEventType::MouseLeave:
m_border->m_color = color_normal;
break;
case kEventType::MouseDownL:
m_border->m_color = color_down;
break;
case kEventType::MouseUpL:
m_border->m_color = color_normal;
if (m_mouse_inside && on_click != nullptr)
on_click();
break;
default:
break;
}
return kEventResult::Consumed;
}
};
class NodeMessageBox : public Node
{
public:
Node* m_template;
NodeButton* btnOk;
virtual Node* clone_instantiate() const override { return new NodeMessageBox(); }
virtual void init() override
{
SetPosition(0, 0);
SetWidthP(100);
SetHeightP(100);
SetPositioning(YGPositionTypeAbsolute);
m_template = (*m_manager)[const_hash("message-box")].m_children[0]->clone();
add_child(m_template);
btnOk = m_template->find<NodeButton>("btn-ok");
btnOk->on_click = [&] { destroy(); };
}
};
class NodePopupMenu : public Node
{
public:
virtual Node* clone_instantiate() const override { return new NodePopupMenu(); }
virtual void init() override
{
m_flood_events = true;
SetPadding(10, 10, 10, 10);
SetPosition(0, 0);
SetWidth(100);
SetHeight(400);
SetPositioning(YGPositionTypeAbsolute);
}
virtual kEventResult handle_event(Event* e) override
{
return kEventResult::Consumed;
}
};
class NodeButtonCustom : public NodeBorder
{
public:
glm::vec4 color_normal{ .1, .1, .1, 1 };
glm::vec4 color_hover{ .2, .2, .2, 1 };
glm::vec4 color_down{ .3, .3, .3, 1 };
std::function<void()> on_click;
virtual Node* clone_instantiate() const override { return new NodeButtonCustom(); }
virtual void clone_copy(Node* dest) const override
{
Node::clone_copy(dest);
NodeButtonCustom* n = static_cast<NodeButtonCustom*>(dest);
n->color_normal = color_normal;
n->color_hover = color_hover;
n->color_down = color_down;
}
virtual void loaded() override
{
m_thinkness = 1;
m_border_color = glm::vec4(0, 0, 0, 1);
m_color = color_normal;
}
virtual kEventResult handle_event(Event* e) override
{
switch (e->m_type)
{
case kEventType::MouseEnter:
m_color = color_hover;
break;
case kEventType::MouseLeave:
m_color = color_normal;
break;
case kEventType::MouseDownL:
m_color = color_down;
break;
case kEventType::MouseUpL:
m_color = color_normal;
if (m_mouse_inside && on_click != nullptr)
on_click();
break;
default:
break;
}
return kEventResult::Consumed;
}
};