#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"), }; 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"), 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 Widget { public: glm::mat4 mvp; glm::mat4 proj; glm::mat4 scale; glm::mat4 pos; glm::vec4 clip; bool m_mouse_inside = false; virtual Widget* clone() = 0; virtual void create() { } virtual void draw() { } virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) { } virtual void update() { } virtual kEventResult on_event(Event* e) { return kEventResult::Available; } }; class Node { friend class LayoutManager; const Node* parent{ nullptr }; YGNodeRef y_node{ nullptr }; class LayoutManager* m_manager; public: uint16_t m_nodeID; std::string m_nodeID_s; std::vector> m_children; glm::mat4 m_proj; glm::mat4 m_mvp; bool m_mouse_inside = 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(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 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); } 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_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; template T* find(const char* ids) { uint16_t id = const_hash(ids); if (id == m_nodeID) return static_cast(this); for (auto& c : *this) if (c.m_nodeID == id) return static_cast(&c); return nullptr; } kEventResult on_event(Event* e); virtual kEventResult handle_event(Event* e) { return kEventResult::Available; } virtual void create() { } virtual void init() { } void add_child(Node* n); class iterator { std::stack 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; glm::vec4 m_border_color; float m_thinkness{ 1 }; 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(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); ShaderManager::u_vec4(kShaderUniform::Col, m_color); m_plane.draw_fill(); glLineWidth(m_thinkness); ShaderManager::u_vec4(kShaderUniform::Col, m_border_color); m_plane.draw_stroke(); } }; 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(dest); } 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_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(dest); } virtual void create() override { if (TextureManager::load(m_path.c_str())) { auto tex_sz = TextureManager::get(m_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_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_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_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 on_click; 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(dest); } 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_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 parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override { Node::parse_attributes(ka, attr); switch (ka) { 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: break; } // m_border->parse_attributes(ka, attr); // m_text->parse_attributes(ka, attr); } virtual void draw() override { m_border->draw(); m_text->draw(); } 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 LayoutManager { std::map> m_layouts; std::string m_path; struct stat m_file_info { 0 }; public: bool load(const char* path); bool reload(); Node& operator[](uint16_t id) { return *m_layouts[id]; } //Node& operator[](const char* ids) { return m_layouts[const_hash(ids)]; } };