#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"), 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"), }; class Widget { public: glm::mat4 mvp; glm::mat4 proj; glm::mat4 scale; glm::mat4 pos; glm::vec4 clip; virtual std::unique_ptr clone() = 0; virtual void create() { } virtual void draw() { } virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) { } virtual void update() { } }; class WidgetRef : public Widget { public: uint16_t id; virtual void create() { } virtual void draw() { } virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) { } }; class WidgetBorder : public Widget { public: static Plane m_plane; glm::vec4 m_color; glm::vec4 m_border_color; float m_thinkness{ 1 }; static void init() { m_plane.create<1>(1, 1); } virtual std::unique_ptr clone() override { auto ret = std::make_unique(); *ret = *this; return std::move(ret); } virtual void draw() override { ShaderManager::use(kShader::Color); ShaderManager::u_mat4(kShaderUniform::MVP, 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(); } virtual void parse_attributes(kAttribute id, const tinyxml2::XMLAttribute* attr) override { switch (id) { 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; } } }; class WidgetShape : public Widget { public: std::unique_ptr m_shape; kShapeType m_type; glm::vec4 m_color; glm::vec4 m_border_color; float m_thinkness{ 1 }; float m_radius{ .5f }; virtual std::unique_ptr clone() override { auto ret = std::make_unique(); //*ret = *this; return std::move(ret); } virtual void create() override { switch (m_type) { case kShapeType::Quad: m_shape = std::make_unique(); ((Plane*)m_shape.get())->create<1>(1, 1); break; case kShapeType::Poly: m_shape = std::make_unique(); ((Circle*)m_shape.get())->create<5>(m_radius); break; case kShapeType::RoundRect: m_shape = std::make_unique(); ((Rounded*)m_shape.get())->create<3>(1, 1, .1f); break; case kShapeType::Slice9: m_shape = std::make_unique(); ((Slice9*)m_shape.get())->create(1, 1, .1f, .1f); break; default: break; } } virtual void draw() override { ShaderManager::use(kShader::Color); ShaderManager::u_mat4(kShaderUniform::MVP, mvp); ShaderManager::u_vec4(kShaderUniform::Col, m_color); m_shape->draw_fill(); glLineWidth(m_thinkness); ShaderManager::u_vec4(kShaderUniform::Col, m_border_color); m_shape->draw_stroke(); } virtual void parse_attributes(kAttribute id, const tinyxml2::XMLAttribute* attr) override { switch (id) { 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; case kAttribute::Type: m_type = (kShapeType)const_hash(attr->Value()); break; default: break; } } }; class WidgetText : public Widget { public: TextMesh m_text_mesh; std::string m_text; std::string m_font; glm::vec4 m_color{ 1, 1, 1, 1 }; int m_size; virtual std::unique_ptr clone() override { auto ret = std::make_unique(); *ret = *this; return std::move(ret); } virtual void create() override { char font[64]; sprintf(font, "%s-%d", m_font.c_str(), m_size); kFont font_id = (kFont)const_hash(font); m_text_mesh.create(); m_text_mesh.update(font_id, m_text.c_str()); } virtual void draw() override { ShaderManager::use(kShader::Font); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_mat4(kShaderUniform::MVP, mvp); ShaderManager::u_vec4(kShaderUniform::Col, m_color); m_text_mesh.draw(); } virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override { switch (ka) { case kAttribute::Text: m_text = attr->Value(); break; case kAttribute::FontFace: m_font = attr->Value(); break; case kAttribute::FontSize: m_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 update() override { mvp = proj * pos; } }; class WidgetImage : public Widget { 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 init() { m_plane.create<1>(1, 1); m_sampler.create(); } virtual std::unique_ptr clone() override { auto ret = std::make_unique(); *ret = *this; return std::move(ret); } 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 draw() override { TextureManager::get(m_id).bind(); m_sampler.bind(0); 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, mvp); m_plane.draw_fill(); m_sampler.unbind(); TextureManager::get(m_id).unbind(); } virtual void parse_attributes(kAttribute id, const tinyxml2::XMLAttribute* attr) override { switch (id) { 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; } } }; class Node { friend class LayoutManager; const Node* parent{ nullptr }; YGNodeRef y_node{ nullptr }; class LayoutManager* m_manager; public: std::vector m_children; std::unique_ptr m_widget; glm::vec2 m_pos; glm::vec2 m_size; glm::vec4 m_clip; std::string m_name; 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_widget = std::move(o.m_widget); m_children = std::move(o.m_children); for (auto& c : m_children) c.parent = this; 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); } 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); void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr); void load_internal(const tinyxml2::XMLElement* x_node); void draw(); Node clone(); 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); 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 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)]; } };