#pragma once #include "shape.h" #include "util.h" #include "shader.h" #include "font.h" #include "asset.h" #include #include enum class kAttribute : uint16_t { id = const_hash("id"), Width = const_hash("width"), MinWidth = const_hash("min-width"), MaxWidth = const_hash("max-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"), FloodEvents = const_hash("flood-events"), Icon = const_hash("icon"), Selected = const_hash("selected"), }; enum class kWidget : uint16_t { Border = const_hash("border"), Shape = const_hash("shape"), Text = const_hash("text"), Image = const_hash("image"), Icon = const_hash("icon"), Button = const_hash("button"), ButtonCustom = const_hash("button-custom"), SliderCursor = const_hash("slider-cursor"), Slider = const_hash("slider"), PopupMenu = const_hash("popup-menu"), Viewport = const_hash("viewport"), Ref = const_hash("ref"), CheckBox = const_hash("checkbox"), Layer = const_hash("layer"), PanelLayers = const_hash("panel-layers"), PanelBrushes = const_hash("panel-brushes"), }; 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> m_layouts; std::string m_path; struct stat m_file_info { 0 }; public: std::function on_loaded; bool load(const char* path); bool reload(); class Node* operator[](uint16_t id) { auto i = m_layouts.find(id); return i == m_layouts.end() ? nullptr : i->second.get(); } //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> m_children; Node* current_mouse_capture = nullptr; bool m_mouse_captured = false; glm::mat4 m_proj; glm::mat4 m_mvp; bool m_mouse_inside = false; bool m_flood_events = false; bool m_destroyed = false; bool m_mouse_ignore = true; float m_zoom = 1.f; glm::vec2 m_scale{ 1.f }; 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; m_zoom = o.m_zoom; m_mouse_ignore = o.m_mouse_ignore; 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); } void SetAspectRatio(float ar) { YGNodeStyleSetAspectRatio(y_node, ar); } glm::vec2 GetPosition() { return{ YGNodeLayoutGetLeft(y_node), YGNodeLayoutGetTop(y_node) }; } float GetWidth() { return YGNodeLayoutGetWidth(y_node); } float GetHeight() { return YGNodeLayoutGetHeight(y_node); } glm::vec2 GetSize() { return { GetWidth(), GetHeight() }; } 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, float zoom); 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 observer) { observer(this); for (auto& c : m_children) c->watch(observer); } void destroy() { m_destroyed = true; } Node* root() { Node* ret = this; while (ret->parent) ret = ret->parent; return ret; } template T* find(const char* ids) { uint16_t id = const_hash(ids); if (id == m_nodeID) return static_cast(this); for (auto& c : m_children) if (auto found = c->find(ids)) return static_cast(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() { } const Node* init_template(const char* id); void add_child(Node* n); void add_child(Node* n, int index); void remove_child(Node* n); void move_child(Node* n, int index); void move_child_offset(Node* n, int offset); int get_child_index(Node* n); void mouse_capture() { root()->current_mouse_capture = this; m_mouse_captured = true; } void mouse_release() { root()->current_mouse_capture = nullptr; m_mouse_captured = false; } // 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 ui::Plane m_plane; glm::vec4 m_color{ 0, 0, 0, 1 }; glm::vec4 m_border_color{ 1, 1, 1, 1 }; float m_thinkness{ 0 }; NodeBorder() { m_mouse_ignore = false; } 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; n->m_mouse_ignore = false; } 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, pad.x, pad.x, 1); 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 { using namespace ui; ui::ShaderManager::use(kShader::Color); ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp); if (m_color.a != 1.f) glEnable(GL_BLEND); ui::ShaderManager::u_vec4(kShaderUniform::Col, m_color); m_plane.draw_fill(); if (m_thinkness > 0) { glLineWidth(m_thinkness); ui::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; kFont font_id; 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); n->m_text_mesh.create(); n->m_text_mesh.update(font_id, m_text.c_str()); n->m_text = m_text; n->m_font = m_font; n->m_color = m_color; n->m_font_size = m_font_size; n->font_id = font_id; } virtual void create() override { char font[64]; sprintf(font, "%s-%d", m_font.c_str(), m_font_size); 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; } } void set_text(const char* s) { m_text = s; m_text_mesh.update(font_id, s); SetSize(m_text_mesh.bb); } virtual void draw() override { using namespace ui; glm::mat4 pos = glm::translate(glm::vec3(glm::floor(m_pos), 0)); m_mvp = m_proj * pos; ui::ShaderManager::use(kShader::Font); ui::ShaderManager::u_int(kShaderUniform::Tex, 0); ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp); ui::ShaderManager::u_vec4(kShaderUniform::Col, m_color); glEnable(GL_BLEND); m_text_mesh.draw(); glDisable(GL_BLEND); } }; class NodeImage : public Node { public: static ui::Plane m_plane; static Sampler m_sampler; bool m_use_atlas = false; 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(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 (!m_path.empty() && 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 { using namespace ui; TextureManager::get(m_tex_id).bind(); m_sampler.bind(0); glEnable(GL_BLEND); if (m_use_atlas) { ui::ShaderManager::use(kShader::Atlas); ui::ShaderManager::u_vec2(kShaderUniform::Tof, m_off); ui::ShaderManager::u_vec2(kShaderUniform::Tsz, m_sz); } else { ui::ShaderManager::use(kShader::Texture); } ui::ShaderManager::u_int(kShaderUniform::Tex, 0); ui::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 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(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(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; n->m_mouse_ignore = false; } 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); m_border->m_mouse_ignore = false; m_mouse_ignore = false; } virtual void create() override { m_border->create(); m_text->create(); m_border->m_mouse_ignore = false; m_mouse_ignore = false; } 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; m_border->m_mouse_ignore = false; m_mouse_ignore = false; } 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 { Node::handle_event(e); 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(this); 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("btn-ok"); btnOk->on_click = [&](Node*) { destroy(); }; } }; class NodePopupMenu : public Node { public: virtual Node* clone_instantiate() const override { return new NodePopupMenu(); } virtual void init() override { m_flood_events = true; SetPosition(0, 0); SetWidth(100); SetHeight(400); SetPositioning(YGPositionTypeAbsolute); } virtual kEventResult handle_event(Event* e) override { switch (e->m_type) { case kEventType::MouseDownL: if (!m_mouse_inside) { mouse_release(); destroy(); } break; case kEventType::MouseUpL: break; default: break; } return kEventResult::Consumed; } }; class NodeButtonCustom : public NodeBorder { public: glm::vec4 color_normal{ .2, .2, .2, 1 }; glm::vec4 color_hover{ .3, .3, .3, 1 }; glm::vec4 color_down{ .4, .4, .4, 1 }; std::function on_click; virtual Node* clone_instantiate() const override { return new NodeButtonCustom(); } virtual void clone_copy(Node* dest) const override { NodeBorder::clone_copy(dest); NodeButtonCustom* n = static_cast(dest); n->color_normal = color_normal; n->color_hover = color_hover; n->color_down = color_down; n->m_mouse_ignore = false; n->m_color = color_normal; } virtual void loaded() override { NodeBorder::loaded(); //m_thinkness = 1; //m_border_color = glm::vec4(0, 0, 0, 1); m_color = color_normal; m_mouse_ignore = false; } virtual kEventResult handle_event(Event* e) override { NodeBorder::handle_event(e); 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 = m_mouse_inside ? color_hover : color_normal; if (m_mouse_inside && on_click != nullptr) on_click(this); break; default: break; } return kEventResult::Consumed; } virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override { NodeBorder::parse_attributes(ka, attr); switch (ka) { case kAttribute::Color: color_normal = m_color; break; default: break; } } }; class NodeSettings : public Node { Node* m_template; NodeButton* btnOk; public: virtual Node* clone_instantiate() const override { return new NodeButtonCustom(); } virtual void init() override { SetPosition(0, 0); SetWidthP(100); SetHeightP(100); SetPositioning(YGPositionTypeAbsolute); m_template = (*m_manager)[const_hash("settings")]->m_children[0]->clone(); add_child(m_template); btnOk = m_template->find("btn-ok"); btnOk->on_click = [&](Node*) { destroy(); }; } virtual kEventResult handle_event(Event* e) override { return kEventResult::Consumed; } }; class NodeIcon : public NodeImage { static std::map m_icons; std::string m_icon_name; public: static void static_init() { // spritesheet maker: https://draeton.github.io/stitches/ // icons: http://www.famfamfam.com/lab/icons/silk/ // regex css -> spritesheet.txt: \.([^{]+) {\s+width: (\d+)px;\s+height: (\d+)px;\s+.*: -(\d+)px -(\d+)px;\s+}\s+ // to: "\1",\2,\3,\4,\5\n Asset file; if (!(file.open("data/spritesheet.txt") && file.read_all())) return; char* data = (char*)file.m_data; int size = file.m_len; static char name[256]; int x, y, w, h; char* s = strtok(data, "\n"); int i = strlen(s) + 1; while (i < size && sscanf(s, "%s %d %d %d %d", name, &w, &h, &x, &y) == 5) { m_icons[name] = glm::vec4(x, y, x + w, y + h); s = strtok(nullptr, "\n"); i += strlen(s) + 1; } file.close(); } virtual Node* clone_instantiate() const override { return new NodeIcon(); } virtual void clone_copy(Node* dest) const override { NodeImage::clone_copy(dest); NodeIcon* n = static_cast(dest); n->m_icon_name = m_icon_name; } virtual void create() override { m_region = m_icons[m_icon_name]; m_path = "data/spritesheet.png"; m_tex_id = const_hash(m_path.c_str()); m_use_atlas = true; NodeImage::create(); auto tex_sz = TextureManager::get(m_tex_id).size(); YGNodeStyleSetAspectRatio(y_node, tex_sz.x / tex_sz.y); } virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override { NodeImage::parse_attributes(ka, attr); switch (ka) { case kAttribute::Icon: m_icon_name = attr->Value(); break; default: break; } } }; class NodeViewport : public Node { public: std::unique_ptr m_faces; std::unique_ptr m_sampler; uint16_t m_tex_id; glm::vec2 drag_start; glm::vec2 drag_end; bool dragging = false; float angle = 0.0f; float angle_old; virtual void draw() override { using namespace ui; glm::mat4 cam = glm::lookAt(glm::vec3(sinf(angle) * 10, 0, -10), glm::vec3(0, 0, 0), glm::vec3(0, -1, 0)); glm::mat4 proj = glm::perspective(glm::radians(45.f), m_clip.z / m_clip.w, .1f, 100); GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); glClearColor(1, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); auto box = m_clip * root()->m_zoom; auto c = glm::vec4(box.x, (int)(vp[3] - box.y - box.w), box.z, box.w); glViewport(c.x, c.y, c.z, c.w); TextureManager::get(m_tex_id).bind(); m_sampler->bind(0); glEnable(GL_BLEND); ui::ShaderManager::use(kShader::Texture); ui::ShaderManager::u_int(kShaderUniform::Tex, 0); ui::ShaderManager::u_mat4(kShaderUniform::MVP, proj * cam); m_faces->draw_fill(); m_sampler->unbind(); TextureManager::get(m_tex_id).unbind(); glDisable(GL_BLEND); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); } virtual Node* clone_instantiate() const override { return new NodeViewport; } virtual void create() override { m_faces = std::make_unique(); m_faces->create<1>(10, 10); m_sampler = std::make_unique(); m_sampler->create(); TextureManager::load("data/uvs.jpg"); m_tex_id = const_hash("data/uvs.jpg"); } virtual kEventResult handle_event(Event* e) override { Node::handle_event(e); switch (e->m_type) { case kEventType::MouseDownL: dragging = true; drag_end = drag_start = ((MouseEvent*)e)->m_pos; angle_old = angle; break; case kEventType::MouseUpL: dragging = false; break; case kEventType::MouseMove: if (dragging) { drag_end = ((MouseEvent*)e)->m_pos; angle = angle_old + (drag_end - drag_start).x * .01f; } break; default: break; } return kEventResult::Consumed; } }; class NodeSliderCursor : public NodeButtonCustom { public: glm::vec2 drag_start; glm::vec2 drag_diff; bool dragging = false; glm::vec2 old_pos; virtual Node* clone_instantiate() const override { return new NodeSliderCursor(); } virtual void clone_copy(Node* dest) const override { NodeButtonCustom::clone_copy(dest); NodeSliderCursor* n = static_cast(dest); } virtual kEventResult handle_event(Event* e) override { NodeBorder::handle_event(e); switch (e->m_type) { case kEventType::MouseDownL: old_pos = GetPosition(); drag_start = ((MouseEvent*)e)->m_pos; dragging = true; mouse_capture(); break; case kEventType::MouseUpL: mouse_release(); dragging = false; break; case kEventType::MouseMove: if (dragging) { float pw = parent->GetWidth(); float w = GetWidth(); drag_diff = old_pos + ((MouseEvent*)e)->m_pos - drag_start; float x = glm::clamp(drag_diff.x, 0, pw - w); SetPosition(x, 0); } break; default: break; } return kEventResult::Consumed; } }; class NodeSlider : public NodeBorder { public: NodeSliderCursor* m_cursor; virtual Node* clone_instantiate() const override { return new NodeSlider(); } virtual void init() override { const auto& m_template = (NodeBorder*)init_template("tpl-slider"); m_color = m_template->m_color; m_border_color = m_template->m_border_color; m_thinkness = m_thinkness; m_cursor = find("cursor"); } }; class NodeCheckBox : public Node { public: NodeBorder* m_outer; NodeBorder* m_inner; bool checked = false; virtual Node* clone_instantiate() const override { return new NodeCheckBox(); } virtual void clone_children(Node* dest) const override { Node::clone_children(dest); NodeCheckBox* n = static_cast(dest); n->m_outer = (NodeBorder*)n->m_children[0].get(); n->m_inner = (NodeBorder*)n->m_outer->m_children[0].get(); n->m_mouse_ignore = false; } virtual void init() override { m_outer = new NodeBorder(); m_inner = new NodeBorder(); add_child(m_outer); m_outer->add_child(m_inner); m_outer->init(); m_outer->m_color = { .3, .3, .3, 1 }; m_outer->SetAlign(YGAlignCenter); m_outer->SetJustify(YGJustifyCenter); m_outer->SetPadding(5, 5, 5, 5); m_outer->SetWidthP(100); m_outer->SetHeightP(100); m_outer->m_mouse_ignore = false; m_inner->init(); m_inner->SetWidthP(100); m_inner->SetHeightP(100); m_inner->m_border_color = glm::vec4(.8, .8, .8, 1); m_inner->m_thinkness = 1; m_inner->m_color = glm::vec4(.8, .8, .8, 1); m_mouse_ignore = false; } virtual void create() override { m_outer->create(); m_inner->create(); } virtual kEventResult handle_event(Event* e) override { Node::handle_event(e); switch (e->m_type) { case kEventType::MouseEnter: break; case kEventType::MouseLeave: break; case kEventType::MouseDownL: break; case kEventType::MouseUpL: checked = !checked; break; default: break; } return kEventResult::Consumed; } virtual void draw() override { m_inner->m_color = checked ? glm::vec4(.4, .4, .4, 1) : glm::vec4(.8, .8, .8, 1); Node::draw(); } }; class NodeLayer : public NodeBorder { public: std::function on_selected; bool m_selected = false; glm::vec4 m_color_normal = glm::vec4(.4, .4, .4, 1); glm::vec4 m_color_selected = glm::vec4(.3, .3, .3, 1); glm::vec4 m_color_hover = glm::vec4(.5, .5, .5, 1); std::string m_label_text; NodeText* m_label; NodeCheckBox* m_visibility; virtual Node* clone_instantiate() const override { return new NodeLayer(); } virtual void clone_children(Node* dest) const override { NodeBorder::clone_children(dest); NodeLayer* n = static_cast(dest); n->m_label = n->find("label"); n->m_visibility = n->find("cb"); } virtual void clone_copy(Node* dest) const override { NodeBorder::clone_copy(dest); NodeLayer* n = (NodeLayer*)dest; n->m_selected = m_selected; n->m_label_text = m_label_text; } virtual void init() override { const auto& m_template = (NodeBorder*)init_template("tpl-layer"); m_color = m_template->m_color; m_border_color = m_template->m_border_color; m_thinkness = m_template->m_thinkness; m_label = find("label"); m_visibility = find("cb"); } virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override { NodeBorder::parse_attributes(ka, attr); switch (ka) { case kAttribute::Text: m_label_text = attr->Value(); break; case kAttribute::Selected: m_selected = attr->BoolValue(); default: break; } } virtual void loaded() override { NodeBorder::loaded(); if (!m_label_text.empty()) m_label->set_text(m_label_text.c_str()); } virtual kEventResult handle_event(Event* e) override { NodeBorder::handle_event(e); switch (e->m_type) { case kEventType::MouseEnter: break; case kEventType::MouseLeave: break; case kEventType::MouseDownL: m_selected = true; if (on_selected) on_selected(this); break; case kEventType::MouseUpL: break; default: break; } return kEventResult::Consumed; } virtual void draw() override { auto c = m_selected ? m_color_selected : m_color_normal; m_thinkness = m_selected ? 1 : 0; m_color = m_mouse_inside ? m_color_hover : c; NodeBorder::draw(); } void set_name(const char* s) { m_label_text = s; m_label->set_text(s); } }; class NodePanelLayers : public Node { NodeButtonCustom* btn_add; NodeButtonCustom* btn_remove; NodeButtonCustom* btn_up; NodeButtonCustom* btn_down; int id_counter = 0; public: std::function on_layer_change; std::function on_layer_delete; std::function on_layer_add; NodeLayer* m_current_layer = nullptr; std::vector m_layers; NodeBorder* m_layers_container; virtual Node* clone_instantiate() const override { return new NodePanelLayers(); } virtual void init() override { init_template("tpl-panel-layers"); m_layers_container = find("layers-container"); for (int i = 0; i < 1; i++) { add_layer(); } m_current_layer = m_layers[0]; m_layers[0]->m_selected = true; btn_add = find("btn-add"); btn_remove = find("btn-remove"); btn_up = find("btn-up"); btn_down = find("btn-down"); btn_add->on_click = [this](Node*) { add_layer(); }; btn_remove->on_click = [this](Node*) { if (m_layers.size() == 1) return; // dont' delete the last layer remove_layer(m_current_layer); }; btn_up->on_click = [this](Node*) { m_layers_container->move_child_offset(m_current_layer, -1); }; btn_down->on_click = [this](Node*) { m_layers_container->move_child_offset(m_current_layer, +1); }; } void add_layer() { static char s[64]; sprintf(s, "Layer-%d", id_counter++); add_layer(s); } void add_layer(const char* name) { NodeLayer* l = new NodeLayer; m_layers_container->add_child(l); l->init(); l->create(); l->loaded(); l->set_name(name); l->on_selected = std::bind(&NodePanelLayers::handle_layer_selected, this, std::placeholders::_1); m_layers.push_back(l); if (on_layer_add) on_layer_add(this); } void remove_layer(NodeLayer* layer) { auto it = std::find(m_layers.begin(), m_layers.end(), m_current_layer); auto i = m_layers_container->get_child_index(m_current_layer); m_layers_container->remove_child(m_current_layer); m_layers.erase(it); i = std::min(i, m_layers.size() - 1); m_current_layer = m_layers[i]; m_current_layer->m_selected = true; if (on_layer_delete) on_layer_delete(this, std::distance(m_layers.begin(), it)); if (on_layer_change) on_layer_change(this, -1, i); } void handle_layer_selected(NodeLayer* target) { if (m_current_layer) m_current_layer->m_selected = false; m_current_layer = target; m_current_layer->m_selected = true; if (on_layer_change) on_layer_change(this, -1, m_layers_container->get_child_index(m_current_layer)); } }; class NodeButtonBrush : public NodeButtonCustom { public: int m_brushID; bool m_selected = false; NodeImage* img; virtual Node* clone_instantiate() const override { return new NodeButtonBrush(); } virtual void init() override { init_template("tpl-brush-icon"); color_hover = glm::vec4(.7, .7, .7, 1); color_normal = glm::vec4(.3, .3, .3, 1); m_color = color_normal; img = (NodeImage*)m_children[0].get(); } void set_icon(const char* path) { img->m_path = path; img->m_tex_id = const_hash(img->m_path.c_str()); img->create(); } virtual void draw() override { m_color = m_selected ? glm::vec4(1, 0, 0, 1) : color_normal; NodeButtonCustom::draw(); } }; class NodePanelBrushes : public Node { std::vector m_brushes; NodeButtonBrush* m_current = nullptr; Node* m_container; public: std::function on_brush_changed; virtual Node* clone_instantiate() const override { return new NodePanelLayers(); } virtual void init() override { init_template("tpl-panel-brushes"); //m_layers_container = find("layers-container"); static auto icons = FindAllBrushes("data\\Icons\\"); if (m_container = find("brushes")) { int count = 0; for (auto& i : icons) { std::string path = "data\\Icons\\" + i; NodeButtonBrush* brush = new NodeButtonBrush; m_container->add_child(brush); brush->init(); brush->create(); brush->loaded(); brush->set_icon(path.c_str()); brush->m_brushID = count++; m_brushes.push_back(brush); brush->on_click = std::bind(&NodePanelBrushes::handle_click, this, std::placeholders::_1); } } } void handle_click(Node* target) { if (target == m_current) return; if (m_current) m_current->m_selected = false; m_current = (NodeButtonBrush*)target; m_current->m_selected = true; on_brush_changed(this, m_current->m_brushID); } std::vector FindAllBrushes(std::string folder) { std::vector names; std::string search_path = folder + "*.png"; WIN32_FIND_DATAA fd; HANDLE hFind = ::FindFirstFileA(search_path.c_str(), &fd); if (hFind != INVALID_HANDLE_VALUE) { do { // read all (real) files in current folder // , delete '!' read other 2 default folder . and .. if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { names.push_back(fd.cFileName); } } while (::FindNextFileA(hFind, &fd)); ::FindClose(hFind); } return names; } };