diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index b5cdd24..928d266 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -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-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-0022 | Open | Modernization | Animation panel frame command planning, panel-control/timeline execution dispatch, selected-frame click dispatch, and playback tick stepping 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 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 select --frame-count 3 --selected-frame 1 --layer-index 2 --layer-id 42`; `pano_cli plan-animation-operation --kind playback --total-duration 5 --current-frame 4 --offset 1`; `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-0022 | Open | Modernization | Animation panel frame command planning, panel-control/timeline execution dispatch, selected-frame click dispatch, playback tick stepping, and play-mode toggles 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 the live adapter still mutates or reads legacy `Canvas`/`Layer` frame state and canvas mode 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 select --frame-count 3 --selected-frame 1 --layer-index 2 --layer-id 42`; `pano_cli plan-animation-operation --kind playback --total-duration 5 --current-frame 4 --offset 1`; `pano_cli plan-animation-operation --kind toggle-playback --playing`; `ctest --preset desktop-fast --build-config Debug` | Animation frame/timeline/playback execution is owned by injected document/app timeline services with no legacy `Canvas`/`Layer`/canvas-mode 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-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 | diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index d776aad..5dbea82 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -498,11 +498,11 @@ dispatch through `DocumentLayerMenuServices` before the legacy canvas/layer UI adapter continues execution. `pano_cli plan-animation-operation` exposes app-core planning for animation frame add, duplicate, remove, duration adjustment, timeline moves, timeline -goto/next/previous, and onion-size updates used by the live animation panel -plus frame selection and no-reload playback stepping. Panel-control, -timeline, selected-frame click, and playback tick execution now dispatch -through `DocumentAnimationServices` before the legacy `Canvas`/`Layer` adapter -continues. +goto/next/previous, onion-size updates, frame selection, no-reload playback +stepping, and play-mode toggles used by the live animation panel. +Panel-control, timeline, selected-frame click, playback tick, and play-button +toggle execution now dispatch through `DocumentAnimationServices` before the +legacy `Canvas`/`Layer`/canvas-mode adapter continues. `pano_cli plan-brush-operation` exposes app-core planning for brush color changes, tip/pattern/dual texture changes, preset brush replacement, and stroke settings refreshes used by the live brush, quick, color, and floating panel @@ -1205,12 +1205,15 @@ Results: rejection, duration floor/overflow handling, timeline move edge behavior, goto/next/previous wrapping, onion-size rejection, service dispatch ordering, frame-click selection planning, no-reload playback step planning, - non-mutating duration no-ops, and malformed execution payload rejection. + playback toggle start/stop planning, non-mutating duration no-ops, and + malformed execution payload rejection. - `pano_cli_plan_animation_operation_add_smoke`, `pano_cli_plan_animation_operation_duration_floor_smoke`, `pano_cli_plan_animation_operation_next_wrap_smoke`, `pano_cli_plan_animation_operation_select_smoke`, `pano_cli_plan_animation_operation_playback_smoke`, + `pano_cli_plan_animation_operation_toggle_playback_start_smoke`, + `pano_cli_plan_animation_operation_toggle_playback_stop_smoke`, `pano_cli_plan_animation_operation_rejects_remove_last_frame`, and `pano_cli_plan_animation_operation_rejects_bad_selection` passed and expose live animation-panel planning as JSON automation. diff --git a/src/app_core/document_animation.h b/src/app_core/document_animation.h index 599f33d..a0eb41e 100644 --- a/src/app_core/document_animation.h +++ b/src/app_core/document_animation.h @@ -21,6 +21,7 @@ enum class DocumentAnimationOperation { goto_next, goto_previous, playback_step, + toggle_playback, set_onion_size, }; @@ -36,11 +37,15 @@ struct DocumentAnimationOperationPlan { int onion_size = 1; int layer_index = 0; std::uint32_t layer_id = 0; + int playback_idle_ms = 100; bool requires_selected_frame = false; bool mutates_document = false; bool reloads_animation_layers = false; bool updates_canvas_animation = false; bool marks_unsaved = false; + bool playback_was_active = false; + bool playback_active = false; + bool resets_playback_timer = false; }; class DocumentAnimationServices { @@ -57,6 +62,12 @@ public: virtual void goto_frame(int target_frame) = 0; virtual void set_timeline_frame(int target_frame) = 0; virtual void set_onion_size(int onion_size) = 0; + virtual void capture_playback_restore_mode() = 0; + virtual void enter_playback_camera_mode() = 0; + virtual void restore_playback_canvas_mode() = 0; + virtual void set_playback_active(bool active) = 0; + virtual void reset_playback_timer() = 0; + virtual void set_playback_idle_ms(int idle_ms) = 0; virtual void update_canvas_animation() = 0; virtual void update_frame_status() = 0; virtual void reload_animation_layers() = 0; @@ -343,6 +354,18 @@ public: return pp::foundation::Result::success(plan); } +[[nodiscard]] inline pp::foundation::Result plan_animation_playback_toggle( + bool playback_active) +{ + DocumentAnimationOperationPlan plan; + plan.operation = DocumentAnimationOperation::toggle_playback; + plan.playback_was_active = playback_active; + plan.playback_active = !playback_active; + plan.playback_idle_ms = playback_active ? 100 : 10; + plan.resets_playback_timer = !playback_active; + return pp::foundation::Result::success(plan); +} + [[nodiscard]] inline pp::foundation::Result plan_animation_onion_size(int onion_size) { if (onion_size < 0) { @@ -419,6 +442,18 @@ public: } return validate_animation_frame_index(plan.frame_count, plan.target_frame); + case DocumentAnimationOperation::toggle_playback: + if (plan.playback_active == plan.playback_was_active) { + return pp::foundation::Status::invalid_argument("animation playback toggle must change state"); + } + if (plan.playback_idle_ms <= 0) { + return pp::foundation::Status::invalid_argument("animation playback idle interval must be positive"); + } + if (plan.playback_active && !plan.resets_playback_timer) { + return pp::foundation::Status::invalid_argument("animation playback start must reset timer"); + } + return pp::foundation::Status::success(); + case DocumentAnimationOperation::set_onion_size: if (plan.onion_size < 0) { return pp::foundation::Status::invalid_argument("animation onion size must not be negative"); @@ -517,6 +552,20 @@ public: services.update_frame_status(); return pp::foundation::Status::success(); + case DocumentAnimationOperation::toggle_playback: + if (plan.playback_active) { + services.capture_playback_restore_mode(); + services.enter_playback_camera_mode(); + if (plan.resets_playback_timer) { + services.reset_playback_timer(); + } + } else { + services.restore_playback_canvas_mode(); + } + services.set_playback_active(plan.playback_active); + services.set_playback_idle_ms(plan.playback_idle_ms); + return pp::foundation::Status::success(); + case DocumentAnimationOperation::set_onion_size: services.set_onion_size(plan.onion_size); if (plan.updates_canvas_animation) { diff --git a/src/node_panel_animation.cpp b/src/node_panel_animation.cpp index 22d080e..5ecd033 100644 --- a/src/node_panel_animation.cpp +++ b/src/node_panel_animation.cpp @@ -96,6 +96,36 @@ void NodePanelAnimation::execute_animation_plan(const pp::app::DocumentAnimation 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(); @@ -117,6 +147,12 @@ void NodePanelAnimation::execute_animation_plan(const pp::app::DocumentAnimation } private: + static kCanvasMode& playback_restore_mode() + { + static auto mode = Canvas::I->m_current_mode; + return mode; + } + NodePanelAnimation& panel_; Layer* layer_ = nullptr; }; @@ -258,23 +294,10 @@ void NodePanelAnimation::init_controls() return; execute_animation_plan(plan.value()); }; - btn_play->on_click = [this] (Node* target) { - static auto mode = Canvas::I->m_current_mode; - auto b = static_cast(target); - if (b->is_active()) - { - Canvas::set_mode(mode); - b->set_active(false); - App::I->idle_ms = 100; - } - else - { - mode = Canvas::I->m_current_mode; - Canvas::set_mode(kCanvasMode::Camera); - m_playback_timer = 0; - b->set_active(true); - App::I->idle_ms = 10; - } + btn_play->on_click = [this] (Node*) { + const auto plan = pp::app::plan_animation_playback_toggle(btn_play->is_active()); + if (plan) + execute_animation_plan(plan.value()); }; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a6dfd65..8f6e110 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1060,6 +1060,18 @@ if(TARGET pano_cli) LABELS "app;integration;desktop-fast" PASS_REGULAR_EXPRESSION "\"command\":\"plan-animation-operation\".*\"operation\":\"playback-step\".*\"currentFrame\":4.*\"targetFrame\":0.*\"reloadsAnimationLayers\":false.*\"updatesCanvasAnimation\":true") + add_test(NAME pano_cli_plan_animation_operation_toggle_playback_start_smoke + COMMAND pano_cli plan-animation-operation --kind toggle-playback) + set_tests_properties(pano_cli_plan_animation_operation_toggle_playback_start_smoke PROPERTIES + LABELS "app;integration;desktop-fast" + PASS_REGULAR_EXPRESSION "\"command\":\"plan-animation-operation\".*\"operation\":\"toggle-playback\".*\"playbackIdleMs\":10.*\"playbackWasActive\":false.*\"playbackActive\":true.*\"resetsPlaybackTimer\":true") + + add_test(NAME pano_cli_plan_animation_operation_toggle_playback_stop_smoke + COMMAND pano_cli plan-animation-operation --kind toggle-playback --playing) + set_tests_properties(pano_cli_plan_animation_operation_toggle_playback_stop_smoke PROPERTIES + LABELS "app;integration;desktop-fast" + PASS_REGULAR_EXPRESSION "\"command\":\"plan-animation-operation\".*\"operation\":\"toggle-playback\".*\"playbackIdleMs\":100.*\"playbackWasActive\":true.*\"playbackActive\":false.*\"resetsPlaybackTimer\":false") + add_test(NAME pano_cli_plan_animation_operation_rejects_remove_last_frame COMMAND pano_cli plan-animation-operation --kind remove --frame-count 1 --selected-frame 0) set_tests_properties(pano_cli_plan_animation_operation_rejects_remove_last_frame PROPERTIES diff --git a/tests/app_core/document_animation_tests.cpp b/tests/app_core/document_animation_tests.cpp index 05c550b..fb0a3e9 100644 --- a/tests/app_core/document_animation_tests.cpp +++ b/tests/app_core/document_animation_tests.cpp @@ -92,6 +92,44 @@ public: call_order += "onion;"; } + void capture_playback_restore_mode() override + { + playback_restore_captures += 1; + call_order += "capture-mode;"; + } + + void enter_playback_camera_mode() override + { + playback_camera_entries += 1; + call_order += "camera-mode;"; + } + + void restore_playback_canvas_mode() override + { + playback_mode_restores += 1; + call_order += "restore-mode;"; + } + + void set_playback_active(bool active) override + { + playback_active_sets += 1; + last_playback_active = active; + call_order += active ? "play-active;" : "play-inactive;"; + } + + void reset_playback_timer() override + { + playback_timer_resets += 1; + call_order += "reset-timer;"; + } + + void set_playback_idle_ms(int idle_ms) override + { + playback_idle_sets += 1; + last_playback_idle_ms = idle_ms; + call_order += "idle;"; + } + void update_canvas_animation() override { canvas_updates += 1; @@ -126,6 +164,12 @@ public: int gotos = 0; int timeline_sets = 0; int onion_sets = 0; + int playback_restore_captures = 0; + int playback_camera_entries = 0; + int playback_mode_restores = 0; + int playback_active_sets = 0; + int playback_timer_resets = 0; + int playback_idle_sets = 0; int canvas_updates = 0; int frame_status_updates = 0; int reloads = 0; @@ -137,7 +181,9 @@ public: int last_move_offset = 0; int last_onion_size = -1; int last_layer_index = -1; + int last_playback_idle_ms = 0; std::uint32_t last_layer_id = 0; + bool last_playback_active = false; int move_result = 0; std::string call_order; }; @@ -263,6 +309,25 @@ void frame_selection_and_playback_plans_keep_ui_refresh_scoped(pp::tests::Harnes PP_EXPECT(harness, !pp::app::plan_animation_playback_step(3, 0, 0)); } +void playback_toggle_plans_capture_start_and_stop_intent(pp::tests::Harness& harness) +{ + const auto start = pp::app::plan_animation_playback_toggle(false); + PP_REQUIRE(harness, start); + PP_EXPECT(harness, start.value().operation == pp::app::DocumentAnimationOperation::toggle_playback); + PP_EXPECT(harness, !start.value().playback_was_active); + PP_EXPECT(harness, start.value().playback_active); + PP_EXPECT(harness, start.value().resets_playback_timer); + PP_EXPECT(harness, start.value().playback_idle_ms == 10); + + const auto stop = pp::app::plan_animation_playback_toggle(true); + PP_REQUIRE(harness, stop); + PP_EXPECT(harness, stop.value().operation == pp::app::DocumentAnimationOperation::toggle_playback); + PP_EXPECT(harness, stop.value().playback_was_active); + PP_EXPECT(harness, !stop.value().playback_active); + PP_EXPECT(harness, !stop.value().resets_playback_timer); + PP_EXPECT(harness, stop.value().playback_idle_ms == 100); +} + void onion_size_updates_canvas_without_document_mutation(pp::tests::Harness& harness) { const auto plan = pp::app::plan_animation_onion_size(2); @@ -339,6 +404,14 @@ void executor_dispatches_timeline_and_parameter_operations(pp::tests::Harness& h PP_REQUIRE(harness, playback); PP_EXPECT(harness, pp::app::execute_animation_operation_plan(playback.value(), services).ok()); + const auto play_start = pp::app::plan_animation_playback_toggle(false); + PP_REQUIRE(harness, play_start); + PP_EXPECT(harness, pp::app::execute_animation_operation_plan(play_start.value(), services).ok()); + + const auto play_stop = pp::app::plan_animation_playback_toggle(true); + PP_REQUIRE(harness, play_stop); + PP_EXPECT(harness, pp::app::execute_animation_operation_plan(play_stop.value(), services).ok()); + PP_EXPECT(harness, services.duration_sets == 1); PP_EXPECT(harness, services.last_duration == 5); PP_EXPECT(harness, services.moves == 1); @@ -357,11 +430,20 @@ void executor_dispatches_timeline_and_parameter_operations(pp::tests::Harness& h PP_EXPECT(harness, services.canvas_updates == 2); PP_EXPECT(harness, services.frame_status_updates == 1); PP_EXPECT(harness, services.reloads == 3); + PP_EXPECT(harness, services.playback_restore_captures == 1); + PP_EXPECT(harness, services.playback_camera_entries == 1); + PP_EXPECT(harness, services.playback_mode_restores == 1); + PP_EXPECT(harness, services.playback_active_sets == 2); + PP_EXPECT(harness, services.playback_timer_resets == 1); + PP_EXPECT(harness, services.playback_idle_sets == 2); + PP_EXPECT(harness, !services.last_playback_active); + PP_EXPECT(harness, services.last_playback_idle_ms == 100); PP_EXPECT( harness, services.call_order == "duration;unsaved;update;reload;move;unsaved;goto;reload;goto;reload;onion;update;" - "select-frame;goto;select-layer;goto;timeline;frame-status;"); + "select-frame;goto;select-layer;goto;timeline;frame-status;" + "capture-mode;camera-mode;reset-timer;play-active;idle;restore-mode;play-inactive;idle;"); } void executor_rejects_malformed_animation_plans(pp::tests::Harness& harness) @@ -396,6 +478,14 @@ void executor_rejects_malformed_animation_plans(pp::tests::Harness& harness) playback.move_offset = 0; PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(playback, services).ok()); + auto toggle = pp::app::plan_animation_playback_toggle(false).value(); + toggle.playback_active = false; + PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(toggle, services).ok()); + + toggle = pp::app::plan_animation_playback_toggle(false).value(); + toggle.playback_idle_ms = 0; + PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(toggle, services).ok()); + PP_EXPECT(harness, services.adds == 0); PP_EXPECT(harness, services.duration_sets == 0); PP_EXPECT(harness, services.gotos == 0); @@ -411,6 +501,7 @@ int main() 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("frame selection and playback plans keep ui refresh scoped", frame_selection_and_playback_plans_keep_ui_refresh_scoped); + harness.run("playback toggle plans capture start and stop intent", playback_toggle_plans_capture_start_and_stop_intent); 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); diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index bfd5c9c..7ed0970 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -294,6 +294,7 @@ struct PlanAnimationOperationArgs { int delta = 1; int offset = 1; int onion_size = 1; + bool playback_active = false; }; struct PlanBrushOperationArgs { @@ -993,6 +994,8 @@ const char* document_animation_operation_name(pp::app::DocumentAnimationOperatio return "goto-previous"; case pp::app::DocumentAnimationOperation::playback_step: return "playback-step"; + case pp::app::DocumentAnimationOperation::toggle_playback: + return "toggle-playback"; case pp::app::DocumentAnimationOperation::set_onion_size: return "set-onion-size"; } @@ -1553,7 +1556,7 @@ void print_help() << " plan-layer-rename --old-name NAME --new-name NAME\n" << " plan-layer-menu --command clear|rename|merge [--no-current-layer] [--current-index N] [--animation-duration N] [--current-name NAME] [--lower-name NAME]\n" << " plan-layer-operation --kind add|duplicate|select|reorder|remove|opacity|visibility|alpha-lock|blend-mode|highlight [--layer-count N] [--index N] [--from-index N] [--to-index N] [--source-index N] [--name NAME] [--opacity N] [--blend-mode N] [--enabled]\n" - << " plan-animation-operation --kind add|duplicate|remove|duration|move|select|goto|next|prev|playback|onion [--frame-count N] [--total-duration N] [--current-frame N] [--selected-frame N] [--layer-index N] [--layer-id N] [--current-duration N] [--delta N] [--offset N] [--onion-size N]\n" + << " plan-animation-operation --kind add|duplicate|remove|duration|move|select|goto|next|prev|playback|toggle-playback|onion [--frame-count N] [--total-duration N] [--current-frame N] [--selected-frame N] [--layer-index N] [--layer-id N] [--current-duration N] [--delta N] [--offset N] [--onion-size N] [--playing]\n" << " plan-brush-operation --kind color|tip|pattern|dual|preset|settings [--path FILE] [--thumb FILE] [--r N] [--g N] [--b N] [--a N] [--no-brush]\n" << " plan-canvas-tool --kind draw|erase|line|camera|grid|copy|cut|fill|mask-free|mask-line|bucket|pick|touch-lock [--current-mode-draw]\n" << " plan-canvas-tool-state [--mode draw|erase|line|camera|grid|copy|cut|fill|mask-free|mask-line|bucket] [--picking] [--touch-lock]\n" @@ -3857,6 +3860,8 @@ pp::foundation::Status parse_plan_animation_operation_args( return pp::foundation::Status::invalid_argument("missing value for option"); } args.kind = argv[++i]; + } else if (key == "--playing") { + args.playback_active = true; } else if (key == "--frame-count" || key == "--total-duration" || key == "--current-frame" || key == "--selected-frame" || key == "--current-duration" || key == "--delta" || key == "--offset" || key == "--onion-size" || key == "--layer-index" @@ -3941,6 +3946,9 @@ pp::foundation::Result make_animation_o if (args.kind == "playback") { return pp::app::plan_animation_playback_step(args.total_duration, args.current_frame, args.offset); } + if (args.kind == "toggle-playback" || args.kind == "play-toggle") { + return pp::app::plan_animation_playback_toggle(args.playback_active); + } if (args.kind == "onion") { return pp::app::plan_animation_onion_size(args.onion_size); } @@ -3977,6 +3985,7 @@ int plan_animation_operation(int argc, char** argv) << ",\"delta\":" << args.delta << ",\"offset\":" << args.offset << ",\"onionSize\":" << args.onion_size + << ",\"playbackActive\":" << json_bool(args.playback_active) << "},\"plan\":{\"operation\":\"" << document_animation_operation_name(value.operation) << "\",\"frameCount\":" << value.frame_count << ",\"currentFrame\":" << value.current_frame @@ -3988,11 +3997,15 @@ int plan_animation_operation(int argc, char** argv) << ",\"durationDelta\":" << value.duration_delta << ",\"moveOffset\":" << value.move_offset << ",\"onionSize\":" << value.onion_size + << ",\"playbackIdleMs\":" << value.playback_idle_ms << ",\"requiresSelectedFrame\":" << json_bool(value.requires_selected_frame) << ",\"mutatesDocument\":" << json_bool(value.mutates_document) << ",\"reloadsAnimationLayers\":" << json_bool(value.reloads_animation_layers) << ",\"updatesCanvasAnimation\":" << json_bool(value.updates_canvas_animation) << ",\"marksUnsaved\":" << json_bool(value.marks_unsaved) + << ",\"playbackWasActive\":" << json_bool(value.playback_was_active) + << ",\"playbackActive\":" << json_bool(value.playback_active) + << ",\"resetsPlaybackTimer\":" << json_bool(value.resets_playback_timer) << "}}\n"; return 0; }