Add animation panel service boundary
This commit is contained in:
@@ -39,6 +39,22 @@ struct DocumentAnimationOperationPlan {
|
||||
bool marks_unsaved = false;
|
||||
};
|
||||
|
||||
class DocumentAnimationServices {
|
||||
public:
|
||||
virtual ~DocumentAnimationServices() = default;
|
||||
|
||||
virtual void add_frame() = 0;
|
||||
virtual void duplicate_frame(int selected_frame) = 0;
|
||||
virtual void remove_frame(int selected_frame, int target_frame) = 0;
|
||||
virtual void set_frame_duration(int selected_frame, int duration) = 0;
|
||||
virtual int move_frame(int selected_frame, int move_offset) = 0;
|
||||
virtual void goto_frame(int target_frame) = 0;
|
||||
virtual void set_onion_size(int onion_size) = 0;
|
||||
virtual void update_canvas_animation() = 0;
|
||||
virtual void reload_animation_layers() = 0;
|
||||
virtual void mark_unsaved() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_animation_frame_count(int frame_count) noexcept
|
||||
{
|
||||
if (frame_count <= 0) {
|
||||
@@ -288,4 +304,145 @@ struct DocumentAnimationOperationPlan {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_animation_operation_plan(
|
||||
const DocumentAnimationOperationPlan& plan) noexcept
|
||||
{
|
||||
switch (plan.operation) {
|
||||
case DocumentAnimationOperation::add_frame:
|
||||
if (!plan.mutates_document || !plan.marks_unsaved) {
|
||||
return pp::foundation::Status::invalid_argument("animation add plan must mutate the document");
|
||||
}
|
||||
return validate_animation_frame_count(plan.frame_count);
|
||||
|
||||
case DocumentAnimationOperation::duplicate_frame:
|
||||
case DocumentAnimationOperation::remove_frame:
|
||||
if (!plan.requires_selected_frame || !plan.mutates_document || !plan.marks_unsaved) {
|
||||
return pp::foundation::Status::invalid_argument("animation selected-frame plan must mutate the document");
|
||||
}
|
||||
if (plan.operation == DocumentAnimationOperation::remove_frame && plan.frame_count <= 1) {
|
||||
return pp::foundation::Status::invalid_argument("animation layer must keep at least one frame");
|
||||
}
|
||||
return validate_animation_frame_index(plan.frame_count, plan.selected_frame);
|
||||
|
||||
case DocumentAnimationOperation::adjust_duration:
|
||||
if (!plan.requires_selected_frame) {
|
||||
return pp::foundation::Status::invalid_argument("animation duration plan must require a selected frame");
|
||||
}
|
||||
{
|
||||
const auto index_status = validate_animation_frame_index(plan.frame_count, plan.selected_frame);
|
||||
if (!index_status.ok()) {
|
||||
return index_status;
|
||||
}
|
||||
}
|
||||
return validate_animation_frame_duration(plan.frame_duration);
|
||||
|
||||
case DocumentAnimationOperation::move_frame:
|
||||
if (!plan.requires_selected_frame || plan.move_offset == 0) {
|
||||
return pp::foundation::Status::invalid_argument("animation move plan must require selected frame and non-zero offset");
|
||||
}
|
||||
return validate_animation_frame_index(plan.frame_count, plan.selected_frame);
|
||||
|
||||
case DocumentAnimationOperation::goto_frame:
|
||||
case DocumentAnimationOperation::goto_next:
|
||||
case DocumentAnimationOperation::goto_previous:
|
||||
if (!plan.updates_canvas_animation) {
|
||||
return pp::foundation::Status::invalid_argument("animation goto plan must update canvas animation");
|
||||
}
|
||||
return validate_animation_frame_index(plan.frame_count, plan.target_frame);
|
||||
|
||||
case DocumentAnimationOperation::set_onion_size:
|
||||
if (plan.onion_size < 0) {
|
||||
return pp::foundation::Status::invalid_argument("animation onion size must not be negative");
|
||||
}
|
||||
if (!plan.updates_canvas_animation) {
|
||||
return pp::foundation::Status::invalid_argument("animation onion plan must update canvas animation");
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown animation operation");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_animation_operation_plan(
|
||||
const DocumentAnimationOperationPlan& plan,
|
||||
DocumentAnimationServices& services)
|
||||
{
|
||||
const auto validation = validate_animation_operation_plan(plan);
|
||||
if (!validation.ok()) {
|
||||
return validation;
|
||||
}
|
||||
|
||||
switch (plan.operation) {
|
||||
case DocumentAnimationOperation::add_frame:
|
||||
services.add_frame();
|
||||
services.mark_unsaved();
|
||||
services.update_canvas_animation();
|
||||
services.reload_animation_layers();
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::duplicate_frame:
|
||||
services.duplicate_frame(plan.selected_frame);
|
||||
services.mark_unsaved();
|
||||
services.update_canvas_animation();
|
||||
services.reload_animation_layers();
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::remove_frame:
|
||||
services.remove_frame(plan.selected_frame, plan.target_frame);
|
||||
services.mark_unsaved();
|
||||
if (plan.updates_canvas_animation) {
|
||||
services.goto_frame(plan.target_frame);
|
||||
}
|
||||
if (plan.reloads_animation_layers) {
|
||||
services.reload_animation_layers();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::adjust_duration:
|
||||
if (plan.mutates_document) {
|
||||
services.set_frame_duration(plan.selected_frame, plan.frame_duration);
|
||||
services.mark_unsaved();
|
||||
if (plan.updates_canvas_animation) {
|
||||
services.update_canvas_animation();
|
||||
}
|
||||
if (plan.reloads_animation_layers) {
|
||||
services.reload_animation_layers();
|
||||
}
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::move_frame:
|
||||
{
|
||||
const auto actual_target_frame = services.move_frame(plan.selected_frame, plan.move_offset);
|
||||
if (plan.marks_unsaved) {
|
||||
services.mark_unsaved();
|
||||
}
|
||||
if (plan.updates_canvas_animation) {
|
||||
services.goto_frame(actual_target_frame);
|
||||
}
|
||||
if (plan.reloads_animation_layers) {
|
||||
services.reload_animation_layers();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
case DocumentAnimationOperation::goto_frame:
|
||||
case DocumentAnimationOperation::goto_next:
|
||||
case DocumentAnimationOperation::goto_previous:
|
||||
services.goto_frame(plan.target_frame);
|
||||
if (plan.reloads_animation_layers) {
|
||||
services.reload_animation_layers();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::set_onion_size:
|
||||
services.set_onion_size(plan.onion_size);
|
||||
if (plan.updates_canvas_animation) {
|
||||
services.update_canvas_animation();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown animation operation");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
|
||||
@@ -26,6 +26,85 @@ void NodePanelAnimation::init()
|
||||
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 goto_frame(int target_frame) override
|
||||
{
|
||||
Canvas::I->anim_goto_frame(target_frame);
|
||||
}
|
||||
|
||||
void set_onion_size(int onion_size) override
|
||||
{
|
||||
panel_.m_timeline->m_onion_size = onion_size;
|
||||
}
|
||||
|
||||
void update_canvas_animation() override
|
||||
{
|
||||
Canvas::I->anim_update();
|
||||
}
|
||||
|
||||
void reload_animation_layers() override
|
||||
{
|
||||
panel_.load_layers();
|
||||
}
|
||||
|
||||
void mark_unsaved() override
|
||||
{
|
||||
Canvas::I->m_unsaved = true;
|
||||
}
|
||||
|
||||
private:
|
||||
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);
|
||||
}
|
||||
|
||||
void NodePanelAnimation::init_controls()
|
||||
{
|
||||
m_layers_container = find<NodeScroll>("layers");
|
||||
@@ -51,13 +130,7 @@ void NodePanelAnimation::init_controls()
|
||||
Canvas::I->m_anim_frame);
|
||||
if (!plan)
|
||||
return;
|
||||
Canvas::I->layer().add_frame();
|
||||
if (plan.value().marks_unsaved)
|
||||
Canvas::I->m_unsaved = true;
|
||||
if (plan.value().updates_canvas_animation)
|
||||
Canvas::I->anim_update();
|
||||
if (plan.value().reloads_animation_layers)
|
||||
load_layers();
|
||||
execute_animation_plan(plan.value());
|
||||
};
|
||||
btn_duplicate->on_click = [this](Node*) {
|
||||
if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
|
||||
@@ -67,13 +140,7 @@ void NodePanelAnimation::init_controls()
|
||||
m_selected_frame_index);
|
||||
if (!plan)
|
||||
return;
|
||||
layer->duplicate_frame(plan.value().selected_frame);
|
||||
if (plan.value().marks_unsaved)
|
||||
Canvas::I->m_unsaved = true;
|
||||
if (plan.value().updates_canvas_animation)
|
||||
Canvas::I->anim_update();
|
||||
if (plan.value().reloads_animation_layers)
|
||||
load_layers();
|
||||
execute_animation_plan(plan.value(), layer.get());
|
||||
}
|
||||
};
|
||||
btn_remove->on_click = [this](Node*) {
|
||||
@@ -84,24 +151,12 @@ void NodePanelAnimation::init_controls()
|
||||
m_selected_frame_index);
|
||||
if (!plan)
|
||||
return;
|
||||
layer->remove_frame(plan.value().selected_frame);
|
||||
m_selected_frame_index = plan.value().target_frame;
|
||||
if (plan.value().marks_unsaved)
|
||||
Canvas::I->m_unsaved = true;
|
||||
if (plan.value().updates_canvas_animation)
|
||||
Canvas::I->anim_goto_frame(plan.value().target_frame);
|
||||
if (plan.value().reloads_animation_layers)
|
||||
load_layers();
|
||||
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 index_status = pp::app::validate_animation_frame_index(
|
||||
layer->frames_count(),
|
||||
m_selected_frame_index);
|
||||
if (!index_status.ok())
|
||||
return;
|
||||
const auto plan = pp::app::plan_animation_adjust_duration(
|
||||
layer->frames_count(),
|
||||
m_selected_frame_index,
|
||||
@@ -109,23 +164,12 @@ void NodePanelAnimation::init_controls()
|
||||
1);
|
||||
if (!plan)
|
||||
return;
|
||||
layer->set_frame_duration(plan.value().selected_frame, plan.value().frame_duration);
|
||||
if (plan.value().marks_unsaved)
|
||||
Canvas::I->m_unsaved = true;
|
||||
if (plan.value().updates_canvas_animation)
|
||||
Canvas::I->anim_update();
|
||||
if (plan.value().reloads_animation_layers)
|
||||
load_layers();
|
||||
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 index_status = pp::app::validate_animation_frame_index(
|
||||
layer->frames_count(),
|
||||
m_selected_frame_index);
|
||||
if (!index_status.ok())
|
||||
return;
|
||||
const auto plan = pp::app::plan_animation_adjust_duration(
|
||||
layer->frames_count(),
|
||||
m_selected_frame_index,
|
||||
@@ -133,13 +177,7 @@ void NodePanelAnimation::init_controls()
|
||||
-1);
|
||||
if (!plan)
|
||||
return;
|
||||
layer->set_frame_duration(plan.value().selected_frame, plan.value().frame_duration);
|
||||
if (plan.value().marks_unsaved)
|
||||
Canvas::I->m_unsaved = true;
|
||||
if (plan.value().updates_canvas_animation)
|
||||
Canvas::I->anim_update();
|
||||
if (plan.value().reloads_animation_layers)
|
||||
load_layers();
|
||||
execute_animation_plan(plan.value(), layer.get());
|
||||
}
|
||||
};
|
||||
btn_left->on_click = [this](Node*) {
|
||||
@@ -153,13 +191,7 @@ void NodePanelAnimation::init_controls()
|
||||
-1);
|
||||
if (!plan)
|
||||
return;
|
||||
m_selected_frame_index = layer->move_frame_offset(plan.value().selected_frame, plan.value().move_offset);
|
||||
if (plan.value().marks_unsaved)
|
||||
Canvas::I->m_unsaved = true;
|
||||
if (plan.value().updates_canvas_animation)
|
||||
Canvas::I->anim_goto_frame(m_selected_frame_index);
|
||||
if (plan.value().reloads_animation_layers)
|
||||
load_layers();
|
||||
execute_animation_plan(plan.value(), layer.get());
|
||||
}
|
||||
};
|
||||
btn_right->on_click = [this](Node*) {
|
||||
@@ -173,13 +205,7 @@ void NodePanelAnimation::init_controls()
|
||||
1);
|
||||
if (!plan)
|
||||
return;
|
||||
m_selected_frame_index = layer->move_frame_offset(plan.value().selected_frame, plan.value().move_offset);
|
||||
if (plan.value().marks_unsaved)
|
||||
Canvas::I->m_unsaved = true;
|
||||
if (plan.value().updates_canvas_animation)
|
||||
Canvas::I->anim_goto_frame(m_selected_frame_index);
|
||||
if (plan.value().reloads_animation_layers)
|
||||
load_layers();
|
||||
execute_animation_plan(plan.value(), layer.get());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -187,9 +213,7 @@ void NodePanelAnimation::init_controls()
|
||||
const auto plan = pp::app::plan_animation_onion_size(m_onion->get_int());
|
||||
if (!plan)
|
||||
return;
|
||||
m_timeline->m_onion_size = plan.value().onion_size;
|
||||
if (plan.value().updates_canvas_animation)
|
||||
Canvas::I->anim_update();
|
||||
execute_animation_plan(plan.value());
|
||||
};
|
||||
|
||||
m_timeline->on_frame_changed = [this] (NodeAnimationTimeline* target, int frame) {
|
||||
@@ -197,29 +221,20 @@ void NodePanelAnimation::init_controls()
|
||||
if (!plan)
|
||||
return;
|
||||
LOG("goto frame %d", plan.value().target_frame);
|
||||
if (plan.value().updates_canvas_animation)
|
||||
Canvas::I->anim_goto_frame(plan.value().target_frame);
|
||||
if (plan.value().reloads_animation_layers)
|
||||
load_layers();
|
||||
execute_animation_plan(plan.value());
|
||||
};
|
||||
|
||||
btn_next->on_click = [this] (Node* target) {
|
||||
const auto plan = pp::app::plan_animation_step_frame(Canvas::I->anim_duration(), Canvas::I->m_anim_frame, 1);
|
||||
if (!plan)
|
||||
return;
|
||||
if (plan.value().updates_canvas_animation)
|
||||
Canvas::I->anim_goto_frame(plan.value().target_frame);
|
||||
if (plan.value().reloads_animation_layers)
|
||||
load_layers();
|
||||
execute_animation_plan(plan.value());
|
||||
};
|
||||
btn_prev->on_click = [this](Node* target) {
|
||||
const auto plan = pp::app::plan_animation_step_frame(Canvas::I->anim_duration(), Canvas::I->m_anim_frame, -1);
|
||||
if (!plan)
|
||||
return;
|
||||
if (plan.value().updates_canvas_animation)
|
||||
Canvas::I->anim_goto_frame(plan.value().target_frame);
|
||||
if (plan.value().reloads_animation_layers)
|
||||
load_layers();
|
||||
execute_animation_plan(plan.value());
|
||||
};
|
||||
btn_play->on_click = [this] (Node* target) {
|
||||
static auto mode = Canvas::I->m_current_mode;
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
#include "node_button_custom.h"
|
||||
#include "node_combobox.h"
|
||||
|
||||
class Layer;
|
||||
|
||||
namespace pp::app {
|
||||
struct DocumentAnimationOperationPlan;
|
||||
}
|
||||
|
||||
class NodeAnimationFrame : public NodeButtonCustom
|
||||
{
|
||||
public:
|
||||
@@ -65,6 +71,8 @@ class NodePanelAnimation : public Node
|
||||
int m_selected_frame_index = -1;
|
||||
uint32_t m_selected_frame_layer_id = 0;
|
||||
float m_playback_timer = 0;
|
||||
|
||||
void execute_animation_plan(const pp::app::DocumentAnimationOperationPlan& plan, Layer* layer = nullptr);
|
||||
public:
|
||||
using this_class = NodePanelAnimation;
|
||||
using parent = Node;
|
||||
|
||||
Reference in New Issue
Block a user