Extract export menu action planning
This commit is contained in:
@@ -22,7 +22,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| DEBT-0001 | Open | Modernization | Existing platform build files remain alongside new CMake | Required for incremental migration without losing platform coverage | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets |
|
||||
| DEBT-0002 | Open | Modernization | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only, patched, or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace with vcpkg packages or document permanent vendored status after triplet evaluation |
|
||||
| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, `App::share_file`, `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, file-menu save actions, `NodeCanvas` save hotkeys, new/open/browse dirty-document workflow prompts, new-document target/resolution/overwrite decisions, save-as document file naming and overwrite decisions, save-version target decisions, export start/target naming/path decisions, share-file saved-path decisions, file/image/save/directory picker selected-path decisions, display-file external-open decisions, virtual-keyboard visibility decisions, recording lifecycle/export progress decisions, cloud-upload prompt/save-before-upload decisions, cloud-browse availability and selected-download decisions, bulk cloud-upload progress decisions, tools/options app preference decisions, app status/display decisions, document resize decisions, layer rename decisions, `pano_cli classify-open`, `pano_cli plan-open-route`, `pano_cli plan-new-document`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-start`, `pano_cli plan-export-target`, `pano_cli plan-recording-session`, `pano_cli plan-app-preferences`, `pano_cli plan-app-status`, `pano_cli plan-document-resize`, `pano_cli plan-layer-rename`, `pano_cli plan-share-file`, `pano_cli plan-picked-path`, `pano_cli plan-display-file`, `pano_cli plan-keyboard-visibility`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-browse`, `pano_cli plan-cloud-upload-all`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export/recording/preferences/status/share/platform-I/O/display/keyboard/cloud/resize/layer contracts, but document creation/loading, brush import execution, saving, export execution, tools/options UI execution, status/display UI rendering, document resize execution, layer rename execution, settings persistence, platform share service execution, picker service execution, display-file service execution, keyboard service execution, recording/MP4 execution, cloud upload execution, and cloud browse/download execution still reach legacy `Canvas::I`/UI/network/video/platform singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_recording_tests`; `pp_app_core_app_preferences_tests`; `pp_app_core_app_status_tests`; `pp_app_core_document_resize_tests`; `pp_app_core_document_layer_tests`; `pp_app_core_document_sharing_tests`; `pp_app_core_document_platform_io_tests`; `pp_app_core_document_cloud_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-open-route --path D:/Paint/demo.ppi --unsaved`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-app-status --doc-name demo --unsaved --resolution 2048 --resolution-index 3 --zoom 1.25 --history-bytes 1572864 --recording-running --encoder-available --encoded-frames 12`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-share-file --path D:/Paint/demo.ppi`; `pano_cli plan-picked-path --path D:/Paint/demo.ppi`; `pano_cli plan-display-file --path D:/Paint/export.png`; `pano_cli plan-keyboard-visibility --visible`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries |
|
||||
| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, `App::share_file`, `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, file-menu save actions, `NodeCanvas` save hotkeys, new/open/browse dirty-document workflow prompts, new-document target/resolution/overwrite decisions, save-as document file naming and overwrite decisions, save-version target decisions, export start/menu/target naming/path decisions, share-file saved-path decisions, file/image/save/directory picker selected-path decisions, display-file external-open decisions, virtual-keyboard visibility decisions, recording lifecycle/export progress decisions, cloud-upload prompt/save-before-upload decisions, cloud-browse availability and selected-download decisions, bulk cloud-upload progress decisions, tools/options app preference decisions, app status/display decisions, document resize decisions, layer rename decisions, `pano_cli classify-open`, `pano_cli plan-open-route`, `pano_cli plan-new-document`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-start`, `pano_cli plan-export-menu`, `pano_cli plan-export-target`, `pano_cli plan-recording-session`, `pano_cli plan-app-preferences`, `pano_cli plan-app-status`, `pano_cli plan-document-resize`, `pano_cli plan-layer-rename`, `pano_cli plan-share-file`, `pano_cli plan-picked-path`, `pano_cli plan-display-file`, `pano_cli plan-keyboard-visibility`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-browse`, `pano_cli plan-cloud-upload-all`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export/recording/preferences/status/share/platform-I/O/display/keyboard/cloud/resize/layer contracts, but document creation/loading, brush import execution, saving, export execution, tools/options UI execution, status/display UI rendering, document resize execution, layer rename execution, settings persistence, platform share service execution, picker service execution, display-file service execution, keyboard service execution, recording/MP4 execution, cloud upload execution, and cloud browse/download execution still reach legacy `Canvas::I`/UI/network/video/platform singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_recording_tests`; `pp_app_core_app_preferences_tests`; `pp_app_core_app_status_tests`; `pp_app_core_document_resize_tests`; `pp_app_core_document_layer_tests`; `pp_app_core_document_sharing_tests`; `pp_app_core_document_platform_io_tests`; `pp_app_core_document_cloud_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-open-route --path D:/Paint/demo.ppi --unsaved`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind animation-mp4 --demo`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-app-status --doc-name demo --unsaved --resolution 2048 --resolution-index 3 --zoom 1.25 --history-bytes 1572864 --recording-running --encoder-available --encoded-frames 12`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-share-file --path D:/Paint/demo.ppi`; `pano_cli plan-picked-path --path D:/Paint/demo.ppi`; `pano_cli plan-display-file --path D:/Paint/export.png`; `pano_cli plan-keyboard-visibility --visible`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries |
|
||||
| DEBT-0004 | Open | Modernization | Android, Linux, WebGL, Apple, and AppX build files remain platform-specific until root CMake alignment reaches them | Prevent platform regressions during incremental migration; raw Windows `.sln/.vcxproj` files were removed on 2026-05-31 by user decision | `cmake --preset windows-msvc-default`; platform-specific configure/build smoke checks as each platform is migrated | Root CMake owns every platform source list and package path |
|
||||
| DEBT-0005 | Open | Modernization | Temporary local CTest harness is used before Catch2 is wired through vcpkg | `vcpkg` is not currently on PATH, but headless tests need to run now | `ctest --preset desktop-fast --build-config Debug` | Replace `tests/test_harness.h` tests with Catch2 tests once vcpkg toolchain/presets are validated |
|
||||
| DEBT-0007 | Open | Modernization | `vcpkg.json` and `windows-msvc-vcpkg-headless` are validated for the headless Windows component matrix, but app targets still use vendored libraries and Android/Apple triplets are not proven | Dependency migration must stay incremental while SDK/patched/vendor dependencies remain in use | `$env:VCPKG_ROOT="C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg"; cmake --preset windows-msvc-vcpkg-headless`; `ctest --preset desktop-fast-vcpkg --build-config Debug` | Component targets consume vcpkg packages where reliable and desktop app, Android, and Apple triplets are validated or explicitly documented as permanent vendor exceptions |
|
||||
@@ -47,6 +47,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| DEBT-0027 | Open | Modernization | Canvas draw-tool toolbar command, canvas input mode switching, and active-state planning now consume pure `pp_app_core` through `App::init_toolbar_draw`, `App::update`, `NodeCanvas`, `pano_cli plan-canvas-tool`, and `pano_cli plan-canvas-tool-state`, but live execution/state storage still mutates or reads legacy `Canvas` mode state, pen picking state, touch-lock state, and transform copy/cut action objects directly | Preserve current toolbar, stylus eraser, and keyboard draw/erase behavior while canvas input/tools move toward an app/document command boundary | `pp_app_core_canvas_tool_ui_tests`; `pano_cli plan-canvas-tool --kind copy`; `pano_cli plan-canvas-tool-state --mode draw --picking --touch-lock`; `ctest --preset desktop-fast --build-config Debug` | Canvas tool selection, toolbar state refresh, picking, touch lock, stylus eraser/key mode switching, and transform action execution are owned by app/document/canvas services with toolbar/canvas callbacks acting only as adapters |
|
||||
| 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 |
|
||||
|
||||
## Closed Debt
|
||||
|
||||
|
||||
@@ -516,6 +516,10 @@ route decisions, including wide equirectangular images, legacy vertical cube
|
||||
strips, regular transform-placement images, and invalid image dimensions before
|
||||
legacy `Canvas::import_equirectangular` or import transform-mode execution
|
||||
continues.
|
||||
`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
|
||||
and renderer/video execution continue.
|
||||
`pano_cli plan-grid-operation` exposes app-core planning for grid heightmap
|
||||
pick/load/reload/clear, lightmap render capability/limit checks, and heightmap
|
||||
commit used by the live grid panel before legacy image loading, OpenGL texture
|
||||
@@ -1203,6 +1207,14 @@ Results:
|
||||
`pano_cli_plan_image_import_transform_smoke`, and
|
||||
`pano_cli_plan_image_import_rejects_invalid_dimensions` passed and expose File
|
||||
> Import route planning as JSON automation.
|
||||
- `pp_app_core_document_export_tests` passed, now also covering export menu
|
||||
dialog routing, demo-mode MP4/timelapse license gating, and missing-canvas
|
||||
handling before legacy export dialogs continue.
|
||||
- `pano_cli_plan_export_menu_png_smoke`,
|
||||
`pano_cli_plan_export_menu_mp4_demo_blocked_smoke`,
|
||||
`pano_cli_plan_export_menu_no_canvas_smoke`, and
|
||||
`pano_cli_plan_export_menu_rejects_unknown` passed and expose File menu export
|
||||
routing as JSON automation.
|
||||
- `pp_app_core_history_ui_tests` passed, covering undo/redo availability,
|
||||
no-op history commands, clear-history stack/memory state, memory-only clear,
|
||||
and negative metric rejection.
|
||||
|
||||
@@ -32,6 +32,36 @@ enum class DocumentExportStartDecision {
|
||||
unavailable_no_canvas,
|
||||
};
|
||||
|
||||
enum class DocumentExportMenuKind {
|
||||
jpeg,
|
||||
png,
|
||||
layers,
|
||||
cube_faces,
|
||||
depth,
|
||||
animation_frames,
|
||||
animation_mp4,
|
||||
timelapse,
|
||||
};
|
||||
|
||||
enum class DocumentExportMenuAction {
|
||||
show_jpeg_dialog,
|
||||
show_png_dialog,
|
||||
show_layers_dialog,
|
||||
show_cube_faces_dialog,
|
||||
show_depth_dialog,
|
||||
show_animation_frames_dialog,
|
||||
show_animation_mp4_dialog,
|
||||
show_timelapse_dialog,
|
||||
show_license_disabled,
|
||||
unavailable_no_canvas,
|
||||
};
|
||||
|
||||
struct DocumentExportMenuPlan {
|
||||
DocumentExportMenuKind kind = DocumentExportMenuKind::jpeg;
|
||||
DocumentExportMenuAction action = DocumentExportMenuAction::show_jpeg_dialog;
|
||||
bool opens_dialog = true;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr DocumentExportStartDecision plan_document_export_start(
|
||||
bool requires_license,
|
||||
bool license_valid,
|
||||
@@ -46,6 +76,74 @@ enum class DocumentExportStartDecision {
|
||||
: DocumentExportStartDecision::unavailable_no_canvas;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool document_export_menu_requires_license(
|
||||
DocumentExportMenuKind kind) noexcept
|
||||
{
|
||||
switch (kind) {
|
||||
case DocumentExportMenuKind::animation_mp4:
|
||||
case DocumentExportMenuKind::timelapse:
|
||||
return true;
|
||||
case DocumentExportMenuKind::jpeg:
|
||||
case DocumentExportMenuKind::png:
|
||||
case DocumentExportMenuKind::layers:
|
||||
case DocumentExportMenuKind::cube_faces:
|
||||
case DocumentExportMenuKind::depth:
|
||||
case DocumentExportMenuKind::animation_frames:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr DocumentExportMenuAction document_export_menu_dialog_action(
|
||||
DocumentExportMenuKind kind) noexcept
|
||||
{
|
||||
switch (kind) {
|
||||
case DocumentExportMenuKind::jpeg:
|
||||
return DocumentExportMenuAction::show_jpeg_dialog;
|
||||
case DocumentExportMenuKind::png:
|
||||
return DocumentExportMenuAction::show_png_dialog;
|
||||
case DocumentExportMenuKind::layers:
|
||||
return DocumentExportMenuAction::show_layers_dialog;
|
||||
case DocumentExportMenuKind::cube_faces:
|
||||
return DocumentExportMenuAction::show_cube_faces_dialog;
|
||||
case DocumentExportMenuKind::depth:
|
||||
return DocumentExportMenuAction::show_depth_dialog;
|
||||
case DocumentExportMenuKind::animation_frames:
|
||||
return DocumentExportMenuAction::show_animation_frames_dialog;
|
||||
case DocumentExportMenuKind::animation_mp4:
|
||||
return DocumentExportMenuAction::show_animation_mp4_dialog;
|
||||
case DocumentExportMenuKind::timelapse:
|
||||
return DocumentExportMenuAction::show_timelapse_dialog;
|
||||
}
|
||||
|
||||
return DocumentExportMenuAction::show_jpeg_dialog;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr DocumentExportMenuPlan plan_document_export_menu_action(
|
||||
DocumentExportMenuKind kind,
|
||||
bool has_canvas,
|
||||
bool license_valid) noexcept
|
||||
{
|
||||
DocumentExportMenuPlan plan;
|
||||
plan.kind = kind;
|
||||
plan.action = document_export_menu_dialog_action(kind);
|
||||
|
||||
const auto start = plan_document_export_start(
|
||||
document_export_menu_requires_license(kind),
|
||||
license_valid,
|
||||
has_canvas);
|
||||
if (start == DocumentExportStartDecision::show_license_disabled) {
|
||||
plan.action = DocumentExportMenuAction::show_license_disabled;
|
||||
plan.opens_dialog = false;
|
||||
} else if (start == DocumentExportStartDecision::unavailable_no_canvas) {
|
||||
plan.action = DocumentExportMenuAction::unavailable_no_canvas;
|
||||
plan.opens_dialog = false;
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentExportFileTarget> make_document_export_file_target(
|
||||
std::string_view work_directory,
|
||||
std::string_view document_name,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "app_core/canvas_tool_ui.h"
|
||||
#include "app_core/document_layer.h"
|
||||
#include "app_core/document_canvas.h"
|
||||
#include "app_core/document_export.h"
|
||||
#include "app_core/document_import.h"
|
||||
#include "app_core/app_status.h"
|
||||
#include "app_core/history_ui.h"
|
||||
@@ -78,6 +79,50 @@ bool apply_brush_preset_plan(App& app, const std::shared_ptr<Brush>& brush)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool apply_document_export_menu_plan(App& app, pp::app::DocumentExportMenuKind kind)
|
||||
{
|
||||
const auto requires_license = pp::app::document_export_menu_requires_license(kind);
|
||||
const auto plan = pp::app::plan_document_export_menu_action(
|
||||
kind,
|
||||
app.canvas != nullptr,
|
||||
!requires_license || app.check_license());
|
||||
|
||||
switch (plan.action)
|
||||
{
|
||||
case pp::app::DocumentExportMenuAction::show_jpeg_dialog:
|
||||
app.dialog_export(".jpg");
|
||||
return true;
|
||||
case pp::app::DocumentExportMenuAction::show_png_dialog:
|
||||
app.dialog_export(".png");
|
||||
return true;
|
||||
case pp::app::DocumentExportMenuAction::show_layers_dialog:
|
||||
app.dialog_export_layers();
|
||||
return true;
|
||||
case pp::app::DocumentExportMenuAction::show_cube_faces_dialog:
|
||||
app.dialog_export_cube_faces();
|
||||
return true;
|
||||
case pp::app::DocumentExportMenuAction::show_depth_dialog:
|
||||
app.dialog_export_depth();
|
||||
return true;
|
||||
case pp::app::DocumentExportMenuAction::show_animation_frames_dialog:
|
||||
app.dialog_export_anim_frames();
|
||||
return true;
|
||||
case pp::app::DocumentExportMenuAction::show_animation_mp4_dialog:
|
||||
app.dialog_export_mp4();
|
||||
return true;
|
||||
case pp::app::DocumentExportMenuAction::show_timelapse_dialog:
|
||||
app.dialog_timelapse_export();
|
||||
return true;
|
||||
case pp::app::DocumentExportMenuAction::show_license_disabled:
|
||||
app.message_box("License", "This function is disabled in demo mode.");
|
||||
return false;
|
||||
case pp::app::DocumentExportMenuAction::unavailable_no_canvas:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void App::title_update()
|
||||
@@ -801,7 +846,7 @@ void App::init_menu_file()
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-export"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
dialog_export(".jpg");
|
||||
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::jpeg);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
@@ -816,49 +861,49 @@ void App::init_menu_file()
|
||||
subpopup->SetPosition(pos.x, pos.y);
|
||||
layout[main_id]->add_child(subpopup);
|
||||
subpopup->find<NodeButtonCustom>("file-submenu-export-png")->on_click = [this, subpopup, popup](Node*) {
|
||||
dialog_export(".png");
|
||||
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::png);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
subpopup->mouse_release();
|
||||
subpopup->destroy();
|
||||
};
|
||||
subpopup->find<NodeButtonCustom>("file-submenu-export-layers")->on_click = [this, subpopup, popup](Node*) {
|
||||
dialog_export_layers();
|
||||
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::layers);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
subpopup->mouse_release();
|
||||
subpopup->destroy();
|
||||
};
|
||||
subpopup->find<NodeButtonCustom>("file-submenu-export-cube")->on_click = [this, subpopup, popup](Node*) {
|
||||
dialog_export_cube_faces();
|
||||
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::cube_faces);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
subpopup->mouse_release();
|
||||
subpopup->destroy();
|
||||
};
|
||||
subpopup->find<NodeButtonCustom>("file-submenu-export-depth")->on_click = [this, subpopup, popup](Node*) {
|
||||
dialog_export_depth();
|
||||
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::depth);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
subpopup->mouse_release();
|
||||
subpopup->destroy();
|
||||
};
|
||||
subpopup->find<NodeButtonCustom>("file-submenu-export-anim")->on_click = [this, subpopup, popup](Node*) {
|
||||
dialog_export_anim_frames();
|
||||
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::animation_frames);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
subpopup->mouse_release();
|
||||
subpopup->destroy();
|
||||
};
|
||||
subpopup->find<NodeButtonCustom>("file-submenu-export-anim-mp4")->on_click = [this, subpopup, popup](Node*) {
|
||||
dialog_export_mp4();
|
||||
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::animation_mp4);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
subpopup->mouse_release();
|
||||
subpopup->destroy();
|
||||
};
|
||||
subpopup->find<NodeButtonCustom>("file-submenu-export-timelapse")->on_click = [this, subpopup, popup](Node*) {
|
||||
dialog_timelapse_export();
|
||||
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::timelapse);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
subpopup->mouse_release();
|
||||
|
||||
@@ -631,6 +631,30 @@ if(TARGET pano_cli)
|
||||
LABELS "app;integration;desktop-fast;fuzz"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-start\".*\"requiresLicense\":false.*\"hasCanvas\":false.*\"decision\":\"unavailable-no-canvas\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_export_menu_png_smoke
|
||||
COMMAND pano_cli plan-export-menu --kind png)
|
||||
set_tests_properties(pano_cli_plan_export_menu_png_smoke PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-menu\".*\"kind\":\"png\".*\"action\":\"show-png-dialog\".*\"opensDialog\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_export_menu_mp4_demo_blocked_smoke
|
||||
COMMAND pano_cli plan-export-menu --kind animation-mp4 --demo)
|
||||
set_tests_properties(pano_cli_plan_export_menu_mp4_demo_blocked_smoke PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-menu\".*\"kind\":\"animation-mp4\".*\"licenseValid\":false.*\"action\":\"show-license-disabled\".*\"opensDialog\":false")
|
||||
|
||||
add_test(NAME pano_cli_plan_export_menu_no_canvas_smoke
|
||||
COMMAND pano_cli plan-export-menu --kind layers --no-canvas)
|
||||
set_tests_properties(pano_cli_plan_export_menu_no_canvas_smoke PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-menu\".*\"kind\":\"layers\".*\"hasCanvas\":false.*\"action\":\"unavailable-no-canvas\".*\"opensDialog\":false")
|
||||
|
||||
add_test(NAME pano_cli_plan_export_menu_rejects_unknown
|
||||
COMMAND pano_cli plan-export-menu --kind unknown)
|
||||
set_tests_properties(pano_cli_plan_export_menu_rejects_unknown PROPERTIES
|
||||
LABELS "app;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_export_target_file_smoke
|
||||
COMMAND pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png)
|
||||
set_tests_properties(pano_cli_plan_export_target_file_smoke PROPERTIES
|
||||
|
||||
@@ -78,6 +78,51 @@ void export_start_reports_missing_canvas_after_license_gate(pp::tests::Harness&
|
||||
== pp::app::DocumentExportStartDecision::show_license_disabled);
|
||||
}
|
||||
|
||||
void export_menu_routes_image_dialogs_without_license(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto jpeg = pp::app::plan_document_export_menu_action(
|
||||
pp::app::DocumentExportMenuKind::jpeg,
|
||||
true,
|
||||
false);
|
||||
const auto png = pp::app::plan_document_export_menu_action(
|
||||
pp::app::DocumentExportMenuKind::png,
|
||||
true,
|
||||
false);
|
||||
|
||||
PP_EXPECT(harness, jpeg.action == pp::app::DocumentExportMenuAction::show_jpeg_dialog);
|
||||
PP_EXPECT(harness, jpeg.opens_dialog);
|
||||
PP_EXPECT(harness, png.action == pp::app::DocumentExportMenuAction::show_png_dialog);
|
||||
PP_EXPECT(harness, png.opens_dialog);
|
||||
}
|
||||
|
||||
void export_menu_blocks_video_when_license_is_missing(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto mp4 = pp::app::plan_document_export_menu_action(
|
||||
pp::app::DocumentExportMenuKind::animation_mp4,
|
||||
true,
|
||||
false);
|
||||
const auto timelapse = pp::app::plan_document_export_menu_action(
|
||||
pp::app::DocumentExportMenuKind::timelapse,
|
||||
true,
|
||||
false);
|
||||
|
||||
PP_EXPECT(harness, mp4.action == pp::app::DocumentExportMenuAction::show_license_disabled);
|
||||
PP_EXPECT(harness, !mp4.opens_dialog);
|
||||
PP_EXPECT(harness, timelapse.action == pp::app::DocumentExportMenuAction::show_license_disabled);
|
||||
PP_EXPECT(harness, !timelapse.opens_dialog);
|
||||
}
|
||||
|
||||
void export_menu_reports_missing_canvas_for_unlicensed_image_exports(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_document_export_menu_action(
|
||||
pp::app::DocumentExportMenuKind::layers,
|
||||
false,
|
||||
true);
|
||||
|
||||
PP_EXPECT(harness, plan.action == pp::app::DocumentExportMenuAction::unavailable_no_canvas);
|
||||
PP_EXPECT(harness, !plan.opens_dialog);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -91,5 +136,8 @@ int main()
|
||||
harness.run("export start allows valid canvas state", export_start_allows_valid_canvas_state);
|
||||
harness.run("export start blocks demo only when license required", export_start_blocks_demo_only_when_license_required);
|
||||
harness.run("export start reports missing canvas after license gate", export_start_reports_missing_canvas_after_license_gate);
|
||||
harness.run("export menu routes image dialogs without license", export_menu_routes_image_dialogs_without_license);
|
||||
harness.run("export menu blocks video when license is missing", export_menu_blocks_video_when_license_is_missing);
|
||||
harness.run("export menu reports missing canvas for unlicensed image exports", export_menu_reports_missing_canvas_for_unlicensed_image_exports);
|
||||
return harness.finish();
|
||||
}
|
||||
|
||||
@@ -152,6 +152,12 @@ struct PlanExportStartArgs {
|
||||
bool has_canvas = true;
|
||||
};
|
||||
|
||||
struct PlanExportMenuArgs {
|
||||
std::string kind = "jpeg";
|
||||
bool license_valid = true;
|
||||
bool has_canvas = true;
|
||||
};
|
||||
|
||||
struct PlanCloudUploadArgs {
|
||||
bool has_canvas = true;
|
||||
bool new_document = false;
|
||||
@@ -808,6 +814,98 @@ const char* document_export_start_decision_name(pp::app::DocumentExportStartDeci
|
||||
return "unavailable-no-canvas";
|
||||
}
|
||||
|
||||
const char* document_export_menu_kind_name(pp::app::DocumentExportMenuKind kind) noexcept
|
||||
{
|
||||
switch (kind) {
|
||||
case pp::app::DocumentExportMenuKind::jpeg:
|
||||
return "jpeg";
|
||||
case pp::app::DocumentExportMenuKind::png:
|
||||
return "png";
|
||||
case pp::app::DocumentExportMenuKind::layers:
|
||||
return "layers";
|
||||
case pp::app::DocumentExportMenuKind::cube_faces:
|
||||
return "cube-faces";
|
||||
case pp::app::DocumentExportMenuKind::depth:
|
||||
return "depth";
|
||||
case pp::app::DocumentExportMenuKind::animation_frames:
|
||||
return "animation-frames";
|
||||
case pp::app::DocumentExportMenuKind::animation_mp4:
|
||||
return "animation-mp4";
|
||||
case pp::app::DocumentExportMenuKind::timelapse:
|
||||
return "timelapse";
|
||||
}
|
||||
|
||||
return "jpeg";
|
||||
}
|
||||
|
||||
const char* document_export_menu_action_name(pp::app::DocumentExportMenuAction action) noexcept
|
||||
{
|
||||
switch (action) {
|
||||
case pp::app::DocumentExportMenuAction::show_jpeg_dialog:
|
||||
return "show-jpeg-dialog";
|
||||
case pp::app::DocumentExportMenuAction::show_png_dialog:
|
||||
return "show-png-dialog";
|
||||
case pp::app::DocumentExportMenuAction::show_layers_dialog:
|
||||
return "show-layers-dialog";
|
||||
case pp::app::DocumentExportMenuAction::show_cube_faces_dialog:
|
||||
return "show-cube-faces-dialog";
|
||||
case pp::app::DocumentExportMenuAction::show_depth_dialog:
|
||||
return "show-depth-dialog";
|
||||
case pp::app::DocumentExportMenuAction::show_animation_frames_dialog:
|
||||
return "show-animation-frames-dialog";
|
||||
case pp::app::DocumentExportMenuAction::show_animation_mp4_dialog:
|
||||
return "show-animation-mp4-dialog";
|
||||
case pp::app::DocumentExportMenuAction::show_timelapse_dialog:
|
||||
return "show-timelapse-dialog";
|
||||
case pp::app::DocumentExportMenuAction::show_license_disabled:
|
||||
return "show-license-disabled";
|
||||
case pp::app::DocumentExportMenuAction::unavailable_no_canvas:
|
||||
return "unavailable-no-canvas";
|
||||
}
|
||||
|
||||
return "unavailable-no-canvas";
|
||||
}
|
||||
|
||||
pp::foundation::Result<pp::app::DocumentExportMenuKind> parse_document_export_menu_kind(
|
||||
std::string_view kind)
|
||||
{
|
||||
if (kind == "jpeg" || kind == "jpg") {
|
||||
return pp::foundation::Result<pp::app::DocumentExportMenuKind>::success(
|
||||
pp::app::DocumentExportMenuKind::jpeg);
|
||||
}
|
||||
if (kind == "png") {
|
||||
return pp::foundation::Result<pp::app::DocumentExportMenuKind>::success(
|
||||
pp::app::DocumentExportMenuKind::png);
|
||||
}
|
||||
if (kind == "layers") {
|
||||
return pp::foundation::Result<pp::app::DocumentExportMenuKind>::success(
|
||||
pp::app::DocumentExportMenuKind::layers);
|
||||
}
|
||||
if (kind == "cube-faces" || kind == "cube") {
|
||||
return pp::foundation::Result<pp::app::DocumentExportMenuKind>::success(
|
||||
pp::app::DocumentExportMenuKind::cube_faces);
|
||||
}
|
||||
if (kind == "depth") {
|
||||
return pp::foundation::Result<pp::app::DocumentExportMenuKind>::success(
|
||||
pp::app::DocumentExportMenuKind::depth);
|
||||
}
|
||||
if (kind == "animation-frames" || kind == "anim") {
|
||||
return pp::foundation::Result<pp::app::DocumentExportMenuKind>::success(
|
||||
pp::app::DocumentExportMenuKind::animation_frames);
|
||||
}
|
||||
if (kind == "animation-mp4" || kind == "mp4") {
|
||||
return pp::foundation::Result<pp::app::DocumentExportMenuKind>::success(
|
||||
pp::app::DocumentExportMenuKind::animation_mp4);
|
||||
}
|
||||
if (kind == "timelapse") {
|
||||
return pp::foundation::Result<pp::app::DocumentExportMenuKind>::success(
|
||||
pp::app::DocumentExportMenuKind::timelapse);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<pp::app::DocumentExportMenuKind>::failure(
|
||||
pp::foundation::Status::invalid_argument("unknown export menu kind"));
|
||||
}
|
||||
|
||||
const char* cloud_upload_action_name(pp::app::CloudUploadAction action) noexcept
|
||||
{
|
||||
switch (action) {
|
||||
@@ -1025,6 +1123,7 @@ void print_help()
|
||||
<< " plan-document-file --work-dir DIR --name NAME [--target-exists]\n"
|
||||
<< " plan-document-version --directory DIR --doc-name NAME [--existing-path FILE]\n"
|
||||
<< " plan-export-start [--requires-license] [--demo] [--no-canvas]\n"
|
||||
<< " plan-export-menu --kind jpeg|png|layers|cube-faces|depth|animation-frames|animation-mp4|timelapse [--demo] [--no-canvas]\n"
|
||||
<< " plan-export-target --kind file|collection|stem|name --doc-name NAME [--work-dir DIR] [--directory DIR] [--extension EXT] [--suffix SUFFIX]\n"
|
||||
<< " plan-cloud-upload [--no-canvas] [--new-document] [--unsaved]\n"
|
||||
<< " plan-cloud-browse [--no-canvas] [--selected-file FILE]\n"
|
||||
@@ -2194,6 +2293,60 @@ int plan_export_start(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_export_menu_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
PlanExportMenuArgs& args)
|
||||
{
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const std::string_view key(argv[i]);
|
||||
if (key == "--kind") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
args.kind = argv[++i];
|
||||
} else if (key == "--demo") {
|
||||
args.license_valid = false;
|
||||
} else if (key == "--no-canvas") {
|
||||
args.has_canvas = false;
|
||||
} else {
|
||||
return pp::foundation::Status::invalid_argument("unknown option");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
int plan_export_menu(int argc, char** argv)
|
||||
{
|
||||
PlanExportMenuArgs args;
|
||||
const auto status = parse_plan_export_menu_args(argc, argv, args);
|
||||
if (!status.ok()) {
|
||||
print_error("plan-export-menu", status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto kind = parse_document_export_menu_kind(args.kind);
|
||||
if (!kind) {
|
||||
print_error("plan-export-menu", kind.status().message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto plan = pp::app::plan_document_export_menu_action(
|
||||
kind.value(),
|
||||
args.has_canvas,
|
||||
args.license_valid);
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-export-menu\""
|
||||
<< ",\"state\":{\"kind\":\"" << json_escape(args.kind)
|
||||
<< "\",\"licenseValid\":" << json_bool(args.license_valid)
|
||||
<< ",\"hasCanvas\":" << json_bool(args.has_canvas)
|
||||
<< "},\"plan\":{\"kind\":\"" << document_export_menu_kind_name(plan.kind)
|
||||
<< "\",\"action\":\"" << document_export_menu_action_name(plan.action)
|
||||
<< "\",\"opensDialog\":" << json_bool(plan.opens_dialog)
|
||||
<< "}}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_cloud_upload_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
@@ -6125,6 +6278,10 @@ int main(int argc, char** argv)
|
||||
return plan_export_start(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-export-menu") {
|
||||
return plan_export_menu(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-export-target") {
|
||||
return plan_export_target(argc, argv);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user