Route animation panel view through app core
This commit is contained in:
@@ -6,6 +6,10 @@
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
@@ -80,6 +84,39 @@ struct DocumentAnimationTimelineScrubPlan {
|
||||
int target_frame = 0;
|
||||
};
|
||||
|
||||
struct DocumentAnimationLayerInput {
|
||||
int layer_index = 0;
|
||||
std::uint32_t layer_id = 0;
|
||||
std::string name;
|
||||
bool visible = true;
|
||||
std::vector<int> frame_durations;
|
||||
};
|
||||
|
||||
struct DocumentAnimationFrameView {
|
||||
int frame_index = 0;
|
||||
int duration = document_animation_default_frame_duration;
|
||||
bool selected = false;
|
||||
};
|
||||
|
||||
struct DocumentAnimationLayerView {
|
||||
int layer_index = 0;
|
||||
std::uint32_t layer_id = 0;
|
||||
std::string name;
|
||||
bool visible = true;
|
||||
bool current = false;
|
||||
std::vector<DocumentAnimationFrameView> frames;
|
||||
};
|
||||
|
||||
struct DocumentAnimationPanelView {
|
||||
int total_duration = 1;
|
||||
int current_frame = 0;
|
||||
int onion_size = 0;
|
||||
std::uint32_t selected_layer_id = 0;
|
||||
int selected_frame = -1;
|
||||
bool has_selected_frame = false;
|
||||
std::vector<DocumentAnimationLayerView> layers;
|
||||
};
|
||||
|
||||
class DocumentAnimationServices {
|
||||
public:
|
||||
virtual ~DocumentAnimationServices() = default;
|
||||
@@ -140,6 +177,86 @@ public:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationPanelView> plan_animation_panel_view(
|
||||
const std::vector<DocumentAnimationLayerInput>& layers,
|
||||
int total_duration,
|
||||
int current_layer_index,
|
||||
int current_frame,
|
||||
std::uint32_t selected_layer_id,
|
||||
int selected_frame,
|
||||
int onion_size)
|
||||
{
|
||||
if (layers.empty()) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation panel requires at least one layer"));
|
||||
}
|
||||
|
||||
const auto timeline_status = validate_animation_frame_index(total_duration, current_frame);
|
||||
if (!timeline_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(timeline_status);
|
||||
}
|
||||
|
||||
if (current_layer_index < 0 || current_layer_index >= static_cast<int>(layers.size())) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(
|
||||
pp::foundation::Status::out_of_range("current animation layer index is outside the document"));
|
||||
}
|
||||
|
||||
if (onion_size < 0) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation onion size must not be negative"));
|
||||
}
|
||||
|
||||
DocumentAnimationPanelView view;
|
||||
view.total_duration = total_duration;
|
||||
view.current_frame = current_frame;
|
||||
view.onion_size = onion_size;
|
||||
view.selected_layer_id = selected_layer_id;
|
||||
view.selected_frame = selected_frame;
|
||||
view.layers.reserve(layers.size());
|
||||
|
||||
for (std::size_t i = 0; i < layers.size(); ++i) {
|
||||
const auto& input = layers[i];
|
||||
if (input.layer_index < 0) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(
|
||||
pp::foundation::Status::out_of_range("animation layer index must not be negative"));
|
||||
}
|
||||
if (input.frame_durations.empty()) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation layer must contain at least one frame"));
|
||||
}
|
||||
|
||||
DocumentAnimationLayerView layer;
|
||||
layer.layer_index = input.layer_index;
|
||||
layer.layer_id = input.layer_id;
|
||||
layer.name = input.name;
|
||||
layer.visible = input.visible;
|
||||
layer.current = input.layer_index == current_layer_index;
|
||||
layer.frames.reserve(input.frame_durations.size());
|
||||
|
||||
for (std::size_t frame_index = 0; frame_index < input.frame_durations.size(); ++frame_index) {
|
||||
const int duration = input.frame_durations[frame_index];
|
||||
const auto duration_status = validate_animation_frame_duration(duration);
|
||||
if (!duration_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(duration_status);
|
||||
}
|
||||
|
||||
const bool selected = selected_frame >= 0
|
||||
&& input.layer_id == selected_layer_id
|
||||
&& static_cast<int>(frame_index) == selected_frame;
|
||||
view.has_selected_frame = view.has_selected_frame || selected;
|
||||
layer.frames.push_back(DocumentAnimationFrameView {
|
||||
.frame_index = static_cast<int>(frame_index),
|
||||
.duration = duration,
|
||||
.selected = selected,
|
||||
});
|
||||
}
|
||||
|
||||
view.layers.push_back(std::move(layer));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::success(std::move(view));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOnionFrameRange> plan_animation_onion_frame_range(
|
||||
int frame_count,
|
||||
int current_frame,
|
||||
|
||||
@@ -36,8 +36,8 @@ void NodePanelAnimation::execute_animation_plan(const pp::app::DocumentAnimation
|
||||
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,
|
||||
.total_duration = m_timeline ? m_timeline->m_frames_count : 0,
|
||||
.current_frame = m_timeline ? m_timeline->m_frame : 0,
|
||||
.playback_active = btn_play->is_active(),
|
||||
};
|
||||
}
|
||||
@@ -195,50 +195,91 @@ void NodePanelAnimation::load_layers()
|
||||
return;
|
||||
m_layers_container->remove_all_children();
|
||||
m_frames_container->remove_all_children();
|
||||
if (!Canvas::I)
|
||||
return;
|
||||
auto& layers = Canvas::I->m_layers;
|
||||
m_selected_frame = nullptr;
|
||||
|
||||
|
||||
std::vector<pp::app::DocumentAnimationLayerInput> layer_inputs;
|
||||
layer_inputs.reserve(layers.size());
|
||||
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>();
|
||||
pp::app::DocumentAnimationLayerInput input;
|
||||
input.layer_index = i;
|
||||
input.layer_id = layers[i]->id;
|
||||
input.name = layers[i]->m_name;
|
||||
input.visible = layers[i]->m_visible;
|
||||
input.frame_durations.reserve(layers[i]->frames_count());
|
||||
for (int fi = 0; fi < layers[i]->frames_count(); fi++)
|
||||
{
|
||||
auto b = film->add_frame(layers[i]->frame_duration(fi));
|
||||
input.frame_durations.push_back(layers[i]->frame_duration(fi));
|
||||
layer_inputs.push_back(std::move(input));
|
||||
}
|
||||
|
||||
if (m_selected_frame_layer_id == layers[i]->id && m_selected_frame_index == fi)
|
||||
const auto view_result = pp::app::plan_animation_panel_view(
|
||||
layer_inputs,
|
||||
Canvas::I->anim_duration(),
|
||||
Canvas::I->m_current_layer_idx,
|
||||
Canvas::I->m_anim_frame,
|
||||
m_selected_frame_layer_id,
|
||||
m_selected_frame_index,
|
||||
m_onion->get_int());
|
||||
if (!view_result)
|
||||
{
|
||||
LOG("Animation panel view failed: %s", view_result.status().message);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& view = view_result.value();
|
||||
|
||||
for (const auto& layer_view : view.layers)
|
||||
{
|
||||
auto l = m_layers_container->add_child<NodeAnimationLayer>();
|
||||
l->set_text(layer_view.name);
|
||||
l->set_selected(layer_view.current);
|
||||
l->set_chekcbox(layer_view.visible);
|
||||
auto film = m_frames_container->add_child_ref<NodeAnimationFilm>();
|
||||
const int frame_count = static_cast<int>(layer_view.frames.size());
|
||||
for (const auto& frame_view : layer_view.frames)
|
||||
{
|
||||
auto b = film->add_frame(frame_view.duration);
|
||||
|
||||
if (frame_view.selected)
|
||||
{
|
||||
b->set_active(true);
|
||||
m_selected_frame = b.get();
|
||||
}
|
||||
|
||||
b->on_click = [this, fi, lid=layers[i]->id, i] (Node* target) {
|
||||
b->on_click = [this,
|
||||
frame_index = frame_view.frame_index,
|
||||
frame_count,
|
||||
layer_id = layer_view.layer_id,
|
||||
layer_index = layer_view.layer_index] (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());
|
||||
frame_count,
|
||||
layer_index,
|
||||
layer_id,
|
||||
frame_index);
|
||||
if (plan && Canvas::I && layer_index >= 0 && layer_index < static_cast<int>(Canvas::I->m_layers.size()))
|
||||
execute_animation_plan(plan.value(), Canvas::I->m_layers[layer_index].get());
|
||||
};
|
||||
}
|
||||
}
|
||||
m_timeline->m_frame = Canvas::I->m_anim_frame;
|
||||
m_timeline->m_onion_size = m_onion->get_int();
|
||||
m_timeline->m_frame = view.current_frame;
|
||||
m_timeline->m_frames_count = view.total_duration;
|
||||
m_timeline->m_onion_size = view.onion_size;
|
||||
update_frames();
|
||||
}
|
||||
|
||||
void NodePanelAnimation::update_frames()
|
||||
{
|
||||
int total_frames = Canvas::I->anim_duration();
|
||||
if (!m_timeline || !m_frame_label)
|
||||
return;
|
||||
int total_frames = m_timeline ? m_timeline->m_frames_count : 1;
|
||||
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);
|
||||
}
|
||||
@@ -349,8 +390,7 @@ kEventResult NodeAnimationTimeline::handle_event(Event* e)
|
||||
auto me = static_cast<MouseEvent*>(e);
|
||||
auto update = [&](){
|
||||
auto loc = me->m_pos - m_pos;
|
||||
const int total_duration = Canvas::I ? Canvas::I->anim_duration() : 0;
|
||||
const auto scrub = pp::app::plan_animation_timeline_scrub(total_duration, loc.x);
|
||||
const auto scrub = pp::app::plan_animation_timeline_scrub(m_frames_count, loc.x);
|
||||
if (!scrub)
|
||||
return;
|
||||
m_frame = scrub.value().target_frame;
|
||||
|
||||
Reference in New Issue
Block a user