Route layer merge through app core
This commit is contained in:
@@ -49,7 +49,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| DEBT-0029 | Open | Modernization | Image import route planning and execution dispatch now consume pure `pp_app_core` through the File menu, `pano_cli plan-image-import`, and the `DocumentImageImportServices` boundary, but the live adapter still loads images with legacy `Image`, calls legacy `Canvas::import_equirectangular`, or configures legacy import transform mode directly | Preserve current File > Import behavior while image import moves toward document/app/asset command services | `pp_app_core_document_import_tests`; `pano_cli plan-image-import --width 4096 --height 2048`; `pano_cli plan-image-import --width 1024 --height 1024`; `ctest --preset desktop-fast --build-config Debug` | Image loading, equirectangular import, transform-placement import, and failure reporting are owned by injected document/app/asset services with File-menu callbacks acting only as adapters and no legacy image-import adapter |
|
||||
| DEBT-0030 | Open | Modernization | File export menu action planning and execution dispatch now consume pure `pp_app_core` through the File menu, `pano_cli plan-export-menu`, and the `DocumentExportMenuServices` boundary, but the live adapter still opens legacy export dialogs and then reaches legacy canvas/render/video export code | Preserve current export menu behavior while export command execution moves toward document/app/renderer/video services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind png`; `pano_cli plan-export-menu --kind animation-mp4 --demo`; `pano_cli plan-export-menu --kind layers --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Export menu routing, license gating, target creation, image/layer/cube/depth/animation/timelapse execution, and error reporting are owned by injected document/app/renderer/video services with File-menu callbacks acting only as UI adapters and no legacy export adapter |
|
||||
| DEBT-0031 | Open | Modernization | Top-level File menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_file`, `pano_cli plan-file-menu`, and the `FileMenuServices` boundary, but the live adapter still invokes legacy dialogs, platform pickers, cloud code, share code, and canvas import/export paths directly | Preserve File menu behavior while app workflows move toward app/document/platform command services | `pp_app_core_file_menu_tests`; `pano_cli plan-file-menu --command save-as`; `pano_cli plan-file-menu --command import`; `pano_cli plan-file-menu --command cloud-upload`; `ctest --preset desktop-fast --build-config Debug` | File menu routing, picker dispatch, save/share/cloud/resize/export execution, and image/project import execution are owned by injected app/document/platform services with `App::init_menu_file` acting only as a UI adapter and no legacy File menu adapter |
|
||||
| DEBT-0032 | Open | Modernization | Layer menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_layer`, `pano_cli plan-layer-menu`, and the `DocumentLayerMenuServices` boundary, and Layer menu clear now reuses the `DocumentCanvasClearServices` executor, but the live adapter still calls `App::dialog_layer_rename`, `NodePanelLayer::merge`, and reads `Canvas::I` animation/layer state directly | Preserve existing Layer menu behavior while layer commands move toward document/app services | `pp_app_core_document_layer_tests`; `pano_cli plan-layer-menu --command clear --current-index 1 --current-name Paint`; `pano_cli plan-layer-menu --command merge --current-index 2 --lower-name Paint`; `pano_cli plan-layer-menu --command rename --no-current-layer`; `ctest --preset desktop-fast --build-config Debug` | Layer rename, merge-down execution, animation gating, and selected-layer state are owned by injected document/app services with Layer-menu callbacks acting only as UI adapters and no legacy Layer menu adapter |
|
||||
| DEBT-0032 | Open | Modernization | Layer menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_layer`, `pano_cli plan-layer-menu`, and the `DocumentLayerMenuServices` boundary, Layer menu clear reuses the `DocumentCanvasClearServices` executor, and Layer menu merge validates/dispatches through `DocumentLayerMergeServices`, but the live adapter still calls `App::dialog_layer_rename`, `NodePanelLayer::merge`, and reads `Canvas::I` animation/layer state directly | Preserve existing Layer menu behavior while layer commands move toward document/app services | `pp_app_core_document_layer_tests`; `pano_cli plan-layer-menu --command clear --current-index 1 --current-name Paint`; `pano_cli plan-layer-menu --command merge --current-index 2 --lower-name Paint`; `pano_cli plan-layer-merge --layer-count 3 --from-index 2 --to-index 1`; `pano_cli plan-layer-merge --layer-count 3 --from-index 2 --to-index 1 --animation-duration 3`; `pano_cli plan-layer-menu --command rename --no-current-layer`; `ctest --preset desktop-fast --build-config Debug` | Layer rename, merge-down execution, animation gating, and selected-layer state are owned by injected document/app services with Layer-menu callbacks acting only as UI adapters and no legacy Layer menu adapter |
|
||||
| DEBT-0033 | Open | Modernization | Tools menu planning and direct command execution dispatch now consume pure `pp_app_core` through `App::init_menu_tools`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, and the `ToolsMenuServices` boundary, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, reset `NodeCanvas` camera state, open legacy shortcuts UI, and call the iOS SonarPen bridge directly | Preserve current Tools menu behavior while UI shell actions move toward app/UI/platform services | `pp_app_core_tools_menu_tests`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-tools-panel --panel animation --already-visible`; `ctest --preset desktop-fast --build-config Debug` | Tools panel creation, submenu routing, grid clear, camera reset, shortcuts dialog, and SonarPen dispatch are owned by injected app/UI/platform services with `App::init_menu_tools` acting only as a UI adapter and no legacy Tools adapter |
|
||||
| DEBT-0034 | Open | Modernization | About menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_about`, `pano_cli plan-about-menu`, and the `AboutMenuServices` boundary, but the live adapter still opens legacy About/manual/what's-new dialogs, invokes the injected crash hook, and runs the legacy Canvas stroke performance test directly | Preserve About menu behavior while dialogs and diagnostics move toward app/UI/platform services | `pp_app_core_about_menu_tests`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-about-menu --command performance --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | About/manual/what's-new dialog dispatch, crash-test dispatch, and performance-test execution are owned by injected app/UI/platform services with `App::init_menu_about` acting only as a UI adapter and no legacy About adapter |
|
||||
| DEBT-0035 | Open | Modernization | Main toolbar/status command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-main-toolbar`, and the `MainToolbarServices` boundary, and history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, but the live adapter still opens legacy open/save/settings/message-box dialogs and delegates to legacy history/canvas adapters | Preserve reachable toolbar/status behavior while app shell commands move toward app/document/UI services | `pp_app_core_main_toolbar_tests`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-main-toolbar --command clear-canvas --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Open/save/settings/message-box routing, undo/redo/clear-history execution, and canvas-clear execution are owned by injected app/document/UI services with `App::init_toolbar_main` acting only as a UI adapter and no legacy toolbar adapter |
|
||||
|
||||
@@ -501,6 +501,8 @@ rename, and merge-down labels/actions, and direct Layer menu commands now
|
||||
dispatch through `DocumentLayerMenuServices` before the legacy canvas/layer UI
|
||||
adapter continues execution. Layer menu clear now routes through the shared
|
||||
`DocumentCanvasClearServices` executor before the legacy canvas-clear adapter
|
||||
continues, and Layer menu merge now validates and dispatches through
|
||||
`DocumentLayerMergeServices` before the legacy layer-panel merge adapter
|
||||
continues.
|
||||
`pano_cli plan-animation-operation` exposes app-core planning for animation
|
||||
frame add, duplicate, remove, duration adjustment, timeline moves, timeline
|
||||
@@ -1249,8 +1251,9 @@ Results:
|
||||
layer operation side-effect dispatch, no-op operation preservation,
|
||||
malformed operation rejection, Layer menu labels/actions, merge-down routing,
|
||||
animated merge blocking, missing selection handling, bad Layer menu state
|
||||
rejection, Layer menu executor dispatch, and no-op menu execution
|
||||
preservation.
|
||||
rejection, Layer menu executor dispatch, no-op menu execution preservation,
|
||||
merge-plan validation, unsupported animated merge rejection, merge executor
|
||||
dispatch, and malformed merge-plan rejection.
|
||||
- `pano_cli_plan_layer_rename_smoke`,
|
||||
`pano_cli_plan_layer_rename_no_op_smoke`, and
|
||||
`pano_cli_plan_layer_rename_rejects_empty_name` passed and expose live
|
||||
@@ -1261,6 +1264,9 @@ Results:
|
||||
`pano_cli_plan_layer_menu_missing_selection_smoke`, and
|
||||
`pano_cli_plan_layer_menu_rejects_bad_state` passed and expose live Layer
|
||||
menu planning as JSON automation.
|
||||
- `pano_cli_plan_layer_merge_smoke` and
|
||||
`pano_cli_plan_layer_merge_animated_rejected` passed and expose live
|
||||
merge execution planning as JSON automation.
|
||||
- `pano_cli_plan_layer_operation_add_smoke`,
|
||||
`pano_cli_plan_layer_operation_reorder_no_op_smoke`,
|
||||
`pano_cli_plan_layer_operation_highlight_smoke`, and
|
||||
|
||||
@@ -78,6 +78,12 @@ struct DocumentLayerMenuPlan {
|
||||
int to_index = 0;
|
||||
};
|
||||
|
||||
struct DocumentLayerMergePlan {
|
||||
int from_index = 0;
|
||||
int to_index = 0;
|
||||
bool create_history = true;
|
||||
};
|
||||
|
||||
class DocumentLayerMenuServices {
|
||||
public:
|
||||
virtual ~DocumentLayerMenuServices() = default;
|
||||
@@ -115,6 +121,13 @@ public:
|
||||
virtual void update_title() = 0;
|
||||
};
|
||||
|
||||
class DocumentLayerMergeServices {
|
||||
public:
|
||||
virtual ~DocumentLayerMergeServices() = default;
|
||||
|
||||
virtual void merge_layers(int from_index, int to_index, bool create_history) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_layer_index(
|
||||
int layer_count,
|
||||
int index) noexcept
|
||||
@@ -447,6 +460,45 @@ public:
|
||||
return pp::foundation::Result<DocumentLayerMenuPlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerMergePlan> plan_document_layer_merge(
|
||||
int layer_count,
|
||||
int from_index,
|
||||
int to_index,
|
||||
int animation_duration,
|
||||
bool create_history = true)
|
||||
{
|
||||
auto index_status = validate_layer_index(layer_count, from_index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::failure(index_status);
|
||||
}
|
||||
|
||||
index_status = validate_layer_index(layer_count, to_index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::failure(index_status);
|
||||
}
|
||||
|
||||
if (animation_duration < 0) {
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::failure(
|
||||
pp::foundation::Status::out_of_range("animation duration must not be negative"));
|
||||
}
|
||||
|
||||
if (animation_duration > 1) {
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("animated layer merge is not supported"));
|
||||
}
|
||||
|
||||
if (from_index <= to_index) {
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("layer merge source must be above the destination"));
|
||||
}
|
||||
|
||||
DocumentLayerMergePlan plan;
|
||||
plan.from_index = from_index;
|
||||
plan.to_index = to_index;
|
||||
plan.create_history = create_history;
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_layer_rename_plan(
|
||||
const DocumentLayerRenamePlan& plan,
|
||||
DocumentLayerRenameServices& services)
|
||||
@@ -545,6 +597,19 @@ inline void execute_document_layer_operation_side_effects(
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_layer_merge_plan(
|
||||
const DocumentLayerMergePlan& plan,
|
||||
DocumentLayerMergeServices& services)
|
||||
{
|
||||
if (plan.from_index <= plan.to_index) {
|
||||
return pp::foundation::Status::invalid_argument(
|
||||
"layer merge source must be above the destination");
|
||||
}
|
||||
|
||||
services.merge_layers(plan.from_index, plan.to_index, plan.create_history);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_layer_menu_plan(
|
||||
const DocumentLayerMenuPlan& plan,
|
||||
DocumentLayerMenuServices& services)
|
||||
|
||||
@@ -167,6 +167,8 @@ void execute_document_canvas_clear_plan(App& app, const pp::app::DocumentCanvasC
|
||||
LOG("Canvas clear failed: %s", status.message);
|
||||
}
|
||||
|
||||
void execute_document_layer_merge_plan(App& app, const pp::app::DocumentLayerMergePlan& plan);
|
||||
|
||||
bool apply_document_export_menu_plan(App& app, pp::app::DocumentExportMenuKind kind)
|
||||
{
|
||||
class LegacyDocumentExportMenuServices final : public pp::app::DocumentExportMenuServices {
|
||||
@@ -605,8 +607,21 @@ public:
|
||||
|
||||
void merge_with_lower_layer(int from_index, int to_index) override
|
||||
{
|
||||
if (app_.layers)
|
||||
app_.layers->merge(from_index, to_index, true);
|
||||
const int layer_count = app_.canvas && app_.canvas->m_canvas
|
||||
? static_cast<int>(app_.canvas->m_canvas->m_layers.size())
|
||||
: 0;
|
||||
const int animation_duration = Canvas::I
|
||||
? Canvas::I->anim_duration()
|
||||
: 0;
|
||||
const auto plan = pp::app::plan_document_layer_merge(
|
||||
layer_count,
|
||||
from_index,
|
||||
to_index,
|
||||
animation_duration);
|
||||
if (!plan)
|
||||
return;
|
||||
|
||||
execute_document_layer_merge_plan(app_, plan.value());
|
||||
}
|
||||
|
||||
void show_merge_animated_not_supported() override
|
||||
@@ -618,6 +633,23 @@ private:
|
||||
App& app_;
|
||||
};
|
||||
|
||||
class LegacyDocumentLayerMergeServices final : public pp::app::DocumentLayerMergeServices {
|
||||
public:
|
||||
explicit LegacyDocumentLayerMergeServices(App& app) noexcept
|
||||
: app_(app)
|
||||
{
|
||||
}
|
||||
|
||||
void merge_layers(int from_index, int to_index, bool create_history) override
|
||||
{
|
||||
if (app_.layers)
|
||||
app_.layers->merge(from_index, to_index, create_history);
|
||||
}
|
||||
|
||||
private:
|
||||
App& app_;
|
||||
};
|
||||
|
||||
class LegacyDocumentLayerOperationServices final : public pp::app::DocumentLayerOperationServices {
|
||||
public:
|
||||
LegacyDocumentLayerOperationServices(
|
||||
@@ -786,6 +818,14 @@ void execute_document_layer_menu_plan(App& app, const pp::app::DocumentLayerMenu
|
||||
LOG("Layer menu action failed: %s", status.message);
|
||||
}
|
||||
|
||||
void execute_document_layer_merge_plan(App& app, const pp::app::DocumentLayerMergePlan& plan)
|
||||
{
|
||||
LegacyDocumentLayerMergeServices services(app);
|
||||
const auto status = pp::app::execute_document_layer_merge_plan(plan, services);
|
||||
if (!status.ok())
|
||||
LOG("Layer merge failed: %s", status.message);
|
||||
}
|
||||
|
||||
void execute_document_layer_operation_plan(
|
||||
App& app,
|
||||
const pp::app::DocumentLayerOperationPlan& plan,
|
||||
|
||||
@@ -1016,6 +1016,18 @@ if(TARGET pano_cli)
|
||||
LABELS "app;document;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_layer_merge_smoke
|
||||
COMMAND pano_cli plan-layer-merge --layer-count 3 --from-index 2 --to-index 1)
|
||||
set_tests_properties(pano_cli_plan_layer_merge_smoke PROPERTIES
|
||||
LABELS "app;document;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-layer-merge\".*\"layerCount\":3.*\"fromIndex\":2.*\"toIndex\":1.*\"createHistory\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_layer_merge_animated_rejected
|
||||
COMMAND pano_cli plan-layer-merge --layer-count 3 --from-index 2 --to-index 1 --animation-duration 3)
|
||||
set_tests_properties(pano_cli_plan_layer_merge_animated_rejected PROPERTIES
|
||||
LABELS "app;document;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_layer_operation_add_smoke
|
||||
COMMAND pano_cli plan-layer-operation --kind add --layer-count 2 --index 1 --name Paint)
|
||||
set_tests_properties(pano_cli_plan_layer_operation_add_smoke PROPERTIES
|
||||
|
||||
@@ -152,6 +152,22 @@ public:
|
||||
std::string last_name;
|
||||
};
|
||||
|
||||
class FakeDocumentLayerMergeServices final : public pp::app::DocumentLayerMergeServices {
|
||||
public:
|
||||
void merge_layers(int from_index, int to_index, bool create_history) override
|
||||
{
|
||||
merge_calls += 1;
|
||||
last_from_index = from_index;
|
||||
last_to_index = to_index;
|
||||
last_create_history = create_history;
|
||||
}
|
||||
|
||||
int merge_calls = 0;
|
||||
int last_from_index = -1;
|
||||
int last_to_index = -1;
|
||||
bool last_create_history = false;
|
||||
};
|
||||
|
||||
void layer_rename_records_changed_name(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_document_layer_rename("Base", "Paint");
|
||||
@@ -724,6 +740,63 @@ void layer_menu_executor_preserves_no_op_actions(pp::tests::Harness& harness)
|
||||
PP_EXPECT(harness, services.total_calls() == 0);
|
||||
}
|
||||
|
||||
void layer_merge_plan_validates_supported_merge(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_document_layer_merge(3, 2, 1, 1);
|
||||
PP_EXPECT(harness, plan);
|
||||
if (plan) {
|
||||
PP_EXPECT(harness, plan.value().from_index == 2);
|
||||
PP_EXPECT(harness, plan.value().to_index == 1);
|
||||
PP_EXPECT(harness, plan.value().create_history);
|
||||
}
|
||||
|
||||
const auto no_history = pp::app::plan_document_layer_merge(3, 2, 0, 1, false);
|
||||
PP_EXPECT(harness, no_history);
|
||||
if (no_history) {
|
||||
PP_EXPECT(harness, !no_history.value().create_history);
|
||||
}
|
||||
}
|
||||
|
||||
void layer_merge_plan_rejects_bad_or_unsupported_state(pp::tests::Harness& harness)
|
||||
{
|
||||
PP_EXPECT(harness, !pp::app::plan_document_layer_merge(0, 0, 0, 1));
|
||||
PP_EXPECT(harness, !pp::app::plan_document_layer_merge(3, 3, 1, 1));
|
||||
PP_EXPECT(harness, !pp::app::plan_document_layer_merge(3, 1, 3, 1));
|
||||
PP_EXPECT(harness, !pp::app::plan_document_layer_merge(3, 1, 1, 1));
|
||||
PP_EXPECT(harness, !pp::app::plan_document_layer_merge(3, 0, 1, 1));
|
||||
PP_EXPECT(harness, !pp::app::plan_document_layer_merge(3, 2, 1, -1));
|
||||
PP_EXPECT(harness, !pp::app::plan_document_layer_merge(3, 2, 1, 2));
|
||||
}
|
||||
|
||||
void layer_merge_executor_dispatches_merge(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeDocumentLayerMergeServices services;
|
||||
|
||||
const auto plan = pp::app::plan_document_layer_merge(3, 2, 1, 1);
|
||||
PP_EXPECT(harness, plan);
|
||||
if (plan) {
|
||||
PP_EXPECT(harness, pp::app::execute_document_layer_merge_plan(plan.value(), services).ok());
|
||||
PP_EXPECT(harness, services.merge_calls == 1);
|
||||
PP_EXPECT(harness, services.last_from_index == 2);
|
||||
PP_EXPECT(harness, services.last_to_index == 1);
|
||||
PP_EXPECT(harness, services.last_create_history);
|
||||
}
|
||||
}
|
||||
|
||||
void layer_merge_executor_rejects_malformed_plan(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeDocumentLayerMergeServices services;
|
||||
|
||||
pp::app::DocumentLayerMergePlan malformed;
|
||||
malformed.from_index = 1;
|
||||
malformed.to_index = 1;
|
||||
|
||||
const auto status = pp::app::execute_document_layer_merge_plan(malformed, services);
|
||||
PP_EXPECT(harness, !status.ok());
|
||||
PP_EXPECT(harness, status.code == pp::foundation::StatusCode::invalid_argument);
|
||||
PP_EXPECT(harness, services.merge_calls == 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -750,5 +823,9 @@ int main()
|
||||
harness.run("layer menu handles missing selection and bad state", layer_menu_handles_missing_selection_and_bad_state);
|
||||
harness.run("layer menu executor dispatches menu actions", layer_menu_executor_dispatches_menu_actions);
|
||||
harness.run("layer menu executor preserves no op actions", layer_menu_executor_preserves_no_op_actions);
|
||||
harness.run("layer merge plan validates supported merge", layer_merge_plan_validates_supported_merge);
|
||||
harness.run("layer merge plan rejects bad or unsupported state", layer_merge_plan_rejects_bad_or_unsupported_state);
|
||||
harness.run("layer merge executor dispatches merge", layer_merge_executor_dispatches_merge);
|
||||
harness.run("layer merge executor rejects malformed plan", layer_merge_executor_rejects_malformed_plan);
|
||||
return harness.finish();
|
||||
}
|
||||
|
||||
@@ -287,6 +287,14 @@ struct PlanLayerMenuArgs {
|
||||
std::string lower_name = "Layer 0";
|
||||
};
|
||||
|
||||
struct PlanLayerMergeArgs {
|
||||
int layer_count = 2;
|
||||
int from_index = 1;
|
||||
int to_index = 0;
|
||||
int animation_duration = 1;
|
||||
bool create_history = true;
|
||||
};
|
||||
|
||||
struct PlanAnimationOperationArgs {
|
||||
std::string kind = "goto";
|
||||
int frame_count = 1;
|
||||
@@ -1779,6 +1787,7 @@ void print_help()
|
||||
<< " plan-document-resize [--current-resolution N] [--selected-resolution-index N]\n"
|
||||
<< " 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-merge [--layer-count N] [--from-index N] [--to-index N] [--animation-duration N] [--no-history]\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|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-animation-panel-action --action goto|next|prev|playback|toggle-playback [--total-duration N] [--current-frame N] [--target-frame N] [--playing]\n"
|
||||
@@ -3959,6 +3968,75 @@ int plan_layer_menu(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_layer_merge_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
PlanLayerMergeArgs& args)
|
||||
{
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const std::string_view key(argv[i]);
|
||||
if (key == "--layer-count" || key == "--from-index" || key == "--to-index"
|
||||
|| key == "--animation-duration") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
const auto value = parse_i32_arg(argv[++i]);
|
||||
if (!value) {
|
||||
return value.status();
|
||||
}
|
||||
if (key == "--layer-count") {
|
||||
args.layer_count = value.value();
|
||||
} else if (key == "--from-index") {
|
||||
args.from_index = value.value();
|
||||
} else if (key == "--to-index") {
|
||||
args.to_index = value.value();
|
||||
} else {
|
||||
args.animation_duration = value.value();
|
||||
}
|
||||
} else if (key == "--no-history") {
|
||||
args.create_history = false;
|
||||
} else {
|
||||
return pp::foundation::Status::invalid_argument("unknown option");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
int plan_layer_merge(int argc, char** argv)
|
||||
{
|
||||
PlanLayerMergeArgs args;
|
||||
const auto status = parse_plan_layer_merge_args(argc, argv, args);
|
||||
if (!status.ok()) {
|
||||
print_error("plan-layer-merge", status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto plan = pp::app::plan_document_layer_merge(
|
||||
args.layer_count,
|
||||
args.from_index,
|
||||
args.to_index,
|
||||
args.animation_duration,
|
||||
args.create_history);
|
||||
if (!plan) {
|
||||
print_error("plan-layer-merge", plan.status().message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto& value = plan.value();
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-layer-merge\""
|
||||
<< ",\"state\":{\"layerCount\":" << args.layer_count
|
||||
<< ",\"fromIndex\":" << args.from_index
|
||||
<< ",\"toIndex\":" << args.to_index
|
||||
<< ",\"animationDuration\":" << args.animation_duration
|
||||
<< ",\"createHistory\":" << json_bool(args.create_history)
|
||||
<< "},\"plan\":{\"fromIndex\":" << value.from_index
|
||||
<< ",\"toIndex\":" << value.to_index
|
||||
<< ",\"createHistory\":" << json_bool(value.create_history)
|
||||
<< "}}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_layer_operation_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
@@ -8215,6 +8293,10 @@ int main(int argc, char** argv)
|
||||
return plan_layer_menu(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-layer-merge") {
|
||||
return plan_layer_merge(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-layer-operation") {
|
||||
return plan_layer_operation(argc, argv);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user