#pragma once #include "shape.h" #include "util.h" #include "shader.h" #include "font.h" #include "asset.h" #include "rtt.h" #include "bezier.h" #include "canvas.h" #include "event.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"), Template = const_hash("template"), Value = const_hash("value"), Range = const_hash("range"), }; enum class kWidget : uint16_t { Node = const_hash("node"), Border = const_hash("border"), Shape = const_hash("shape"), Text = const_hash("text"), TextInput = const_hash("text-input"), Image = const_hash("image"), Icon = const_hash("icon"), Button = const_hash("button"), ButtonCustom = const_hash("button-custom"), SliderH = const_hash("slider-h"), SliderV = const_hash("slider-v"), SliderHue = const_hash("slider-hue"), PopupMenu = const_hash("popup-menu"), Viewport = const_hash("viewport"), Ref = const_hash("ref"), CheckBox = const_hash("checkbox"), Layer = const_hash("layer"), PanelLayer = const_hash("panel-layer"), PanelBrush = const_hash("panel-brush"), PanelColor = const_hash("panel-color"), PanelStroke = const_hash("panel-stroke"), ColorQuad = const_hash("color-quad"), StrokePreview = const_hash("stroke-preview"), Canvas = const_hash("canvas"), }; enum class kShapeType : uint16_t { Quad = const_hash("rect"), Poly = const_hash("poly"), RoundRect = const_hash("round-rect"), Slice9 = const_hash("slice9"), }; 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; Node* current_key_capture = nullptr; bool m_mouse_captured = false; bool m_key_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; glm::vec4 m_clip_uncut; 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 SetSize(float w, float h) { SetWidth(w); SetHeight(h); } 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 SetPosition(const glm::vec2 pos) { SetPosition(pos.x, pos.y); } 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; virtual void clone_finalize(Node* dest) const { /* find controllers and stuff */ }; 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 handle_resize(glm::vec2 old_size, glm::vec2 new_size) { }; 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 add_child(std::shared_ptr n); void add_child(std::shared_ptr n, int index); void remove_child(Node* n); void remove_all_children(); 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; } void key_capture() { root()->current_key_capture = this; m_key_captured = true; } void key_release() { root()->current_key_capture = nullptr; m_key_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 > 0.f) { m_color.a < 1.f ? glEnable(GL_BLEND) : glDisable(GL_BLEND); ui::ShaderManager::u_vec4(kShaderUniform::Col, m_color); m_plane.draw_fill(); glDisable(GL_BLEND); } if (m_thinkness > 0 && m_border_color.a > 0.f) { glLineWidth(m_thinkness); ui::ShaderManager::u_vec4(kShaderUniform::Col, m_border_color); m_border_color.a < 1.f ? glEnable(GL_BLEND) : glDisable(GL_BLEND); m_plane.draw_stroke(); 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 NodeTextInput : public NodeBorder { public: NodeText* m_text; std::string m_string; virtual Node* clone_instantiate() const override { return new NodeTextInput(); } virtual void init() override { init_controls(); } void init_controls() { m_text = new NodeText; add_child(m_text); m_text->m_font = "arial"; m_text->m_font_size = 11; m_text->m_text = "TextInput"; m_text->create(); } virtual kEventResult handle_event(Event* e) override { KeyEvent* ke = (KeyEvent*)e; switch (e->m_type) { case kEventType::MouseDownL: break; case kEventType::MouseUpL: key_capture(); break; case kEventType::KeyDown: // switch (ke->m_key) // { // case VK_BACK: // m_string.erase(m_string.end() - 1); // m_text->set_text(m_string.c_str()); // break; // default: // break; // } break; case kEventType::KeyChar: if (ke->m_char >= 32 && ke->m_char < (32+96)) { m_string += (char)ke->m_key; m_text->set_text(m_string.c_str()); } 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: std::function on_select; 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); m_mouse_ignore = false; } virtual kEventResult handle_event(Event* e) override { switch (e->m_type) { case kEventType::MouseDownL: break; case kEventType::MouseUpL: if (!m_mouse_inside) { mouse_release(); } else { for (int i = 0; i < m_children.size(); i++) { if (m_children[i]->m_mouse_inside) { if (on_select) on_select(this, i); break; } } mouse_release(); } destroy(); 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"); auto 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; glm::ivec4 c = (glm::ivec4)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 NodeSliderH : public NodeBorder { bool dragging = false; public: glm::vec2 m_mask{ 1, 0 }; glm::vec2 m_value; std::function on_value_changed; virtual Node* clone_instantiate() const override { return new NodeSliderH(); } virtual void clone_copy(Node* dest) const override { NodeBorder::clone_copy(dest); NodeSliderH* n = static_cast(dest); n->m_value = m_value; } virtual void init() override { SetPadding(1, 1, 1, 1); SetWidthP(100); SetHeightP(100); m_color = glm::vec4(1); } virtual void draw() override { NodeBorder::draw(); using namespace ui; auto sz = GetSize(); glm::vec2 cur_size = sz * (1.f - m_mask) + m_mask * glm::vec2(10); glm::mat4 scale = glm::scale(glm::vec3(cur_size, 1.f)); glm::mat4 pos = glm::translate(glm::vec3(m_value * m_mask * sz + m_pos + sz * .5f * (1.f - m_mask), 0)); auto mvp = m_proj * pos * scale; ui::ShaderManager::use(kShader::Color); ui::ShaderManager::u_mat4(kShaderUniform::MVP, mvp); ui::ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 }); m_plane.draw_fill(); } void set_value(float value) { m_value = glm::vec2(value) * m_mask; if (on_value_changed) on_value_changed(this, glm::length(m_value)); } float get_value() { return glm::length(m_value); } virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override { NodeBorder::parse_attributes(ka, attr); switch (ka) { case kAttribute::Value: m_value = glm::vec2(attr->FloatValue()); break; default: break; } } virtual kEventResult handle_event(Event* e) override { NodeBorder::handle_event(e); switch (e->m_type) { case kEventType::MouseDownL: dragging = true; mouse_capture(); { auto sz = GetSize(); auto pos = glm::clamp(((MouseEvent*)e)->m_pos - m_pos, { 0, 0 }, sz) * m_mask; m_value = pos / glm::max({ 1, 1 }, sz); if (on_value_changed) on_value_changed(this, glm::length(m_value)); } break; case kEventType::MouseUpL: mouse_release(); dragging = false; break; case kEventType::MouseMove: if (dragging) { auto sz = GetSize(); auto pos = glm::clamp(((MouseEvent*)e)->m_pos - m_pos, { 0, 0 }, sz) * m_mask; m_value = pos / glm::max({ 1, 1 }, sz); if (on_value_changed) on_value_changed(this, glm::length(m_value)); } break; default: break; } return kEventResult::Consumed; } }; class NodeSliderV : public NodeSliderH { public: virtual Node* clone_instantiate() const override { return new NodeSliderV(); } NodeSliderV() { m_mask = { 0, 1 }; } }; class NodeSliderHue : public NodeSliderV { public: glm::vec4 m_color; std::function on_hue_changed; virtual Node* clone_instantiate() const override { return new NodeSliderHue(); } virtual void clone_finalize(Node* dest) const override { NodeSliderV::clone_finalize(dest); NodeSliderHue* n = static_cast(dest); n->init_controls(); } virtual void init() override { NodeSliderV::init(); init_controls(); } void init_controls() { on_value_changed = [this](Node*, float value) { m_color = glm::vec4(convert_hsv2rgb({ value, 1, 1 }), 1); if (on_hue_changed) on_hue_changed(this, m_color); }; } glm::vec4 get_hue() { return m_color; } virtual void draw() override { using namespace ui; ui::ShaderManager::use(kShader::ColorHue); ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp); //ui::ShaderManager::u_vec4(kShaderUniform::Col, m_color); m_plane.draw_fill(); NodeBorder::m_color = glm::vec4(0); NodeSliderH::draw(); } }; 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; std::function on_opacity_changed; 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; NodeSliderH* m_opacity; 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"); m_opacity = find("sl-opacity"); } 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()); m_opacity->on_value_changed = [this](Node*, float value) { if (on_opacity_changed) on_opacity_changed(this, value); }; } 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.f : 0.f; 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 NodePanelLayer : 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_opacity_changed; std::function on_layer_delete; std::function on_layer_add; std::function on_layer_order; NodeLayer* m_current_layer = nullptr; std::vector m_layers; NodeBorder* m_layers_container; virtual Node* clone_instantiate() const override { return new NodePanelLayer(); } virtual void init() override { LOG("NodePanelLayer::init"); init_template("tpl-panel-layers"); LOG("template initted"); m_layers_container = find("layers-container"); LOG("template container found"); for (int i = 0; i < 1; i++) { LOG("add layer"); add_layer(); } LOG("find components"); 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"); LOG("attach events"); 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*) { int old_idx = m_layers_container->get_child_index(m_current_layer); m_layers_container->move_child_offset(m_current_layer, -1); int new_idx = m_layers_container->get_child_index(m_current_layer); if (on_layer_order && old_idx != new_idx) { on_layer_order(this, old_idx, new_idx); } }; btn_down->on_click = [this](Node*) { int old_idx = m_layers_container->get_child_index(m_current_layer); m_layers_container->move_child_offset(m_current_layer, +1); int new_idx = m_layers_container->get_child_index(m_current_layer); if (on_layer_order && old_idx != new_idx) { on_layer_order(this, old_idx, new_idx); } }; LOG("done init"); } 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(&NodePanelLayer::handle_layer_selected, this, std::placeholders::_1); l->on_opacity_changed = std::bind(&NodePanelLayer::handle_layer_opacity, this, std::placeholders::_1, std::placeholders::_2); 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, (int)m_layers.size() - 1); m_current_layer = m_layers[i]; m_current_layer->m_selected = true; if (on_layer_delete) on_layer_delete(this, (int)std::distance(m_layers.begin(), it)); if (on_layer_change) on_layer_change(this, -1, i); } void handle_layer_opacity(NodeLayer* target, float value) { if (on_layer_opacity_changed) on_layer_opacity_changed(this, m_layers_container->get_child_index(target), value); } 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_mouse_inside ? color_hover : color_normal; m_color = m_selected ? glm::vec4(.9, 0, 0, 1) : m_color; NodeButtonCustom::draw(); } }; class NodePanelBrush : 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 NodePanelLayer(); } 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(&NodePanelBrush::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; if (on_brush_changed) on_brush_changed(this, m_current->m_brushID); } std::vector FindAllBrushes(std::string folder) { std::vector names; std::string search_path = folder + "*.png"; #ifdef _WIN32 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); } #elif __ANDROID__ //LOG("listing brushes"); AAssetDir* dir = AAssetManager_openDir(Asset::m_am, "data/Icons"); while (const char* name = AAssetDir_getNextFileName(dir)) { //LOG("asset: %s", name); names.push_back(name); } AAssetDir_close(dir); #else DIR *dp; struct dirent *ep; dp = opendir(folder.c_str()); if (dp != NULL) { while ((ep = readdir(dp))) if (ep->d_type != DT_DIR) names.push_back(ep->d_name); closedir(dp); } else LOG("Couldn't open the directory: %s", folder.c_str()); #endif return names; } uint16_t get_texture_id(int index) const { return m_brushes[index]->img->m_tex_id; } }; class NodeColorQuad : public NodeBorder { NodeBorder* m_picker; bool dragging = false; public: glm::vec2 m_value; std::function on_value_changed; virtual Node* clone_instantiate() const override { return new NodeColorQuad(); } virtual void clone_finalize(Node* dest) const override { auto n = (NodeColorQuad*)dest; n->m_picker = (NodeBorder*)n->m_children[0].get(); } virtual void init() override { m_picker = new NodeBorder; m_picker->SetSize({ 20, 20 }); m_picker->SetPositioning(YGPositionTypeAbsolute); m_picker->SetPosition(0, 0); m_picker->m_thinkness = 1; m_picker->m_color = glm::vec4(0); add_child(m_picker); } void set_value(float x, float y) { auto sz = GetSize(); auto pos = glm::clamp(glm::vec2(x, y) * sz, { 0, 0 }, sz); m_picker->SetPosition(pos.x, pos.y); m_value = pos / glm::max({ 1,1 }, sz); // avoid div0 if (on_value_changed) on_value_changed(this, m_value); } virtual kEventResult handle_event(Event* e) override { NodeBorder::handle_event(e); switch (e->m_type) { case kEventType::MouseDownL: { dragging = true; mouse_capture(); auto sz = GetSize(); auto pos = glm::clamp(((MouseEvent*)e)->m_pos - m_pos, { 0, 0 }, sz) - m_picker->GetSize() * .5f; m_picker->SetPosition(pos.x, pos.y); m_value = pos / glm::max({ 1,1 }, sz); // avoid div0 if (on_value_changed) on_value_changed(this, m_value); } break; case kEventType::MouseUpL: mouse_release(); dragging = false; break; case kEventType::MouseMove: if (dragging) { auto sz = GetSize(); auto pos = glm::clamp(((MouseEvent*)e)->m_pos - m_pos, { 0, 0 }, sz) - m_picker->GetSize() * .5f; m_picker->SetPosition(pos.x, pos.y); m_value = pos / glm::max({ 1,1 }, sz); // avoid div0 if (on_value_changed) on_value_changed(this, m_value); } break; default: break; } return kEventResult::Consumed; } virtual void draw() override { m_picker->m_border_color = m_value.y > .5f ? glm::vec4(1) : glm::vec4(0, 0, 0, 1); using namespace ui; ui::ShaderManager::use(kShader::ColorQuad); ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp); ui::ShaderManager::u_vec4(kShaderUniform::Col, m_color); m_plane.draw_fill(); } }; class NodePanelColor : public Node { public: NodeColorQuad* m_quad; NodeSliderHue* m_hue; glm::vec4 m_base_color; glm::vec4 m_color; glm::vec2 m_cursor; std::function on_color_changed; virtual Node* clone_instantiate() const override { return new NodePanelColor(); } virtual void clone_finalize(Node* dest) const override { NodePanelColor* n = static_cast(dest); n->init_controls(); } virtual void init() override { init_template("tpl-panel-color"); init_controls(); } void init_controls() { m_quad = find("quad"); m_hue = find("hue"); m_hue->on_hue_changed = [this](Node*, glm::vec4 hue_color) { m_base_color = m_quad->m_color = hue_color; auto x = glm::mix(m_base_color, glm::vec4(1, 1, 1, 1), m_cursor.x); m_color = glm::mix(x, glm::vec4(0, 0, 0, 1), m_cursor.y); if (on_color_changed) on_color_changed(this, m_color); }; m_quad->on_value_changed = [this](Node*, glm::vec2 pos) { auto x = glm::mix(m_base_color, glm::vec4(1, 1, 1, 1), pos.x); m_color = glm::mix(x, glm::vec4(0, 0, 0, 1), pos.y); m_cursor = pos; if (on_color_changed) on_color_changed(this, m_color); }; m_hue->set_value(0); } }; class NodeStrokePreview : public NodeBorder { RTT m_rtt; Sampler m_sampler; ui::BrushMesh m_mesh; public: ui::Brush m_brush; ui::Stroke m_stroke; std::vector m_bez_points; virtual Node* clone_instantiate() const override { return new NodeStrokePreview(); } virtual void clone_copy(Node* dest) const override { NodeBorder::clone_copy(dest); } virtual void clone_children(Node* dest) const override { // stop children cloning } virtual void clone_finalize(Node* dest) const override { NodeStrokePreview* n = (NodeStrokePreview*)dest; n->init_controls(); } void init_controls() { m_mesh.create(); m_sampler.create(); TextureManager::load("data/Icons/Round-Hard.png"); m_brush.m_tex_id = const_hash("data/Icons/Round-Hard.png"); } void draw_stroke() { m_rtt.bindFramebuffer(); { using namespace ui; GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); glClearColor(1, 1, 1, 1); glClear(GL_COLOR_BUFFER_BIT); glViewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight()); glEnable(GL_BLEND); glm::mat4 proj = glm::ortho(0, (float)m_rtt.getWidth(), 0, (float)m_rtt.getHeight(), -1, 1); m_stroke.reset(); m_stroke.start(m_brush); auto samples = m_stroke.compute_samples(); auto& tex = TextureManager::get(m_brush.m_tex_id); tex.bind(); m_sampler.bind(0); if (true) { m_mesh.shader.use(); m_mesh.shader.u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 }); m_mesh.shader.u_int(kShaderUniform::Tex, 0); m_mesh.draw(samples, proj); } // else // { // ShaderManager::use("stroke"); // ShaderManager::u_vec4(kShaderUniform::Col, m_brush.m_tip_color); // ShaderManager::u_int(kShaderUniform::Tex, 0); // for (const auto& s : samples) // { // auto mvp = proj * // glm::translate(glm::vec3(s.pos, 0)) * // glm::scale(glm::vec3(s.size, s.size, 1)) * // glm::eulerAngleZ(s.angle); // // ShaderManager::u_mat4(kShaderUniform::MVP, mvp); // ShaderManager::u_float(kShaderUniform::Alpha, s.flow); // m_plane.draw_fill(); // } // } m_sampler.unbind(); tex.unbind(); glDisable(GL_BLEND); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); } m_rtt.unbindFramebuffer(); } virtual void draw() override { using namespace ui; ui::ShaderManager::use(kShader::Texture); ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp); ui::ShaderManager::u_int(kShaderUniform::Tex, 0); m_rtt.bindTexture(); m_sampler.bind(0); m_plane.draw_fill(); m_sampler.unbind(); m_rtt.unbindTexture(); } virtual void handle_resize(glm::vec2 old_size, glm::vec2 new_size) override { float pad = 30.f; float w = new_size.x; float h = new_size.y; std::vector kp = { { pad, pad },{ pad, h - pad },{ w - pad, pad },{ w - pad, h - pad } }; m_stroke.start(m_brush); for (int i = 0; i < 20; i++) m_stroke.add_point(BezierCurve::Bezier2D(kp, i / 20.f), 1.f); m_rtt.destroy(); m_rtt.create((int)new_size.x, (int)new_size.y); draw_stroke(); } }; class NodePanelStroke : public Node { public: NodeStrokePreview* m_canvas; NodeSliderH* m_tip_size; NodeSliderH* m_tip_spacing; NodeSliderH* m_tip_flow; NodeSliderH* m_tip_opacity; NodeSliderH* m_tip_angle; NodeSliderH* m_jitter_scale; NodeSliderH* m_jitter_angle; NodeSliderH* m_jitter_spread; NodeSliderH* m_jitter_flow; std::function on_stroke_change; virtual Node* clone_instantiate() const override { return new NodePanelStroke(); } virtual void clone_finalize(Node* dest) const override { NodePanelColor* n = static_cast(dest); n->init_controls(); } virtual void init() override { init_template("tpl-panel-stroke"); init_controls(); } void init_controls() { m_canvas = find("canvas"); init_slider(m_tip_size, "tip-size", &ui::Brush::m_tip_size); init_slider(m_tip_spacing, "tip-spacing", &ui::Brush::m_tip_spacing); init_slider(m_tip_flow, "tip-flow", &ui::Brush::m_tip_flow); init_slider(m_tip_opacity, "tip-opacity", &ui::Brush::m_tip_opacity); init_slider(m_tip_angle, "tip-angle", &ui::Brush::m_tip_angle); init_slider(m_jitter_scale, "jitter-scale", &ui::Brush::m_jitter_scale); init_slider(m_jitter_angle, "jitter-angle", &ui::Brush::m_jitter_angle); init_slider(m_jitter_spread, "jitter-spread", &ui::Brush::m_jitter_spread); init_slider(m_jitter_flow, "jitter-flow", &ui::Brush::m_jitter_flow); //m_canvas->draw_stroke(); } void init_slider(NodeSliderH*& slider, const char* id, float ui::Brush::* prop) { slider = find(id); slider->on_value_changed = std::bind(&NodePanelStroke::handle_slide, this, prop, std::placeholders::_1, std::placeholders::_2); m_canvas->m_brush.*prop = slider->m_value.x; } void handle_slide(float ui::Brush::* prop, Node* target, float value) { m_canvas->m_brush.*prop = value; m_canvas->draw_stroke(); if (on_stroke_change) on_stroke_change(this); } }; class NodeCanvas : public Node { bool m_dragging = false; bool m_draggingR = false; glm::vec2 m_dragR_start; glm::vec2 m_pan_start; glm::vec2 m_pan; float m_zoom_canvas = 1.f; public: std::unique_ptr m_canvas; ui::Brush m_brush; Sampler m_sampler; virtual Node* clone_instantiate() const override { return new NodeCanvas(); } virtual void init() override { m_mouse_ignore = false; m_canvas = std::make_unique(); m_canvas->create(512, 512); m_canvas->layer_add("asd"); m_canvas->clear(); m_sampler.create(); } virtual void draw() override { using namespace ui; GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); float zoom = root()->m_zoom; auto box = m_clip * zoom; glm::ivec4 c = (glm::ivec4)glm::vec4(box.x, (int)(vp[3] - box.y - box.w), box.z, box.w); glViewport(c.x, c.y, c.z, c.w); glm::vec2 sz = { m_canvas->m_width, m_canvas->m_height }; m_canvas->m_mvp = glm::ortho(0.f, box.z, 0.f, box.w, -1.f, 1.f) * glm::translate(glm::vec3(m_pan + m_size * 0.5f * zoom, 0)) * // pan glm::scale(glm::vec3(zoom * m_zoom_canvas, zoom * m_zoom_canvas, 1)) * glm::translate(glm::vec3(-sz/2.f, 0)); auto plane_mvp = glm::ortho(0.f, box.z, 0.f, box.w, -1.f, 1.f) * glm::translate(glm::vec3(m_pan + m_size * 0.5f * zoom, 0)) * // pan glm::scale(glm::vec3(sz * zoom * m_zoom_canvas, 1)); m_sampler.bind(0); ui::ShaderManager::use(kShader::TextureAlpha); ui::ShaderManager::u_int(kShaderUniform::Tex, 0); ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp); auto blend = glIsEnabled(GL_BLEND); glEnable(GL_BLEND); for (auto i : m_canvas->m_order) { if (!(m_canvas->m_erase && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == i)) { ui::ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_layers[i].m_opacity); m_canvas->m_layers[i].m_rtt.bindTexture(); NodeBorder::m_plane.draw_fill(); m_canvas->m_layers[i].m_rtt.unbindTexture(); } if (m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == i) { glEnable(GL_BLEND); ui::ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_current_stroke->m_brush.m_tip_opacity); m_canvas->m_tmp.bindTexture(); NodeBorder::m_plane.draw_fill(); m_canvas->m_tmp.unbindTexture(); } } blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND); m_sampler.unbind(); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); } virtual void handle_resize(glm::vec2 old_size, glm::vec2 new_size) override { if (new_size.x > m_canvas->m_width) { m_canvas->resize((int)new_size.x, (int)new_size.y); m_canvas->clear(); } } virtual kEventResult handle_event(Event* e) override { Node::handle_event(e); MouseEvent* me = static_cast(e); KeyEvent* ke = static_cast(e); auto loc = me->m_pos - m_pos; auto clip_space = glm::vec2(loc.x, m_size.y - loc.y - 1.f) / m_size * 2.f - 1.f; auto fb_space = glm::inverse(m_canvas->m_mvp) * glm::vec4(clip_space, 0, 1); auto cur = fb_space.xy(); switch (e->m_type) { case kEventType::MouseDownL: { m_canvas->stroke_start(cur, 1.f, m_brush); m_dragging = true; mouse_capture(); break; } case kEventType::MouseUpL: m_canvas->stroke_end(); m_dragging = false; mouse_release(); break; case kEventType::MouseDownR: m_draggingR = true; m_dragR_start = me->m_pos; m_pan_start = m_pan; mouse_capture(); break; case kEventType::MouseUpR: m_draggingR = false; mouse_release(); break; case kEventType::MouseMove: if (m_dragging) m_canvas->stroke_update(cur, 1.f); if (m_draggingR) m_pan = m_pan_start + (me->m_pos - m_dragR_start) * glm::vec2(1, -1); break; case kEventType::MouseScroll: m_zoom_canvas += me->m_scroll_delta * 0.1f; break; case kEventType::KeyDown: if (ke->m_key == kKey::KeyE) m_canvas->m_erase = true; if (ke->m_key == kKey::AndroidVolumeUp) m_zoom_canvas *= 0.9f; if (ke->m_key == kKey::AndroidVolumeDown) m_zoom_canvas *= 1.1f; break; case kEventType::KeyUp: if (ke->m_key == kKey::KeyE) m_canvas->m_erase = false; default: break; } return kEventResult::Consumed; } };