diff --git a/data/dialogs/panel-animation.xml b/data/dialogs/panel-animation.xml
index e91567b..1b71df1 100644
--- a/data/dialogs/panel-animation.xml
+++ b/data/dialogs/panel-animation.xml
@@ -5,26 +5,50 @@
>
-
-
-
+
+
+
+
-
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/dialogs/panel-floating.xml b/data/dialogs/panel-floating.xml
index 997fa62..fd6ed0f 100644
--- a/data/dialogs/panel-floating.xml
+++ b/data/dialogs/panel-floating.xml
@@ -5,7 +5,7 @@
>
-
+
diff --git a/src/app_layout.cpp b/src/app_layout.cpp
index 55ca796..3686d21 100644
--- a/src/app_layout.cpp
+++ b/src/app_layout.cpp
@@ -171,6 +171,7 @@ void App::init_sidebar()
layers->on_layer_add = [this](Node*, std::shared_ptr layer, int index) {
canvas->m_canvas->layer_add(layers->m_layers.back()->m_label_text.c_str(), layer, index);
canvas->m_canvas->m_unsaved = true;
+ animation->load_layers();
title_update();
};
@@ -190,22 +191,26 @@ void App::init_sidebar()
dst->m_alpha_locked = src->m_alpha_locked;
}
Canvas::I->m_unsaved = true;
+ animation->load_layers();
title_update();
};
layers->on_layer_change = [this](Node*, int old_idx, int new_idx) {
canvas->m_canvas->m_current_layer_idx = new_idx;
+ animation->load_layers();
};
layers->on_layer_order = [this](Node*, int old_idx, int new_idx) {
canvas->m_canvas->layer_order(old_idx, new_idx);
canvas->m_canvas->m_unsaved = true;
+ animation->load_layers();
title_update();
};
layers->on_layer_delete = [this](Node*, int idx) {
canvas->m_canvas->layer_remove(idx);
canvas->m_canvas->m_unsaved = true;
+ animation->load_layers();
title_update();
};
@@ -218,6 +223,7 @@ void App::init_sidebar()
layers->on_layer_visibility_changed = [this](Node*, int idx, bool visible) {
canvas->m_canvas->m_layers[idx]->m_visible = visible;
canvas->m_canvas->m_unsaved = true;
+ animation->load_layers();
title_update();
};
@@ -1437,7 +1443,10 @@ void App::initLayout()
LOG("initializing layout designer xml");
layout_designer.on_loaded = [&](bool reloaded) {
layout_designer.create();
- layout_designer[main_id]->add_child(layout_designer.instantiate("tpl-panel-animation"));
+ //layout_designer[main_id]->add_child(layout_designer.instantiate("tpl-panel-animation"));
+ auto p = layout_designer[main_id]->add_child();
+ p->SetPosition(300, 300);
+ p->m_container->add_child();
};
//layout_designer.load("data/dialogs/panel-animation.xml");
}
@@ -1615,6 +1624,14 @@ void App::ui_restore()
grid->SetWidthP(100);
grid->SetHeightP(100);
break;
+ case NodePanelFloating::kClass::Animation:
+ f->m_container->add_child(animation);
+ //grid->find("title")->SetVisibility(false);
+ animation->SetPositioning(YGPositionTypeRelative);
+ animation->SetPosition(0, 0);
+ animation->SetWidthP(100);
+ animation->SetHeightP(100);
+ break;
case NodePanelFloating::kClass::Generic:
default:
f->m_container->add_child();
diff --git a/src/canvas.cpp b/src/canvas.cpp
index 95a5e64..c45c1e4 100644
--- a/src/canvas.cpp
+++ b/src/canvas.cpp
@@ -1252,6 +1252,16 @@ void Canvas::layer_add(std::string name, std::shared_ptr layer /*= nullpt
}
m_current_layer_idx = index;
}
+
+std::shared_ptr Canvas::layer_with_id(uint32_t id)
+{
+ auto layer = std::find_if(m_layers.begin(), m_layers.end(),
+ [id](const auto& l) { return l->id == id; });
+ if (layer != m_layers.end())
+ return *layer;
+ return nullptr;
+}
+
void Canvas::layer_remove(int idx) // m_order index
{
LOG("canvas layer_remove %d", idx);
@@ -1324,6 +1334,21 @@ void Canvas::layer_merge(int source_idx, int dest_idx) // m_layer index
});
}
+int Canvas::anim_duration() const noexcept
+{
+ int frames = 1;
+ for (auto& l : m_layers)
+ frames = glm::max(frames, l->total_duration());
+ return frames;
+}
+
+void Canvas::anim_goto_frame(int frame) noexcept
+{
+ m_anim_frame = frame;
+ for (auto& l : m_layers)
+ l->goto_frame(frame);
+}
+
void Canvas::flood_fill(int layer, int plane, std::vector pos, FloodData& plane_data,
float threshold, glm::vec4 dest_color, std::unique_ptr& source_color)
{
diff --git a/src/canvas.h b/src/canvas.h
index f382a34..0f6cb37 100644
--- a/src/canvas.h
+++ b/src/canvas.h
@@ -108,6 +108,7 @@ public:
std::unique_ptr m_dual_stroke;
bool m_show_tmp = false;
std::vector> m_layers;
+ int m_anim_frame = 1;
Layer m_layers_merge;
std::vector m_plane_shape[6]; // screen space projection of the plane
glm::mat4 m_plane_unproject[6] = SIXPLETTE(glm::mat4(1));
@@ -176,10 +177,13 @@ public:
bool create(int width, int height);
void resize(int width, int height);
Layer& layer() { return *m_layers[m_current_layer_idx]; }
+ std::shared_ptr layer_with_id(uint32_t id);
void layer_remove(int idx);
void layer_add(std::string name, std::shared_ptr layer = nullptr, int index = 0);
void layer_order(int idx, int pos);
void layer_merge(int source_idx, int dest_idx);
+ int anim_duration() const noexcept;
+ void anim_goto_frame(int frame) noexcept;
void flood_fill(int layer, int plane, std::vector pos, FloodData& plane_data,
float threshold, glm::vec4 dest_color, std::unique_ptr& source_color);
void stroke_start(glm::vec3 point, float pressure);
diff --git a/src/canvas_layer.cpp b/src/canvas_layer.cpp
index 1c48574..649f655 100644
--- a/src/canvas_layer.cpp
+++ b/src/canvas_layer.cpp
@@ -227,8 +227,23 @@ bool Layer::create(int width, int height, std::string name)
bool Layer::add_frame()
{
m_frames.emplace_back();
- m_frame_index = m_frames.size() - 1;
- return frame().create(w, h);
+ return m_frames.back().create(w, h);
+}
+
+int Layer::total_duration() const noexcept
+{
+ int duration = 0;
+ for (auto& f : m_frames)
+ duration += f.m_duration;
+ return duration;
+}
+
+void Layer::goto_frame(int frame) noexcept
+{
+ int i = 0;
+ for (i = 0; i < m_frames.size() && frame >= 0; i++)
+ frame -= m_frames[i].m_duration;
+ m_frame_index = i - 1;
}
void Layer::resize(int width, int height)
diff --git a/src/canvas_layer.h b/src/canvas_layer.h
index bebaa76..59173a1 100644
--- a/src/canvas_layer.h
+++ b/src/canvas_layer.h
@@ -61,6 +61,8 @@ public:
void resize(int width, int height);
bool create(int width, int height, std::string name);
bool add_frame();
+ int total_duration() const noexcept;
+ void goto_frame(int frame) noexcept;
void clear(const glm::vec4& c);
Snapshot snapshot(std::array* dirty_box = nullptr, std::array* dirty_face = nullptr);
TextureCube gen_cube();
diff --git a/src/node.cpp b/src/node.cpp
index fdfc7a4..2dfe695 100644
--- a/src/node.cpp
+++ b/src/node.cpp
@@ -36,6 +36,7 @@
#include "node_usermanual.h"
#include "node_panel_quick.h"
#include "node_tool_bucket.h"
+#include "node_panel_animation.h"
void Node::app_redraw()
{
@@ -1390,6 +1391,7 @@ void Node::load_internal(const tinyxml2::XMLElement* x_node)
CASE(kWidget::Changelog, NodeChangelog);
CASE(kWidget::UserManual, NodeUserManual);
CASE(kWidget::ToolBucket, NodeToolBucket);
+ CASE(kWidget::Timeline, NodeAnimationTimeline);
#undef CASE
case kWidget::Ref:
{
diff --git a/src/node.h b/src/node.h
index 4cc77b0..70e68d5 100644
--- a/src/node.h
+++ b/src/node.h
@@ -95,6 +95,7 @@ enum class kWidget : uint16_t
Changelog = const_hash("changelog"),
UserManual = const_hash("usermanual"),
ToolBucket = const_hash("tool-bucket"),
+ Timeline = const_hash("timeline"),
};
class Node : public std::enable_shared_from_this
diff --git a/src/node_panel_animation.cpp b/src/node_panel_animation.cpp
index ee01711..1cce12c 100644
--- a/src/node_panel_animation.cpp
+++ b/src/node_panel_animation.cpp
@@ -1,5 +1,8 @@
#include "pch.h"
#include "node_panel_animation.h"
+#include "node_button.h"
+#include "node_button_custom.h"
+#include "canvas.h"
Node* NodePanelAnimation::clone_instantiate() const
{
@@ -22,4 +25,197 @@ void NodePanelAnimation::init()
void NodePanelAnimation::init_controls()
{
+ m_container = find("container");
+ m_timeline = find("timeline");
+ btn_add = find("btn-add");
+ btn_remove = find("btn-remove");
+ btn_up = find("btn-up");
+ btn_down = find("btn-down");
+ btn_duplicate = find("btn-duplicate");
+
+ btn_add->on_click = [this](Node*) {
+ Canvas::I->layer().add_frame();
+ load_layers();
+ };
+ btn_up->on_click = [this](Node*) {
+ auto& layers = Canvas::I->m_layers;
+ auto layer = std::find_if(layers.begin(), layers.end(),
+ [id = m_selected_frame_layer_id](const auto& l) { return l->id == id; });
+ if (layer != layers.end())
+ (*layer)->m_frames[m_selected_frame_index].m_duration++;
+ };
+ btn_up->on_click = [this](Node*) {
+ if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
+ layer->m_frames[m_selected_frame_index].m_duration++;
+ load_layers();
+ };
+ btn_down->on_click = [this](Node*) {
+ if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
+ layer->m_frames[m_selected_frame_index].m_duration--;
+ load_layers();
+ };
+
+ m_timeline->on_frame_changed = [this] (NodeAnimationTimeline* target, int frame) {
+ LOG("goto frame %d", frame);
+ Canvas::I->anim_goto_frame(frame);
+ load_layers();
+ };
+}
+
+void NodePanelAnimation::load_layers()
+{
+ if (!added_to_root())
+ return;
+ m_container->remove_all_children();
+ auto& layers = Canvas::I->m_layers;
+ m_selected_frame = nullptr;
+ for (int i = 0; i < layers.size(); i++)
+ {
+ auto l = m_container->add_child();
+ l->set_text(layers[i]->m_name);
+ l->set_selected(Canvas::I->m_current_layer_idx == i);
+ l->set_chekcbox(layers[i]->m_visible);
+ for (int fi = 0; fi < layers[i]->m_frames.size(); fi++)
+ {
+ auto b = l->add_frame(layers[i]->m_frames[fi].m_duration);
+ if (m_selected_frame_layer_id == layers[i]->id && m_selected_frame_index == fi)
+ {
+ b->set_active(true);
+ m_selected_frame = b.get();
+ }
+ b->on_click = [this, fi, lid=layers[i]->id] (Node* target) {
+ auto frame = static_cast(target);
+ frame->set_active(true);
+ if (m_selected_frame)
+ m_selected_frame->set_active(false);
+ m_selected_frame = frame;
+ m_selected_frame_layer_id = lid;
+ m_selected_frame_index = fi;
+ };
+ }
+ }
+}
+
+void NodePanelAnimation::added(Node* parent)
+{
+ parent::added(parent);
+ load_layers();
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+Node* NodeAnimationLayer::clone_instantiate() const
+{
+ return new this_class;
+}
+
+void NodeAnimationLayer::clone_finalize(Node* dest) const
+{
+ parent::clone_finalize(dest);
+ auto n = static_cast(dest);
+ n->init_controls();
+}
+
+void NodeAnimationLayer::init()
+{
+ parent::init();
+ init_template_file("data/dialogs/panel-animation.xml", "tpl-layer");
+ init_controls();
+}
+
+void NodeAnimationLayer::init_controls()
+{
+ m_label = find("label");
+ m_visibility = find("cb");
+ m_container = find("container");
+}
+
+void NodeAnimationLayer::draw()
+{
+ 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;
+ parent::draw();
+}
+
+std::shared_ptr NodeAnimationLayer::add_frame(int duration)
+{
+ auto b = m_container->add_child_ref();
+ b->SetWidth(30 * duration + 5 * (duration - 1));
+ //b->SetFlexGrow(duration);
+ b->SetHeightP(100);
+ b->SetMargin(0, 5, 0, 0);
+ return b;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+void NodeAnimationTimeline::draw()
+{
+ parent::draw();
+ ShaderManager::use(kShader::Color);
+ ShaderManager::u_vec4(kShaderUniform::Col, m_cursor_color);
+ glDisable(GL_BLEND);
+
+ float step = 35.f;
+ glm::vec2 cur_pos = {
+ m_pos.x + step * m_frame + step * 0.5f,
+ m_pos.y + m_size.y * 0.5f
+ };
+ ShaderManager::u_mat4(kShaderUniform::MVP, m_proj
+ * glm::translate(glm::vec3(cur_pos, 0))
+ * glm::scale(glm::vec3(step * 0.25f, m_size.y * 0.5f, 1))
+ );
+ m_plane.draw_fill();
+
+ //bool scissor = glIsEnabled(GL_SCISSOR_TEST);
+ //glDisable(GL_SCISSOR_TEST);
+ ShaderManager::u_mat4(kShaderUniform::MVP, m_proj
+ * glm::translate(glm::vec3(cur_pos, 0))
+ * glm::scale(glm::vec3(step * 0.15f, m_size.y * 0.5f, 1))
+ * glm::translate(glm::vec3(0, .5, 0))
+ );
+ m_plane.draw_fill();
+ //scissor ? glEnable(GL_SCISSOR_TEST) : glDisable(GL_SCISSOR_TEST);
+}
+
+kEventResult NodeAnimationTimeline::handle_event(Event* e)
+{
+ parent::handle_event(e);
+ static int signaled_frame = -1;
+ auto me = static_cast(e);
+ auto ge = static_cast(e);
+ auto update = [&](){
+ auto loc = me->m_pos - m_pos;
+ m_frame = glm::clamp((int)glm::floor(loc.x / 35.f), 0, Canvas::I->anim_duration() - 1);
+ if (on_frame_changed && signaled_frame != m_frame)
+ on_frame_changed(this, m_frame);
+ signaled_frame = m_frame;
+ };
+ switch (e->m_type)
+ {
+ case kEventType::MouseDownL:
+ mouse_capture();
+ m_dragging = true;
+ m_drag_start_frame = m_frame;
+ update();
+ break;
+ case kEventType::MouseMove:
+ if (m_dragging)
+ update();
+ break;
+ case kEventType::MouseUpL:
+ m_dragging = false;
+ mouse_release();
+ break;
+ case kEventType::MouseCancel:
+ m_dragging = false;
+ m_frame = m_drag_start_frame;
+ mouse_release();
+ break;
+ default:
+ return kEventResult::Available;
+ break;
+ }
+ return kEventResult::Consumed;
}
diff --git a/src/node_panel_animation.h b/src/node_panel_animation.h
index b45b99b..931ae41 100644
--- a/src/node_panel_animation.h
+++ b/src/node_panel_animation.h
@@ -1,12 +1,98 @@
-#include "node_border.h"
+#pragma once
-class NodePanelAnimation : public NodeBorder
+#include "node_border.h"
+#include "node_scroll.h"
+#include "node_text.h"
+#include "node_checkbox.h"
+#include "node_button_custom.h"
+
+class NodeAnimationFrame : public NodeButtonCustom
{
public:
- using this_class = NodePanelAnimation;
+ std::function on_selected;
+
+ using this_class = NodeAnimationFrame;
+ using parent = NodeButtonCustom;
+
+ virtual Node* clone_instantiate() const override { return new this_class; }
+};
+
+//////////////////////////////////////////////////////////////////////////
+
+class NodeAnimationTimeline : public NodeBorder
+{
+ bool m_dragging = false;
+ glm::vec2 m_drag_start_pos = { 0, 0 };
+ int m_drag_start_frame = 0;
+public:
+ using this_class = NodeAnimationTimeline;
using parent = NodeBorder;
+
+ std::function on_frame_changed;
+
+ int m_frames_count = 1;
+ int m_frame = 0;
+ glm::vec4 m_cursor_color = { 1, 0, 0, 1 };
+
+ virtual Node* clone_instantiate() const override { return new this_class; }
+ virtual void draw() override;
+ virtual kEventResult handle_event(Event* e) override;
+};
+
+//////////////////////////////////////////////////////////////////////////
+
+class NodePanelAnimation : public Node
+{
+ NodeButtonCustom* btn_add = nullptr;
+ NodeButtonCustom* btn_remove = nullptr;
+ NodeButtonCustom* btn_up = nullptr;
+ NodeButtonCustom* btn_down = nullptr;
+ NodeButtonCustom* btn_duplicate = nullptr;
+ NodeScroll* m_container = nullptr;
+ NodeAnimationFrame* m_selected_frame = nullptr;
+ NodeAnimationTimeline* m_timeline = nullptr;
+ int m_selected_frame_index = -1;
+ uint32_t m_selected_frame_layer_id = 0;
+public:
+ using this_class = NodePanelAnimation;
+ using parent = Node;
+
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
+ virtual void added(Node* parent) override;
+
void init_controls();
+ void load_layers();
};
+
+//////////////////////////////////////////////////////////////////////////
+
+class NodeAnimationLayer : public NodeBorder
+{
+ 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);
+ NodeText* m_label = nullptr;
+ NodeCheckBox* m_visibility = nullptr;
+ NodeScroll* m_container = nullptr;
+public:
+ std::function on_selected;
+ std::function on_visibility_changed;
+ std::function on_highlight;
+
+ using this_class = NodeAnimationLayer;
+ using parent = NodeBorder;
+
+ virtual Node* clone_instantiate() const override;
+ virtual void clone_finalize(Node* dest) const override;
+ virtual void init() override;
+ virtual void draw() override;
+
+ void init_controls();
+ std::shared_ptr add_frame(int duration);
+ void set_text(const std::string& text) { m_label->set_text(text.c_str()); }
+ void set_selected(bool selected) { m_selected = selected; }
+ void set_chekcbox(bool checked) { m_visibility->set_value(checked); }
+};
\ No newline at end of file
diff --git a/src/node_panel_floating.cpp b/src/node_panel_floating.cpp
index fd489ed..59ad652 100644
--- a/src/node_panel_floating.cpp
+++ b/src/node_panel_floating.cpp
@@ -114,7 +114,9 @@ kEventResult NodePanelFloating::handle_event(Event* e)
m_outline->SetPosition(m_parent->m_pos + m_drag_start_pos + me->m_pos - m_drag_start_cur);
m_outline->SetSize(GetSize());
- auto nodes = root()->find("ui-root")->get_children_at_point(me->m_pos);
+ std::vector> nodes;
+ if (auto uir = root()->find("ui-root"))
+ nodes = uir->get_children_at_point(me->m_pos);
bool docked = false;
for (auto const& c : nodes)
{
@@ -181,7 +183,9 @@ kEventResult NodePanelFloating::handle_event(Event* e)
m_drop_placeholder->destroy();
m_drop_placeholder->remove_from_parent();
}
- auto nodes = root()->find("ui-root")->get_children_at_point(me->m_pos);
+ std::vector> nodes;
+ if (auto uir = root()->find("ui-root"))
+ nodes = uir->get_children_at_point(me->m_pos);
bool docked = false;
auto ref = m_parent->m_children[m_parent->get_child_index(this)];
for (auto const& c : nodes)
diff --git a/src/node_panel_layer.cpp b/src/node_panel_layer.cpp
index 2d75eac..d37a06a 100644
--- a/src/node_panel_layer.cpp
+++ b/src/node_panel_layer.cpp
@@ -101,6 +101,9 @@ void NodeLayer::set_name(const char* s)
m_label_text = s;
m_label->set_text(s);
}
+
+//////////////////////////////////////////////////////////////////////////
+
Node* NodePanelLayer::clone_instantiate() const
{
return new NodePanelLayer();
diff --git a/src/pch.h b/src/pch.h
index 8cf074d..70d0a33 100644
--- a/src/pch.h
+++ b/src/pch.h
@@ -40,6 +40,7 @@
#include
#include
#define BT_SetTerminate void
+ #define WITH_CURL 1
#elif __ANDROID__
@@ -58,6 +59,7 @@
#define __block
//#define STBI_NEON
#define BT_SetTerminate void
+ #define WITH_CURL 1
#elif _WIN32
@@ -83,6 +85,7 @@
#define SHADER_VERSION "#version 150\n"
#define PP_OS "win"
#define __block
+ #define WITH_CURL 1
#elif __linux__
@@ -102,6 +105,7 @@
#define __GL__ 1
#define __block
#define BT_SetTerminate void
+ #define WITH_CURL 1
#elif defined(EMSCRIPTEN)