Add animation panel service boundary
This commit is contained in:
@@ -39,7 +39,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
|||||||
| DEBT-0019 | Open | Modernization | Unreferenced-parameter warnings are muted globally through `pp_project_warnings` with MSVC `/wd4100` and Clang/GCC `-Wno-unused-parameter` | Legacy callbacks, virtual hooks, serializer methods, and platform/API compatibility functions carry many intentionally unused parameters during the component split; muting this keeps stricter warning builds focused on higher-signal migration issues | `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset linux-clang --target pp_foundation` | Remove `/wd4100` and `-Wno-unused-parameter`, mark intentionally unused parameters with names/comments or `[[maybe_unused]]`, and make the Windows app plus headless Clang/GCC tests pass without unreferenced-parameter warnings |
|
| DEBT-0019 | Open | Modernization | Unreferenced-parameter warnings are muted globally through `pp_project_warnings` with MSVC `/wd4100` and Clang/GCC `-Wno-unused-parameter` | Legacy callbacks, virtual hooks, serializer methods, and platform/API compatibility functions carry many intentionally unused parameters during the component split; muting this keeps stricter warning builds focused on higher-signal migration issues | `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset linux-clang --target pp_foundation` | Remove `/wd4100` and `-Wno-unused-parameter`, mark intentionally unused parameters with names/comments or `[[maybe_unused]]`, and make the Windows app plus headless Clang/GCC tests pass without unreferenced-parameter warnings |
|
||||||
| DEBT-0020 | Open | Modernization | Document resize dialog state, selected-resolution planning, and execution dispatch now consume pure `pp_app_core` through `NodeDialogResize`, `App::dialog_resize`, `pano_cli plan-document-resize`, and the `DocumentResizeServices` boundary, but the live adapter still calls legacy `Canvas::resize`, updates the legacy app title, and clears legacy `ActionManager` history | Preserve existing layer/frame GPU resize behavior while the document model and canvas execution boundary are extracted incrementally | `pp_app_core_document_resize_tests`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `ctest --preset desktop-fast --build-config Debug` | Document resize execution is owned by injected document/app services with no legacy resize adapter, title shim, or direct `ActionManager` history clearing |
|
| DEBT-0020 | Open | Modernization | Document resize dialog state, selected-resolution planning, and execution dispatch now consume pure `pp_app_core` through `NodeDialogResize`, `App::dialog_resize`, `pano_cli plan-document-resize`, and the `DocumentResizeServices` boundary, but the live adapter still calls legacy `Canvas::resize`, updates the legacy app title, and clears legacy `ActionManager` history | Preserve existing layer/frame GPU resize behavior while the document model and canvas execution boundary are extracted incrementally | `pp_app_core_document_resize_tests`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `ctest --preset desktop-fast --build-config Debug` | Document resize execution is owned by injected document/app services with no legacy resize adapter, title shim, or direct `ActionManager` history clearing |
|
||||||
| DEBT-0021 | Open | Modernization | Layer rename and layer panel operation planning now consume pure `pp_app_core` through `App::dialog_layer_rename`, `App::init_sidebar` layer callbacks, `pano_cli plan-layer-rename`, and `pano_cli plan-layer-operation`, but live execution still mutates legacy `Canvas` layer state, `NodeLayer`/`NodePanelLayer`, and `ActionManager` undo entries directly | Preserve existing UI/canvas behavior while document layer commands and undo history are extracted incrementally | `pp_app_core_document_layer_tests`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-layer-operation --kind add --layer-count 2 --index 1 --name Paint`; `ctest --preset desktop-fast --build-config Debug` | Layer command execution is owned by the document/app command boundary with legacy `Canvas`/UI nodes acting only as adapters or removed entirely |
|
| DEBT-0021 | Open | Modernization | Layer rename and layer panel operation planning now consume pure `pp_app_core` through `App::dialog_layer_rename`, `App::init_sidebar` layer callbacks, `pano_cli plan-layer-rename`, and `pano_cli plan-layer-operation`, but live execution still mutates legacy `Canvas` layer state, `NodeLayer`/`NodePanelLayer`, and `ActionManager` undo entries directly | Preserve existing UI/canvas behavior while document layer commands and undo history are extracted incrementally | `pp_app_core_document_layer_tests`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-layer-operation --kind add --layer-count 2 --index 1 --name Paint`; `ctest --preset desktop-fast --build-config Debug` | Layer command execution is owned by the document/app command boundary with legacy `Canvas`/UI nodes acting only as adapters or removed entirely |
|
||||||
| DEBT-0022 | Open | Modernization | Animation panel frame command planning now consumes pure `pp_app_core` through `NodePanelAnimation` and `pano_cli plan-animation-operation`, and `pp_legacy_ui_core` temporarily links `pp_app_core`, but live execution still mutates legacy `Canvas`/`Layer` frame state and animation playback state directly | Preserve existing animation panel behavior while timeline/frame commands move toward the document/app command boundary | `pp_app_core_document_animation_tests`; `pano_cli plan-animation-operation --kind add --frame-count 2 --current-frame 0`; `pano_cli plan-animation-operation --kind next --total-duration 5 --current-frame 4`; `ctest --preset desktop-fast --build-config Debug` | Animation frame/timeline execution is owned by the document/app command boundary with legacy `Canvas`/`Layer`/UI nodes acting only as adapters or removed entirely |
|
| DEBT-0022 | Open | Modernization | Animation panel frame command planning and panel-control/timeline execution dispatch now consume pure `pp_app_core` through `NodePanelAnimation`, `pano_cli plan-animation-operation`, and `DocumentAnimationServices`, and `pp_legacy_ui_core` temporarily links `pp_app_core`, but selected-frame click handling, playback/play-mode toggles, and the live adapter still mutate or read legacy `Canvas`/`Layer` frame state directly | Preserve existing animation panel behavior while timeline/frame commands move toward the document/app command boundary | `pp_app_core_document_animation_tests`; `pano_cli plan-animation-operation --kind add --frame-count 2 --current-frame 0`; `pano_cli plan-animation-operation --kind next --total-duration 5 --current-frame 4`; `ctest --preset desktop-fast --build-config Debug` | Animation frame/timeline execution is owned by injected document/app timeline services with no legacy `Canvas`/`Layer` adapter and UI nodes acting only as adapters or removed entirely |
|
||||||
| DEBT-0023 | Open | Modernization | Brush/color/preset/stroke-settings UI planning and execution dispatch now consume pure `pp_app_core` through `App::init_sidebar`, restored/docked floating-panel callbacks, `pano_cli plan-brush-operation`, and the `BrushUiServices` boundary, but the live adapter still mutates legacy `Brush`, calls legacy brush texture loading, and refreshes legacy quick/stroke/color widgets | Preserve existing brush UI behavior while brush commands move toward a brush/app command boundary and asset-managed texture selection | `pp_app_core_brush_ui_tests`; `pano_cli plan-brush-operation --kind color --r 0.25 --g 0.5 --b 0.75 --a 1`; `pano_cli plan-brush-operation --kind pattern --path data/patterns/noise.png --thumb data/patterns/thumbs/noise.png`; `ctest --preset desktop-fast --build-config Debug` | Brush color/texture/preset/stroke-settings execution is owned by injected brush/app/asset/UI services with no legacy brush adapter |
|
| DEBT-0023 | Open | Modernization | Brush/color/preset/stroke-settings UI planning and execution dispatch now consume pure `pp_app_core` through `App::init_sidebar`, restored/docked floating-panel callbacks, `pano_cli plan-brush-operation`, and the `BrushUiServices` boundary, but the live adapter still mutates legacy `Brush`, calls legacy brush texture loading, and refreshes legacy quick/stroke/color widgets | Preserve existing brush UI behavior while brush commands move toward a brush/app command boundary and asset-managed texture selection | `pp_app_core_brush_ui_tests`; `pano_cli plan-brush-operation --kind color --r 0.25 --g 0.5 --b 0.75 --a 1`; `pano_cli plan-brush-operation --kind pattern --path data/patterns/noise.png --thumb data/patterns/thumbs/noise.png`; `ctest --preset desktop-fast --build-config Debug` | Brush color/texture/preset/stroke-settings execution is owned by injected brush/app/asset/UI services with no legacy brush adapter |
|
||||||
| DEBT-0024 | Open | Modernization | Grid/heightmap/lightmap UI planning now consumes pure `pp_app_core` through `NodePanelGrid` and `pano_cli plan-grid-operation`, but live execution still performs legacy image loading, OpenGL texture updates, nanort lightmap baking, progress UI, and `Canvas::draw_objects` commit directly | Preserve grid/lightmap behavior while moving renderable grid commands toward app/renderer/document boundaries | `pp_app_core_grid_ui_tests`; `pano_cli plan-grid-operation --kind render --float32 --texture-resolution 1024 --samples 32`; `ctest --preset desktop-fast --build-config Debug` | Grid heightmap/lightmap execution is owned by app/renderer/document services with `NodePanelGrid` acting only as UI adapter |
|
| DEBT-0024 | Open | Modernization | Grid/heightmap/lightmap UI planning now consumes pure `pp_app_core` through `NodePanelGrid` and `pano_cli plan-grid-operation`, but live execution still performs legacy image loading, OpenGL texture updates, nanort lightmap baking, progress UI, and `Canvas::draw_objects` commit directly | Preserve grid/lightmap behavior while moving renderable grid commands toward app/renderer/document boundaries | `pp_app_core_grid_ui_tests`; `pano_cli plan-grid-operation --kind render --float32 --texture-resolution 1024 --samples 32`; `ctest --preset desktop-fast --build-config Debug` | Grid heightmap/lightmap execution is owned by app/renderer/document services with `NodePanelGrid` acting only as UI adapter |
|
||||||
| DEBT-0025 | Open | Modernization | Quick brush/color slot and mini-state planning and execution dispatch now consume pure `pp_app_core` through `NodePanelQuick`, `pano_cli plan-quick-operation`, and the `QuickUiServices` boundary, but the live adapter still mutates legacy quick UI widgets, `Brush` previews, color picker popup state, and preset popup state | Preserve quick-panel behavior while quick brush/color commands move toward a brush/app command boundary with safer automation coverage | `pp_app_core_quick_ui_tests`; `pano_cli plan-quick-operation --kind brush --current-index 0 --slot-index 2`; `pano_cli plan-quick-operation --kind restore --brush-index 2 --color-index 1 --fire-event`; `ctest --preset desktop-fast --build-config Debug` | Quick-panel selection, popup, restore, reset, brush preview, and color execution are owned by injected app/brush/UI services with no legacy quick-panel adapter |
|
| DEBT-0025 | Open | Modernization | Quick brush/color slot and mini-state planning and execution dispatch now consume pure `pp_app_core` through `NodePanelQuick`, `pano_cli plan-quick-operation`, and the `QuickUiServices` boundary, but the live adapter still mutates legacy quick UI widgets, `Brush` previews, color picker popup state, and preset popup state | Preserve quick-panel behavior while quick brush/color commands move toward a brush/app command boundary with safer automation coverage | `pp_app_core_quick_ui_tests`; `pano_cli plan-quick-operation --kind brush --current-index 0 --slot-index 2`; `pano_cli plan-quick-operation --kind restore --brush-index 2 --color-index 1 --fire-event`; `ctest --preset desktop-fast --build-config Debug` | Quick-panel selection, popup, restore, reset, brush preview, and color execution are owned by injected app/brush/UI services with no legacy quick-panel adapter |
|
||||||
|
|||||||
@@ -499,7 +499,9 @@ adapter continues execution.
|
|||||||
`pano_cli plan-animation-operation` exposes app-core planning for animation
|
`pano_cli plan-animation-operation` exposes app-core planning for animation
|
||||||
frame add, duplicate, remove, duration adjustment, timeline moves, timeline
|
frame add, duplicate, remove, duration adjustment, timeline moves, timeline
|
||||||
goto/next/previous, and onion-size updates used by the live animation panel
|
goto/next/previous, and onion-size updates used by the live animation panel
|
||||||
before legacy `Canvas`/`Layer` frame execution continues.
|
Panel-control and timeline execution now dispatch through
|
||||||
|
`DocumentAnimationServices` before the legacy `Canvas`/`Layer` adapter
|
||||||
|
continues.
|
||||||
`pano_cli plan-brush-operation` exposes app-core planning for brush color
|
`pano_cli plan-brush-operation` exposes app-core planning for brush color
|
||||||
changes, tip/pattern/dual texture changes, preset brush replacement, and stroke
|
changes, tip/pattern/dual texture changes, preset brush replacement, and stroke
|
||||||
settings refreshes used by the live brush, quick, color, and floating panel
|
settings refreshes used by the live brush, quick, color, and floating panel
|
||||||
@@ -1200,7 +1202,8 @@ Results:
|
|||||||
- `pp_app_core_document_animation_tests` passed, covering animation frame
|
- `pp_app_core_document_animation_tests` passed, covering animation frame
|
||||||
add/duplicate/remove planning, selected-frame rejection, last-frame remove
|
add/duplicate/remove planning, selected-frame rejection, last-frame remove
|
||||||
rejection, duration floor/overflow handling, timeline move edge behavior,
|
rejection, duration floor/overflow handling, timeline move edge behavior,
|
||||||
goto/next/previous wrapping, and onion-size rejection.
|
goto/next/previous wrapping, onion-size rejection, service dispatch ordering,
|
||||||
|
non-mutating duration no-ops, and malformed execution payload rejection.
|
||||||
- `pano_cli_plan_animation_operation_add_smoke`,
|
- `pano_cli_plan_animation_operation_add_smoke`,
|
||||||
`pano_cli_plan_animation_operation_duration_floor_smoke`,
|
`pano_cli_plan_animation_operation_duration_floor_smoke`,
|
||||||
`pano_cli_plan_animation_operation_next_wrap_smoke`, and
|
`pano_cli_plan_animation_operation_next_wrap_smoke`, and
|
||||||
|
|||||||
@@ -39,6 +39,22 @@ struct DocumentAnimationOperationPlan {
|
|||||||
bool marks_unsaved = false;
|
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
|
[[nodiscard]] inline pp::foundation::Status validate_animation_frame_count(int frame_count) noexcept
|
||||||
{
|
{
|
||||||
if (frame_count <= 0) {
|
if (frame_count <= 0) {
|
||||||
@@ -288,4 +304,145 @@ struct DocumentAnimationOperationPlan {
|
|||||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
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
|
} // namespace pp::app
|
||||||
|
|||||||
@@ -26,6 +26,85 @@ void NodePanelAnimation::init()
|
|||||||
init_controls();
|
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()
|
void NodePanelAnimation::init_controls()
|
||||||
{
|
{
|
||||||
m_layers_container = find<NodeScroll>("layers");
|
m_layers_container = find<NodeScroll>("layers");
|
||||||
@@ -51,13 +130,7 @@ void NodePanelAnimation::init_controls()
|
|||||||
Canvas::I->m_anim_frame);
|
Canvas::I->m_anim_frame);
|
||||||
if (!plan)
|
if (!plan)
|
||||||
return;
|
return;
|
||||||
Canvas::I->layer().add_frame();
|
execute_animation_plan(plan.value());
|
||||||
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();
|
|
||||||
};
|
};
|
||||||
btn_duplicate->on_click = [this](Node*) {
|
btn_duplicate->on_click = [this](Node*) {
|
||||||
if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
|
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);
|
m_selected_frame_index);
|
||||||
if (!plan)
|
if (!plan)
|
||||||
return;
|
return;
|
||||||
layer->duplicate_frame(plan.value().selected_frame);
|
execute_animation_plan(plan.value(), layer.get());
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
btn_remove->on_click = [this](Node*) {
|
btn_remove->on_click = [this](Node*) {
|
||||||
@@ -84,24 +151,12 @@ void NodePanelAnimation::init_controls()
|
|||||||
m_selected_frame_index);
|
m_selected_frame_index);
|
||||||
if (!plan)
|
if (!plan)
|
||||||
return;
|
return;
|
||||||
layer->remove_frame(plan.value().selected_frame);
|
execute_animation_plan(plan.value(), layer.get());
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
btn_up->on_click = [this](Node*) {
|
btn_up->on_click = [this](Node*) {
|
||||||
if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
|
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(
|
const auto plan = pp::app::plan_animation_adjust_duration(
|
||||||
layer->frames_count(),
|
layer->frames_count(),
|
||||||
m_selected_frame_index,
|
m_selected_frame_index,
|
||||||
@@ -109,23 +164,12 @@ void NodePanelAnimation::init_controls()
|
|||||||
1);
|
1);
|
||||||
if (!plan)
|
if (!plan)
|
||||||
return;
|
return;
|
||||||
layer->set_frame_duration(plan.value().selected_frame, plan.value().frame_duration);
|
execute_animation_plan(plan.value(), layer.get());
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
btn_down->on_click = [this](Node*) {
|
btn_down->on_click = [this](Node*) {
|
||||||
if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
|
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(
|
const auto plan = pp::app::plan_animation_adjust_duration(
|
||||||
layer->frames_count(),
|
layer->frames_count(),
|
||||||
m_selected_frame_index,
|
m_selected_frame_index,
|
||||||
@@ -133,13 +177,7 @@ void NodePanelAnimation::init_controls()
|
|||||||
-1);
|
-1);
|
||||||
if (!plan)
|
if (!plan)
|
||||||
return;
|
return;
|
||||||
layer->set_frame_duration(plan.value().selected_frame, plan.value().frame_duration);
|
execute_animation_plan(plan.value(), layer.get());
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
btn_left->on_click = [this](Node*) {
|
btn_left->on_click = [this](Node*) {
|
||||||
@@ -153,13 +191,7 @@ void NodePanelAnimation::init_controls()
|
|||||||
-1);
|
-1);
|
||||||
if (!plan)
|
if (!plan)
|
||||||
return;
|
return;
|
||||||
m_selected_frame_index = layer->move_frame_offset(plan.value().selected_frame, plan.value().move_offset);
|
execute_animation_plan(plan.value(), layer.get());
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
btn_right->on_click = [this](Node*) {
|
btn_right->on_click = [this](Node*) {
|
||||||
@@ -173,13 +205,7 @@ void NodePanelAnimation::init_controls()
|
|||||||
1);
|
1);
|
||||||
if (!plan)
|
if (!plan)
|
||||||
return;
|
return;
|
||||||
m_selected_frame_index = layer->move_frame_offset(plan.value().selected_frame, plan.value().move_offset);
|
execute_animation_plan(plan.value(), layer.get());
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -187,9 +213,7 @@ void NodePanelAnimation::init_controls()
|
|||||||
const auto plan = pp::app::plan_animation_onion_size(m_onion->get_int());
|
const auto plan = pp::app::plan_animation_onion_size(m_onion->get_int());
|
||||||
if (!plan)
|
if (!plan)
|
||||||
return;
|
return;
|
||||||
m_timeline->m_onion_size = plan.value().onion_size;
|
execute_animation_plan(plan.value());
|
||||||
if (plan.value().updates_canvas_animation)
|
|
||||||
Canvas::I->anim_update();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
m_timeline->on_frame_changed = [this] (NodeAnimationTimeline* target, int frame) {
|
m_timeline->on_frame_changed = [this] (NodeAnimationTimeline* target, int frame) {
|
||||||
@@ -197,29 +221,20 @@ void NodePanelAnimation::init_controls()
|
|||||||
if (!plan)
|
if (!plan)
|
||||||
return;
|
return;
|
||||||
LOG("goto frame %d", plan.value().target_frame);
|
LOG("goto frame %d", plan.value().target_frame);
|
||||||
if (plan.value().updates_canvas_animation)
|
execute_animation_plan(plan.value());
|
||||||
Canvas::I->anim_goto_frame(plan.value().target_frame);
|
|
||||||
if (plan.value().reloads_animation_layers)
|
|
||||||
load_layers();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
btn_next->on_click = [this] (Node* target) {
|
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);
|
const auto plan = pp::app::plan_animation_step_frame(Canvas::I->anim_duration(), Canvas::I->m_anim_frame, 1);
|
||||||
if (!plan)
|
if (!plan)
|
||||||
return;
|
return;
|
||||||
if (plan.value().updates_canvas_animation)
|
execute_animation_plan(plan.value());
|
||||||
Canvas::I->anim_goto_frame(plan.value().target_frame);
|
|
||||||
if (plan.value().reloads_animation_layers)
|
|
||||||
load_layers();
|
|
||||||
};
|
};
|
||||||
btn_prev->on_click = [this](Node* target) {
|
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);
|
const auto plan = pp::app::plan_animation_step_frame(Canvas::I->anim_duration(), Canvas::I->m_anim_frame, -1);
|
||||||
if (!plan)
|
if (!plan)
|
||||||
return;
|
return;
|
||||||
if (plan.value().updates_canvas_animation)
|
execute_animation_plan(plan.value());
|
||||||
Canvas::I->anim_goto_frame(plan.value().target_frame);
|
|
||||||
if (plan.value().reloads_animation_layers)
|
|
||||||
load_layers();
|
|
||||||
};
|
};
|
||||||
btn_play->on_click = [this] (Node* target) {
|
btn_play->on_click = [this] (Node* target) {
|
||||||
static auto mode = Canvas::I->m_current_mode;
|
static auto mode = Canvas::I->m_current_mode;
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
#include "node_button_custom.h"
|
#include "node_button_custom.h"
|
||||||
#include "node_combobox.h"
|
#include "node_combobox.h"
|
||||||
|
|
||||||
|
class Layer;
|
||||||
|
|
||||||
|
namespace pp::app {
|
||||||
|
struct DocumentAnimationOperationPlan;
|
||||||
|
}
|
||||||
|
|
||||||
class NodeAnimationFrame : public NodeButtonCustom
|
class NodeAnimationFrame : public NodeButtonCustom
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -65,6 +71,8 @@ class NodePanelAnimation : public Node
|
|||||||
int m_selected_frame_index = -1;
|
int m_selected_frame_index = -1;
|
||||||
uint32_t m_selected_frame_layer_id = 0;
|
uint32_t m_selected_frame_layer_id = 0;
|
||||||
float m_playback_timer = 0;
|
float m_playback_timer = 0;
|
||||||
|
|
||||||
|
void execute_animation_plan(const pp::app::DocumentAnimationOperationPlan& plan, Layer* layer = nullptr);
|
||||||
public:
|
public:
|
||||||
using this_class = NodePanelAnimation;
|
using this_class = NodePanelAnimation;
|
||||||
using parent = Node;
|
using parent = Node;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "test_harness.h"
|
#include "test_harness.h"
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#define PP_REQUIRE(harness, expression) \
|
#define PP_REQUIRE(harness, expression) \
|
||||||
do { \
|
do { \
|
||||||
@@ -14,6 +15,97 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
class FakeDocumentAnimationServices final : public pp::app::DocumentAnimationServices {
|
||||||
|
public:
|
||||||
|
void add_frame() override
|
||||||
|
{
|
||||||
|
adds += 1;
|
||||||
|
call_order += "add;";
|
||||||
|
}
|
||||||
|
|
||||||
|
void duplicate_frame(int selected_frame) override
|
||||||
|
{
|
||||||
|
duplicates += 1;
|
||||||
|
last_selected_frame = selected_frame;
|
||||||
|
call_order += "duplicate;";
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_frame(int selected_frame, int target_frame) override
|
||||||
|
{
|
||||||
|
removes += 1;
|
||||||
|
last_selected_frame = selected_frame;
|
||||||
|
last_target_frame = target_frame;
|
||||||
|
call_order += "remove;";
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_frame_duration(int selected_frame, int duration) override
|
||||||
|
{
|
||||||
|
duration_sets += 1;
|
||||||
|
last_selected_frame = selected_frame;
|
||||||
|
last_duration = duration;
|
||||||
|
call_order += "duration;";
|
||||||
|
}
|
||||||
|
|
||||||
|
int move_frame(int selected_frame, int move_offset) override
|
||||||
|
{
|
||||||
|
moves += 1;
|
||||||
|
last_selected_frame = selected_frame;
|
||||||
|
last_move_offset = move_offset;
|
||||||
|
call_order += "move;";
|
||||||
|
return move_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void goto_frame(int target_frame) override
|
||||||
|
{
|
||||||
|
gotos += 1;
|
||||||
|
last_target_frame = target_frame;
|
||||||
|
call_order += "goto;";
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_onion_size(int onion_size) override
|
||||||
|
{
|
||||||
|
onion_sets += 1;
|
||||||
|
last_onion_size = onion_size;
|
||||||
|
call_order += "onion;";
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_canvas_animation() override
|
||||||
|
{
|
||||||
|
canvas_updates += 1;
|
||||||
|
call_order += "update;";
|
||||||
|
}
|
||||||
|
|
||||||
|
void reload_animation_layers() override
|
||||||
|
{
|
||||||
|
reloads += 1;
|
||||||
|
call_order += "reload;";
|
||||||
|
}
|
||||||
|
|
||||||
|
void mark_unsaved() override
|
||||||
|
{
|
||||||
|
unsaved_marks += 1;
|
||||||
|
call_order += "unsaved;";
|
||||||
|
}
|
||||||
|
|
||||||
|
int adds = 0;
|
||||||
|
int duplicates = 0;
|
||||||
|
int removes = 0;
|
||||||
|
int duration_sets = 0;
|
||||||
|
int moves = 0;
|
||||||
|
int gotos = 0;
|
||||||
|
int onion_sets = 0;
|
||||||
|
int canvas_updates = 0;
|
||||||
|
int reloads = 0;
|
||||||
|
int unsaved_marks = 0;
|
||||||
|
int last_selected_frame = -1;
|
||||||
|
int last_target_frame = -1;
|
||||||
|
int last_duration = -1;
|
||||||
|
int last_move_offset = 0;
|
||||||
|
int last_onion_size = -1;
|
||||||
|
int move_result = 0;
|
||||||
|
std::string call_order;
|
||||||
|
};
|
||||||
|
|
||||||
void add_duplicate_and_remove_validate_frame_bounds(pp::tests::Harness& harness)
|
void add_duplicate_and_remove_validate_frame_bounds(pp::tests::Harness& harness)
|
||||||
{
|
{
|
||||||
const auto add = pp::app::plan_animation_add_frame(2, 0);
|
const auto add = pp::app::plan_animation_add_frame(2, 0);
|
||||||
@@ -120,6 +212,107 @@ void onion_size_updates_canvas_without_document_mutation(pp::tests::Harness& har
|
|||||||
PP_EXPECT(harness, !pp::app::plan_animation_onion_size(-1));
|
PP_EXPECT(harness, !pp::app::plan_animation_onion_size(-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void executor_dispatches_mutating_frame_operations(pp::tests::Harness& harness)
|
||||||
|
{
|
||||||
|
FakeDocumentAnimationServices services;
|
||||||
|
|
||||||
|
const auto add = pp::app::plan_animation_add_frame(2, 0);
|
||||||
|
PP_REQUIRE(harness, add);
|
||||||
|
PP_EXPECT(harness, pp::app::execute_animation_operation_plan(add.value(), services).ok());
|
||||||
|
|
||||||
|
const auto duplicate = pp::app::plan_animation_duplicate_frame(3, 1);
|
||||||
|
PP_REQUIRE(harness, duplicate);
|
||||||
|
PP_EXPECT(harness, pp::app::execute_animation_operation_plan(duplicate.value(), services).ok());
|
||||||
|
|
||||||
|
const auto remove = pp::app::plan_animation_remove_frame(3, 2);
|
||||||
|
PP_REQUIRE(harness, remove);
|
||||||
|
PP_EXPECT(harness, pp::app::execute_animation_operation_plan(remove.value(), services).ok());
|
||||||
|
|
||||||
|
PP_EXPECT(harness, services.adds == 1);
|
||||||
|
PP_EXPECT(harness, services.duplicates == 1);
|
||||||
|
PP_EXPECT(harness, services.removes == 1);
|
||||||
|
PP_EXPECT(harness, services.unsaved_marks == 3);
|
||||||
|
PP_EXPECT(harness, services.canvas_updates == 2);
|
||||||
|
PP_EXPECT(harness, services.gotos == 1);
|
||||||
|
PP_EXPECT(harness, services.reloads == 3);
|
||||||
|
PP_EXPECT(harness, services.last_selected_frame == 2);
|
||||||
|
PP_EXPECT(harness, services.last_target_frame == 1);
|
||||||
|
PP_EXPECT(
|
||||||
|
harness,
|
||||||
|
services.call_order == "add;unsaved;update;reload;duplicate;unsaved;update;reload;remove;unsaved;goto;reload;");
|
||||||
|
}
|
||||||
|
|
||||||
|
void executor_dispatches_timeline_and_parameter_operations(pp::tests::Harness& harness)
|
||||||
|
{
|
||||||
|
FakeDocumentAnimationServices services;
|
||||||
|
services.move_result = 2;
|
||||||
|
|
||||||
|
const auto duration = pp::app::plan_animation_adjust_duration(3, 1, 4, 1);
|
||||||
|
PP_REQUIRE(harness, duration);
|
||||||
|
PP_EXPECT(harness, pp::app::execute_animation_operation_plan(duration.value(), services).ok());
|
||||||
|
|
||||||
|
const auto duration_noop = pp::app::plan_animation_adjust_duration(3, 1, 1, -1);
|
||||||
|
PP_REQUIRE(harness, duration_noop);
|
||||||
|
PP_EXPECT(harness, pp::app::execute_animation_operation_plan(duration_noop.value(), services).ok());
|
||||||
|
|
||||||
|
const auto move = pp::app::plan_animation_move_frame(3, 1, 1);
|
||||||
|
PP_REQUIRE(harness, move);
|
||||||
|
PP_EXPECT(harness, pp::app::execute_animation_operation_plan(move.value(), services).ok());
|
||||||
|
|
||||||
|
const auto next = pp::app::plan_animation_step_frame(5, 4, 1);
|
||||||
|
PP_REQUIRE(harness, next);
|
||||||
|
PP_EXPECT(harness, pp::app::execute_animation_operation_plan(next.value(), services).ok());
|
||||||
|
|
||||||
|
const auto onion = pp::app::plan_animation_onion_size(3);
|
||||||
|
PP_REQUIRE(harness, onion);
|
||||||
|
PP_EXPECT(harness, pp::app::execute_animation_operation_plan(onion.value(), services).ok());
|
||||||
|
|
||||||
|
PP_EXPECT(harness, services.duration_sets == 1);
|
||||||
|
PP_EXPECT(harness, services.last_duration == 5);
|
||||||
|
PP_EXPECT(harness, services.moves == 1);
|
||||||
|
PP_EXPECT(harness, services.last_move_offset == 1);
|
||||||
|
PP_EXPECT(harness, services.gotos == 2);
|
||||||
|
PP_EXPECT(harness, services.last_target_frame == 0);
|
||||||
|
PP_EXPECT(harness, services.onion_sets == 1);
|
||||||
|
PP_EXPECT(harness, services.last_onion_size == 3);
|
||||||
|
PP_EXPECT(harness, services.unsaved_marks == 2);
|
||||||
|
PP_EXPECT(harness, services.canvas_updates == 2);
|
||||||
|
PP_EXPECT(harness, services.reloads == 3);
|
||||||
|
PP_EXPECT(
|
||||||
|
harness,
|
||||||
|
services.call_order == "duration;unsaved;update;reload;move;unsaved;goto;reload;goto;reload;onion;update;");
|
||||||
|
}
|
||||||
|
|
||||||
|
void executor_rejects_malformed_animation_plans(pp::tests::Harness& harness)
|
||||||
|
{
|
||||||
|
FakeDocumentAnimationServices services;
|
||||||
|
|
||||||
|
auto add = pp::app::plan_animation_add_frame(2, 0).value();
|
||||||
|
add.marks_unsaved = false;
|
||||||
|
PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(add, services).ok());
|
||||||
|
|
||||||
|
auto remove = pp::app::plan_animation_remove_frame(2, 1).value();
|
||||||
|
remove.frame_count = 1;
|
||||||
|
PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(remove, services).ok());
|
||||||
|
|
||||||
|
auto duration = pp::app::plan_animation_adjust_duration(2, 1, 4, 1).value();
|
||||||
|
duration.frame_duration = 0;
|
||||||
|
PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(duration, services).ok());
|
||||||
|
|
||||||
|
auto move = pp::app::plan_animation_move_frame(3, 1, 1).value();
|
||||||
|
move.move_offset = 0;
|
||||||
|
PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(move, services).ok());
|
||||||
|
|
||||||
|
auto go = pp::app::plan_animation_goto_frame(3, 1).value();
|
||||||
|
go.target_frame = 3;
|
||||||
|
PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(go, services).ok());
|
||||||
|
|
||||||
|
PP_EXPECT(harness, services.adds == 0);
|
||||||
|
PP_EXPECT(harness, services.duration_sets == 0);
|
||||||
|
PP_EXPECT(harness, services.gotos == 0);
|
||||||
|
PP_EXPECT(harness, services.call_order.empty());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
@@ -129,5 +322,8 @@ int main()
|
|||||||
harness.run("duration plans clamp floor and reject overflow", duration_plans_clamp_floor_and_reject_overflow);
|
harness.run("duration plans clamp floor and reject overflow", duration_plans_clamp_floor_and_reject_overflow);
|
||||||
harness.run("move and timeline plans handle edges", move_and_timeline_plans_handle_edges);
|
harness.run("move and timeline plans handle edges", move_and_timeline_plans_handle_edges);
|
||||||
harness.run("onion size updates canvas without document mutation", onion_size_updates_canvas_without_document_mutation);
|
harness.run("onion size updates canvas without document mutation", onion_size_updates_canvas_without_document_mutation);
|
||||||
|
harness.run("executor dispatches mutating frame operations", executor_dispatches_mutating_frame_operations);
|
||||||
|
harness.run("executor dispatches timeline and parameter operations", executor_dispatches_timeline_and_parameter_operations);
|
||||||
|
harness.run("executor rejects malformed animation plans", executor_rejects_malformed_animation_plans);
|
||||||
return harness.finish();
|
return harness.finish();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user