Add file menu service boundary
This commit is contained in:
@@ -48,7 +48,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| DEBT-0028 | Open | Modernization | Canvas clear command planning now consumes pure `pp_app_core` through `App::init_toolbar_main` and `pano_cli plan-canvas-clear`, but live execution still calls legacy `Canvas::clear`, which records `ActionLayerClear`, clears the current layer/frame, and marks legacy `Canvas::I` unsaved directly | Preserve clear-current-layer behavior while canvas/document commands move toward document/app command services | `pp_app_core_document_canvas_tests`; `pano_cli plan-canvas-clear --r 0 --g 0.1 --b 0.2 --a 0.3`; `pano_cli plan-canvas-clear --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Canvas clear execution, undo recording, dirty-state updates, and clear color handling are owned by document/app services with toolbar callbacks acting only as adapters |
|
||||
| DEBT-0029 | Open | Modernization | Image import route planning now consumes pure `pp_app_core` through the File menu and `pano_cli plan-image-import`, but live execution still calls legacy `Canvas::import_equirectangular` or legacy import transform mode setup directly after image loading | 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 document/app/asset services with File-menu callbacks acting only as adapters |
|
||||
| DEBT-0030 | Open | Modernization | File export menu action planning now consumes pure `pp_app_core` through the File menu and `pano_cli plan-export-menu`, but live execution 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 document/app services with File-menu callbacks acting only as adapters |
|
||||
| DEBT-0031 | Open | Modernization | Top-level File menu command planning now consumes pure `pp_app_core` through `App::init_menu_file` and `pano_cli plan-file-menu`, but live execution 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 app/document/platform services with `App::init_menu_file` acting only as a UI 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, but the live adapter still calls legacy `Canvas::clear`, `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 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 clear, 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 |
|
||||
|
||||
@@ -522,8 +522,9 @@ legacy `Canvas::import_equirectangular` or import transform-mode execution
|
||||
continues.
|
||||
`pano_cli plan-file-menu` exposes app-core planning for the top-level File menu
|
||||
commands, including new/open/import, save/save-as/save-version, share, resize,
|
||||
cloud upload/browse, JPEG export, and export-submenu routing before legacy
|
||||
dialogs, pickers, platform services, cloud code, and canvas workflows continue.
|
||||
cloud upload/browse, JPEG export, and export-submenu routing. Direct File menu
|
||||
commands now dispatch through `FileMenuServices` before legacy dialogs, pickers,
|
||||
platform services, cloud code, and canvas workflows continue.
|
||||
`pano_cli plan-export-menu` exposes app-core planning for File menu export
|
||||
choices, including image, layer, cube-face, depth, animation-frame, MP4, and
|
||||
timelapse dialog routing plus license/canvas gating before legacy export dialogs
|
||||
@@ -1245,7 +1246,8 @@ Results:
|
||||
> Import route planning as JSON automation.
|
||||
- `pp_app_core_file_menu_tests` passed, covering top-level File menu routing for
|
||||
creation/open/import, save intents, export/submenu/cloud actions, and unknown
|
||||
command rejection.
|
||||
command rejection, plus executor dispatch for dialog, picker, save, export,
|
||||
share, resize, and cloud actions.
|
||||
- `pano_cli_plan_file_menu_import_smoke`,
|
||||
`pano_cli_plan_file_menu_save_as_smoke`,
|
||||
`pano_cli_plan_file_menu_export_smoke`,
|
||||
|
||||
@@ -45,6 +45,23 @@ struct FileMenuPlan {
|
||||
DocumentExportMenuKind export_kind = DocumentExportMenuKind::jpeg;
|
||||
};
|
||||
|
||||
class FileMenuServices {
|
||||
public:
|
||||
virtual ~FileMenuServices() = default;
|
||||
|
||||
virtual void show_new_document_dialog() = 0;
|
||||
virtual void pick_image_for_import() = 0;
|
||||
virtual void pick_project_file() = 0;
|
||||
virtual void show_cloud_browser_dialog() = 0;
|
||||
virtual void save_document(DocumentSaveIntent intent) = 0;
|
||||
virtual void show_export_jpeg_dialog(DocumentExportMenuKind kind) = 0;
|
||||
virtual void show_export_submenu() = 0;
|
||||
virtual void share_document() = 0;
|
||||
virtual void show_resize_dialog() = 0;
|
||||
virtual void upload_to_cloud() = 0;
|
||||
virtual void browse_cloud_documents() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr FileMenuPlan plan_file_menu_command(FileMenuCommand command) noexcept
|
||||
{
|
||||
FileMenuPlan plan;
|
||||
@@ -146,4 +163,47 @@ struct FileMenuPlan {
|
||||
pp::foundation::Status::invalid_argument("unknown file menu command"));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_file_menu_plan(
|
||||
const FileMenuPlan& plan,
|
||||
FileMenuServices& services)
|
||||
{
|
||||
switch (plan.action) {
|
||||
case FileMenuAction::show_new_document_dialog:
|
||||
services.show_new_document_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::pick_image_for_import:
|
||||
services.pick_image_for_import();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::pick_project_file:
|
||||
services.pick_project_file();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::show_cloud_browser_dialog:
|
||||
services.show_cloud_browser_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::save_document:
|
||||
services.save_document(plan.save_intent);
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::show_export_jpeg_dialog:
|
||||
services.show_export_jpeg_dialog(plan.export_kind);
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::show_export_submenu:
|
||||
services.show_export_submenu();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::share_document:
|
||||
services.share_document();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::show_resize_dialog:
|
||||
services.show_resize_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::upload_to_cloud:
|
||||
services.upload_to_cloud();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::browse_cloud_documents:
|
||||
services.browse_cloud_documents();
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown file menu action");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
|
||||
@@ -127,18 +127,22 @@ bool apply_document_export_menu_plan(App& app, pp::app::DocumentExportMenuKind k
|
||||
return false;
|
||||
}
|
||||
|
||||
void apply_file_menu_plan(App& app, pp::app::FileMenuCommand command)
|
||||
{
|
||||
const auto plan = pp::app::plan_file_menu_command(command);
|
||||
switch (plan.action)
|
||||
class LegacyFileMenuServices final : public pp::app::FileMenuServices {
|
||||
public:
|
||||
explicit LegacyFileMenuServices(App& app) noexcept
|
||||
: app_(app)
|
||||
{
|
||||
case pp::app::FileMenuAction::show_new_document_dialog:
|
||||
app.dialog_newdoc();
|
||||
break;
|
||||
case pp::app::FileMenuAction::pick_image_for_import:
|
||||
}
|
||||
|
||||
void show_new_document_dialog() override
|
||||
{
|
||||
auto* app_ptr = &app;
|
||||
app.pick_image([app_ptr](std::string path) {
|
||||
app_.dialog_newdoc();
|
||||
}
|
||||
|
||||
void pick_image_for_import() override
|
||||
{
|
||||
auto* app_ptr = &app_;
|
||||
app_.pick_image([app_ptr](std::string path) {
|
||||
Image img;
|
||||
img.load_file(path);
|
||||
const auto import_plan = pp::app::plan_document_image_import(img.width, img.height);
|
||||
@@ -157,40 +161,66 @@ void apply_file_menu_plan(App& app, pp::app::FileMenuCommand command)
|
||||
Canvas::set_mode(kCanvasMode::Import);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case pp::app::FileMenuAction::pick_project_file:
|
||||
|
||||
void pick_project_file() override
|
||||
{
|
||||
auto* app_ptr = &app;
|
||||
app.pick_file({ "ppi" }, [app_ptr](std::string path) {
|
||||
auto* app_ptr = &app_;
|
||||
app_.pick_file({ "ppi" }, [app_ptr](std::string path) {
|
||||
app_ptr->open_document(path);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case pp::app::FileMenuAction::show_cloud_browser_dialog:
|
||||
app.dialog_browse();
|
||||
break;
|
||||
case pp::app::FileMenuAction::save_document:
|
||||
app.save_document(plan.save_intent);
|
||||
break;
|
||||
case pp::app::FileMenuAction::show_export_jpeg_dialog:
|
||||
apply_document_export_menu_plan(app, plan.export_kind);
|
||||
break;
|
||||
case pp::app::FileMenuAction::show_export_submenu:
|
||||
break;
|
||||
case pp::app::FileMenuAction::share_document:
|
||||
app.share_file(app.doc_path);
|
||||
break;
|
||||
case pp::app::FileMenuAction::show_resize_dialog:
|
||||
app.dialog_resize();
|
||||
break;
|
||||
case pp::app::FileMenuAction::upload_to_cloud:
|
||||
app.cloud_upload();
|
||||
break;
|
||||
case pp::app::FileMenuAction::browse_cloud_documents:
|
||||
app.cloud_browse();
|
||||
break;
|
||||
|
||||
void show_cloud_browser_dialog() override
|
||||
{
|
||||
app_.dialog_browse();
|
||||
}
|
||||
|
||||
void save_document(pp::app::DocumentSaveIntent intent) override
|
||||
{
|
||||
app_.save_document(intent);
|
||||
}
|
||||
|
||||
void show_export_jpeg_dialog(pp::app::DocumentExportMenuKind kind) override
|
||||
{
|
||||
apply_document_export_menu_plan(app_, kind);
|
||||
}
|
||||
|
||||
void show_export_submenu() override
|
||||
{
|
||||
}
|
||||
|
||||
void share_document() override
|
||||
{
|
||||
app_.share_file(app_.doc_path);
|
||||
}
|
||||
|
||||
void show_resize_dialog() override
|
||||
{
|
||||
app_.dialog_resize();
|
||||
}
|
||||
|
||||
void upload_to_cloud() override
|
||||
{
|
||||
app_.cloud_upload();
|
||||
}
|
||||
|
||||
void browse_cloud_documents() override
|
||||
{
|
||||
app_.cloud_browse();
|
||||
}
|
||||
|
||||
private:
|
||||
App& app_;
|
||||
};
|
||||
|
||||
void apply_file_menu_plan(App& app, pp::app::FileMenuCommand command)
|
||||
{
|
||||
const auto plan = pp::app::plan_file_menu_command(command);
|
||||
LegacyFileMenuServices services(app);
|
||||
const auto status = pp::app::execute_file_menu_plan(plan, services);
|
||||
if (!status.ok())
|
||||
LOG("File menu action failed: %s", status.message);
|
||||
}
|
||||
|
||||
pp::app::DocumentLayerMenuPlan make_layer_menu_plan(
|
||||
|
||||
@@ -3,6 +3,43 @@
|
||||
|
||||
namespace {
|
||||
|
||||
class FakeFileMenuServices final : public pp::app::FileMenuServices {
|
||||
public:
|
||||
void show_new_document_dialog() override { new_document_dialogs += 1; }
|
||||
void pick_image_for_import() override { image_picks += 1; }
|
||||
void pick_project_file() override { project_picks += 1; }
|
||||
void show_cloud_browser_dialog() override { cloud_browser_dialogs += 1; }
|
||||
void save_document(pp::app::DocumentSaveIntent intent) override
|
||||
{
|
||||
save_calls += 1;
|
||||
last_save_intent = intent;
|
||||
}
|
||||
void show_export_jpeg_dialog(pp::app::DocumentExportMenuKind kind) override
|
||||
{
|
||||
export_jpeg_dialogs += 1;
|
||||
last_export_kind = kind;
|
||||
}
|
||||
void show_export_submenu() override { export_submenus += 1; }
|
||||
void share_document() override { share_calls += 1; }
|
||||
void show_resize_dialog() override { resize_dialogs += 1; }
|
||||
void upload_to_cloud() override { cloud_uploads += 1; }
|
||||
void browse_cloud_documents() override { cloud_browses += 1; }
|
||||
|
||||
int new_document_dialogs = 0;
|
||||
int image_picks = 0;
|
||||
int project_picks = 0;
|
||||
int cloud_browser_dialogs = 0;
|
||||
int save_calls = 0;
|
||||
int export_jpeg_dialogs = 0;
|
||||
int export_submenus = 0;
|
||||
int share_calls = 0;
|
||||
int resize_dialogs = 0;
|
||||
int cloud_uploads = 0;
|
||||
int cloud_browses = 0;
|
||||
pp::app::DocumentSaveIntent last_save_intent = pp::app::DocumentSaveIntent::save;
|
||||
pp::app::DocumentExportMenuKind last_export_kind = pp::app::DocumentExportMenuKind::jpeg;
|
||||
};
|
||||
|
||||
void file_menu_routes_document_creation_and_opening(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto new_doc = pp::app::plan_file_menu_command(pp::app::FileMenuCommand::new_document);
|
||||
@@ -57,6 +94,95 @@ void file_menu_parser_accepts_legacy_button_names(pp::tests::Harness& harness)
|
||||
PP_EXPECT(harness, !invalid);
|
||||
}
|
||||
|
||||
void file_menu_executor_dispatches_dialog_picker_and_document_actions(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeFileMenuServices services;
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::execute_file_menu_plan(
|
||||
pp::app::plan_file_menu_command(pp::app::FileMenuCommand::new_document),
|
||||
services).ok());
|
||||
PP_EXPECT(harness, services.new_document_dialogs == 1);
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::execute_file_menu_plan(
|
||||
pp::app::plan_file_menu_command(pp::app::FileMenuCommand::import_image),
|
||||
services).ok());
|
||||
PP_EXPECT(harness, services.image_picks == 1);
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::execute_file_menu_plan(
|
||||
pp::app::plan_file_menu_command(pp::app::FileMenuCommand::open_project),
|
||||
services).ok());
|
||||
PP_EXPECT(harness, services.project_picks == 1);
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::execute_file_menu_plan(
|
||||
pp::app::plan_file_menu_command(pp::app::FileMenuCommand::save_as),
|
||||
services).ok());
|
||||
PP_EXPECT(harness, services.save_calls == 1);
|
||||
PP_EXPECT(harness, services.last_save_intent == pp::app::DocumentSaveIntent::save_as);
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::execute_file_menu_plan(
|
||||
pp::app::plan_file_menu_command(pp::app::FileMenuCommand::share),
|
||||
services).ok());
|
||||
PP_EXPECT(harness, services.share_calls == 1);
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::execute_file_menu_plan(
|
||||
pp::app::plan_file_menu_command(pp::app::FileMenuCommand::resize),
|
||||
services).ok());
|
||||
PP_EXPECT(harness, services.resize_dialogs == 1);
|
||||
}
|
||||
|
||||
void file_menu_executor_dispatches_export_and_cloud_actions(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeFileMenuServices services;
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::execute_file_menu_plan(
|
||||
pp::app::plan_file_menu_command(pp::app::FileMenuCommand::export_jpeg),
|
||||
services).ok());
|
||||
PP_EXPECT(harness, services.export_jpeg_dialogs == 1);
|
||||
PP_EXPECT(harness, services.last_export_kind == pp::app::DocumentExportMenuKind::jpeg);
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::execute_file_menu_plan(
|
||||
pp::app::plan_file_menu_command(pp::app::FileMenuCommand::export_submenu),
|
||||
services).ok());
|
||||
PP_EXPECT(harness, services.export_submenus == 1);
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::execute_file_menu_plan(
|
||||
pp::app::plan_file_menu_command(pp::app::FileMenuCommand::browse_cloud),
|
||||
services).ok());
|
||||
PP_EXPECT(harness, services.cloud_browser_dialogs == 1);
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::execute_file_menu_plan(
|
||||
pp::app::plan_file_menu_command(pp::app::FileMenuCommand::cloud_upload),
|
||||
services).ok());
|
||||
PP_EXPECT(harness, services.cloud_uploads == 1);
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::execute_file_menu_plan(
|
||||
pp::app::plan_file_menu_command(pp::app::FileMenuCommand::cloud_browse),
|
||||
services).ok());
|
||||
PP_EXPECT(harness, services.cloud_browses == 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -66,5 +192,11 @@ int main()
|
||||
harness.run("file menu preserves save intents", file_menu_preserves_save_intents);
|
||||
harness.run("file menu routes export and cloud actions", file_menu_routes_export_and_cloud_actions);
|
||||
harness.run("file menu parser accepts legacy button names", file_menu_parser_accepts_legacy_button_names);
|
||||
harness.run(
|
||||
"file menu executor dispatches dialog picker and document actions",
|
||||
file_menu_executor_dispatches_dialog_picker_and_document_actions);
|
||||
harness.run(
|
||||
"file menu executor dispatches export and cloud actions",
|
||||
file_menu_executor_dispatches_export_and_cloud_actions);
|
||||
return harness.finish();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user