515 lines
16 KiB
C++
515 lines
16 KiB
C++
#include "pch.h"
|
|
#include "node_panel_animation.h"
|
|
#include "app_core/document_animation.h"
|
|
#include "node_button.h"
|
|
#include "node_button_custom.h"
|
|
#include "renderer_gl/opengl_capabilities.h"
|
|
#include "canvas.h"
|
|
#include "app.h"
|
|
|
|
Node* NodePanelAnimation::clone_instantiate() const
|
|
{
|
|
return new this_class;
|
|
}
|
|
|
|
void NodePanelAnimation::clone_finalize(Node* dest) const
|
|
{
|
|
parent::clone_finalize(dest);
|
|
auto n = static_cast<this_class*>(dest);
|
|
n->init_controls();
|
|
}
|
|
|
|
void NodePanelAnimation::init()
|
|
{
|
|
parent::init();
|
|
init_template_file("data/dialogs/panel-animation.xml", "tpl-panel-animation");
|
|
init_controls();
|
|
}
|
|
|
|
void NodePanelAnimation::execute_animation_plan(const pp::app::DocumentAnimationOperationPlan& plan, Layer* layer)
|
|
{
|
|
class LegacyAnimationServices final : public pp::app::DocumentAnimationServices {
|
|
public:
|
|
LegacyAnimationServices(NodePanelAnimation& panel, Layer* layer) noexcept
|
|
: panel_(panel)
|
|
, layer_(layer)
|
|
{
|
|
}
|
|
|
|
void add_frame() override
|
|
{
|
|
Canvas::I->layer().add_frame();
|
|
}
|
|
|
|
void duplicate_frame(int selected_frame) override
|
|
{
|
|
if (layer_)
|
|
layer_->duplicate_frame(selected_frame);
|
|
}
|
|
|
|
void remove_frame(int selected_frame, int target_frame) override
|
|
{
|
|
if (!layer_)
|
|
return;
|
|
layer_->remove_frame(selected_frame);
|
|
panel_.m_selected_frame_index = target_frame;
|
|
}
|
|
|
|
void set_frame_duration(int selected_frame, int duration) override
|
|
{
|
|
if (layer_)
|
|
layer_->set_frame_duration(selected_frame, duration);
|
|
}
|
|
|
|
int move_frame(int selected_frame, int move_offset) override
|
|
{
|
|
if (!layer_)
|
|
return selected_frame;
|
|
panel_.m_selected_frame_index = layer_->move_frame_offset(selected_frame, move_offset);
|
|
return panel_.m_selected_frame_index;
|
|
}
|
|
|
|
void select_frame(std::uint32_t layer_id, int layer_index, int selected_frame) override
|
|
{
|
|
panel_.m_selected_frame_layer_id = layer_id;
|
|
panel_.m_selected_frame_index = selected_frame;
|
|
panel_.m_timeline->m_frame = selected_frame;
|
|
}
|
|
|
|
void select_layer(int layer_index) override
|
|
{
|
|
App::I->layers->handle_layer_selected(App::I->layers->get_layer_at(layer_index));
|
|
}
|
|
|
|
void goto_frame(int target_frame) override
|
|
{
|
|
Canvas::I->anim_goto_frame(target_frame);
|
|
}
|
|
|
|
void set_timeline_frame(int target_frame) override
|
|
{
|
|
panel_.m_timeline->m_frame = target_frame;
|
|
}
|
|
|
|
void set_onion_size(int onion_size) override
|
|
{
|
|
panel_.m_timeline->m_onion_size = onion_size;
|
|
}
|
|
|
|
void capture_playback_restore_mode() override
|
|
{
|
|
playback_restore_mode() = Canvas::I->m_current_mode;
|
|
}
|
|
|
|
void enter_playback_camera_mode() override
|
|
{
|
|
Canvas::set_mode(kCanvasMode::Camera);
|
|
}
|
|
|
|
void restore_playback_canvas_mode() override
|
|
{
|
|
Canvas::set_mode(playback_restore_mode());
|
|
}
|
|
|
|
void set_playback_active(bool active) override
|
|
{
|
|
panel_.btn_play->set_active(active);
|
|
}
|
|
|
|
void reset_playback_timer() override
|
|
{
|
|
panel_.m_playback_timer = 0;
|
|
}
|
|
|
|
void set_playback_idle_ms(int idle_ms) override
|
|
{
|
|
App::I->idle_ms = idle_ms;
|
|
}
|
|
|
|
void update_canvas_animation() override
|
|
{
|
|
Canvas::I->anim_update();
|
|
}
|
|
|
|
void update_frame_status() override
|
|
{
|
|
panel_.update_frames();
|
|
}
|
|
|
|
void reload_animation_layers() override
|
|
{
|
|
panel_.load_layers();
|
|
}
|
|
|
|
void mark_unsaved() override
|
|
{
|
|
Canvas::I->m_unsaved = true;
|
|
}
|
|
|
|
private:
|
|
static kCanvasMode& playback_restore_mode()
|
|
{
|
|
static auto mode = Canvas::I->m_current_mode;
|
|
return mode;
|
|
}
|
|
|
|
NodePanelAnimation& panel_;
|
|
Layer* layer_ = nullptr;
|
|
};
|
|
|
|
LegacyAnimationServices services(*this, layer);
|
|
const auto status = pp::app::execute_animation_operation_plan(plan, services);
|
|
if (!status.ok())
|
|
LOG("Animation panel action failed: %s", status.message);
|
|
}
|
|
|
|
pp::app::DocumentAnimationPanelState NodePanelAnimation::animation_panel_state() const
|
|
{
|
|
return pp::app::DocumentAnimationPanelState {
|
|
.total_duration = Canvas::I->anim_duration(),
|
|
.current_frame = Canvas::I->m_anim_frame,
|
|
.playback_active = btn_play->is_active(),
|
|
};
|
|
}
|
|
|
|
void NodePanelAnimation::init_controls()
|
|
{
|
|
m_layers_container = find<NodeScroll>("layers");
|
|
m_frames_container = find<NodeScroll>("frames");
|
|
m_timeline = find<NodeAnimationTimeline>("timeline");
|
|
btn_add = find<NodeButtonCustom>("btn-add");
|
|
btn_remove = find<NodeButtonCustom>("btn-remove");
|
|
btn_up = find<NodeButtonCustom>("btn-up");
|
|
btn_down = find<NodeButtonCustom>("btn-down");
|
|
btn_left = find<NodeButtonCustom>("btn-left");
|
|
btn_right = find<NodeButtonCustom>("btn-right");
|
|
btn_duplicate = find<NodeButtonCustom>("btn-duplicate");
|
|
btn_next = find<NodeButtonCustom>("btn-next");
|
|
btn_prev = find<NodeButtonCustom>("btn-prev");
|
|
btn_play = find<NodeButtonCustom>("btn-play");
|
|
m_fps = find<NodeComboBox>("fps");
|
|
m_onion = find<NodeComboBox>("onion");
|
|
m_frame_label = find<NodeText>("frame-index");
|
|
|
|
btn_add->on_click = [this](Node*) {
|
|
const auto plan = pp::app::plan_animation_add_frame(
|
|
Canvas::I->layer().frames_count(),
|
|
Canvas::I->m_anim_frame);
|
|
if (!plan)
|
|
return;
|
|
execute_animation_plan(plan.value());
|
|
};
|
|
btn_duplicate->on_click = [this](Node*) {
|
|
if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
|
|
{
|
|
const auto plan = pp::app::plan_animation_duplicate_frame(
|
|
layer->frames_count(),
|
|
m_selected_frame_index);
|
|
if (!plan)
|
|
return;
|
|
execute_animation_plan(plan.value(), layer.get());
|
|
}
|
|
};
|
|
btn_remove->on_click = [this](Node*) {
|
|
if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
|
|
{
|
|
const auto plan = pp::app::plan_animation_remove_frame(
|
|
layer->frames_count(),
|
|
m_selected_frame_index);
|
|
if (!plan)
|
|
return;
|
|
execute_animation_plan(plan.value(), layer.get());
|
|
}
|
|
};
|
|
btn_up->on_click = [this](Node*) {
|
|
if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
|
|
{
|
|
const auto plan = pp::app::plan_animation_adjust_duration(
|
|
layer->frames_count(),
|
|
m_selected_frame_index,
|
|
layer->frame_duration(m_selected_frame_index),
|
|
1);
|
|
if (!plan)
|
|
return;
|
|
execute_animation_plan(plan.value(), layer.get());
|
|
}
|
|
};
|
|
btn_down->on_click = [this](Node*) {
|
|
if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
|
|
{
|
|
const auto plan = pp::app::plan_animation_adjust_duration(
|
|
layer->frames_count(),
|
|
m_selected_frame_index,
|
|
layer->frame_duration(m_selected_frame_index),
|
|
-1);
|
|
if (!plan)
|
|
return;
|
|
execute_animation_plan(plan.value(), layer.get());
|
|
}
|
|
};
|
|
btn_left->on_click = [this](Node*) {
|
|
if (!m_selected_frame)
|
|
return;
|
|
if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
|
|
{
|
|
const auto plan = pp::app::plan_animation_move_frame(
|
|
layer->frames_count(),
|
|
m_selected_frame_index,
|
|
-1);
|
|
if (!plan)
|
|
return;
|
|
execute_animation_plan(plan.value(), layer.get());
|
|
}
|
|
};
|
|
btn_right->on_click = [this](Node*) {
|
|
if (!m_selected_frame)
|
|
return;
|
|
if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
|
|
{
|
|
const auto plan = pp::app::plan_animation_move_frame(
|
|
layer->frames_count(),
|
|
m_selected_frame_index,
|
|
1);
|
|
if (!plan)
|
|
return;
|
|
execute_animation_plan(plan.value(), layer.get());
|
|
}
|
|
};
|
|
|
|
m_onion->on_select = [this] (Node* target, int index) {
|
|
const auto plan = pp::app::plan_animation_onion_size(m_onion->get_int());
|
|
if (!plan)
|
|
return;
|
|
execute_animation_plan(plan.value());
|
|
};
|
|
|
|
m_timeline->on_frame_changed = [this] (NodeAnimationTimeline* target, int frame) {
|
|
const auto plan = pp::app::plan_animation_panel_action(
|
|
pp::app::DocumentAnimationPanelAction::goto_frame,
|
|
animation_panel_state(),
|
|
frame);
|
|
if (!plan)
|
|
return;
|
|
LOG("goto frame %d", plan.value().target_frame);
|
|
execute_animation_plan(plan.value());
|
|
};
|
|
|
|
btn_next->on_click = [this] (Node* target) {
|
|
const auto plan = pp::app::plan_animation_panel_action(
|
|
pp::app::DocumentAnimationPanelAction::next_frame,
|
|
animation_panel_state());
|
|
if (!plan)
|
|
return;
|
|
execute_animation_plan(plan.value());
|
|
};
|
|
btn_prev->on_click = [this](Node* target) {
|
|
const auto plan = pp::app::plan_animation_panel_action(
|
|
pp::app::DocumentAnimationPanelAction::previous_frame,
|
|
animation_panel_state());
|
|
if (!plan)
|
|
return;
|
|
execute_animation_plan(plan.value());
|
|
};
|
|
btn_play->on_click = [this] (Node*) {
|
|
const auto plan = pp::app::plan_animation_panel_action(
|
|
pp::app::DocumentAnimationPanelAction::toggle_playback,
|
|
animation_panel_state());
|
|
if (plan)
|
|
execute_animation_plan(plan.value());
|
|
};
|
|
}
|
|
|
|
void NodePanelAnimation::load_layers()
|
|
{
|
|
if (!added_to_root())
|
|
return;
|
|
m_layers_container->remove_all_children();
|
|
m_frames_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_layers_container->add_child<NodeAnimationLayer>();
|
|
l->set_text(layers[i]->m_name);
|
|
l->set_selected(Canvas::I->m_current_layer_idx == i);
|
|
l->set_chekcbox(layers[i]->m_visible);
|
|
auto film = m_frames_container->add_child_ref<NodeAnimationFilm>();
|
|
for (int fi = 0; fi < layers[i]->frames_count(); fi++)
|
|
{
|
|
auto b = film->add_frame(layers[i]->frame_duration(fi));
|
|
|
|
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, i] (Node* target) {
|
|
auto frame = static_cast<NodeAnimationFrame*>(target);
|
|
if (m_selected_frame)
|
|
m_selected_frame->set_active(false);
|
|
frame->set_active(true);
|
|
m_selected_frame = frame;
|
|
const auto plan = pp::app::plan_animation_select_frame(
|
|
Canvas::I->m_layers[i]->frames_count(),
|
|
i,
|
|
lid,
|
|
fi);
|
|
if (plan)
|
|
execute_animation_plan(plan.value(), Canvas::I->m_layers[i].get());
|
|
};
|
|
}
|
|
}
|
|
m_timeline->m_frame = Canvas::I->m_anim_frame;
|
|
m_timeline->m_onion_size = m_onion->get_int();
|
|
update_frames();
|
|
}
|
|
|
|
void NodePanelAnimation::update_frames()
|
|
{
|
|
int total_frames = Canvas::I->anim_duration();
|
|
int digits = (int)floor(glm::log(total_frames));
|
|
m_frame_label->set_text_format("%0*d/%d", digits, m_timeline->m_frame + 1, total_frames);
|
|
}
|
|
|
|
void NodePanelAnimation::on_tick(float dt)
|
|
{
|
|
parent::on_tick(dt);
|
|
if (btn_play->is_active())
|
|
{
|
|
m_playback_timer += dt;
|
|
if (m_playback_timer > (1.f / m_fps->get_float()))
|
|
{
|
|
m_playback_timer = 0;
|
|
const auto plan = pp::app::plan_animation_panel_action(
|
|
pp::app::DocumentAnimationPanelAction::playback_step,
|
|
animation_panel_state());
|
|
if (plan)
|
|
execute_animation_plan(plan.value());
|
|
}
|
|
}
|
|
}
|
|
|
|
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<this_class*>(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<NodeText>("label");
|
|
m_visibility = find<NodeCheckBox>("cb");
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void NodeAnimationTimeline::draw()
|
|
{
|
|
parent::draw();
|
|
ShaderManager::use(kShader::Color);
|
|
glDisable(pp::renderer::gl::blend_state());
|
|
|
|
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_vec4(kShaderUniform::Col, glm::vec4(glm::vec3(m_cursor_color) * 0.5f, 1.f));
|
|
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, m_proj
|
|
* glm::translate(glm::vec3(cur_pos, 0))
|
|
* glm::scale(glm::vec3(step * m_onion_size * 2.f, m_size.y * 0.5f, 1))
|
|
);
|
|
m_plane.draw_fill();
|
|
|
|
ShaderManager::u_vec4(kShaderUniform::Col, m_cursor_color);
|
|
|
|
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.75f, 1))
|
|
);
|
|
m_plane.draw_fill();
|
|
|
|
/*
|
|
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();
|
|
*/
|
|
}
|
|
|
|
kEventResult NodeAnimationTimeline::handle_event(Event* e)
|
|
{
|
|
parent::handle_event(e);
|
|
static int signaled_frame = -1;
|
|
auto me = static_cast<MouseEvent*>(e);
|
|
auto ge = static_cast<GestureEvent*>(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;
|
|
}
|