Route canvas view execution through app core
This commit is contained in:
@@ -62,6 +62,8 @@ set(PP_LEGACY_APP_SOURCES
|
||||
src/legacy_app_shell_services.h
|
||||
src/legacy_canvas_tool_services.cpp
|
||||
src/legacy_canvas_tool_services.h
|
||||
src/legacy_canvas_view_services.cpp
|
||||
src/legacy_canvas_view_services.h
|
||||
src/legacy_document_canvas_services.cpp
|
||||
src/legacy_document_canvas_services.h
|
||||
src/legacy_document_layer_services.cpp
|
||||
|
||||
@@ -226,9 +226,14 @@ Known local toolchain state:
|
||||
`pp_app_core` contracts while legacy dialogs, pickers, cloud/share/export,
|
||||
Tools, About, history, canvas-clear, settings, and platform SonarPen startup
|
||||
execution remain tracked by `DEBT-0029`, `DEBT-0030`, `DEBT-0031`,
|
||||
`DEBT-0033`, `DEBT-0034`, and `DEBT-0035`. `NodeCanvas::reset_camera()`
|
||||
now consumes the tested `pp_app_core` reset-camera state exposed through
|
||||
`pano_cli plan-canvas-camera-reset` before retained canvas camera mutation.
|
||||
`DEBT-0033`, `DEBT-0034`, and `DEBT-0035`. `src/legacy_canvas_view_services.*`
|
||||
is the shared bridge for reset-camera, viewport-density, and cursor-mode
|
||||
execution; live Tools reset-camera, document open/new-document reset, cloud
|
||||
download reset, and options viewport/cursor callbacks consume the tested
|
||||
`pp_app_core` canvas-view plans exposed through
|
||||
`pano_cli plan-canvas-camera-reset`, `pano_cli plan-canvas-view-density`,
|
||||
and `pano_cli plan-canvas-view-cursor-mode` before retained canvas mutation
|
||||
and settings writes.
|
||||
- `src/legacy_app_preference_services.*` is the current app-shell bridge for
|
||||
options-menu preference execution. It keeps UI scale, viewport scale, RTL,
|
||||
VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks on
|
||||
|
||||
@@ -75,12 +75,16 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
small-brush, not-painting, modifier, and malformed-brush states for
|
||||
automation. Legacy `Canvas`/`CanvasModePen` state reads and app cursor
|
||||
execution remain open under DEBT-0027.
|
||||
- 2026-06-05: DEBT-0033 was narrowed. Canvas reset-camera defaults now go
|
||||
through tested `pp_app_core`, live `NodeCanvas::reset_camera()` consumes the
|
||||
planner before retained canvas mutation, and `pano_cli plan-canvas-camera-reset`
|
||||
exposes the exact identity rotation, zero position/pan, and 85-degree field
|
||||
of view for automation. Legacy Tools/document/cloud callers still reach the
|
||||
legacy `NodeCanvas` adapter until canvas view services exist.
|
||||
- 2026-06-05: DEBT-0033 was narrowed again. Canvas reset-camera defaults,
|
||||
viewport density, and cursor mode now go through tested `pp_app_core` plans
|
||||
and shared `src/legacy_canvas_view_services.*` execution. Live Tools
|
||||
reset-camera, document open/new-document reset, cloud download reset, and
|
||||
options viewport/cursor callbacks consume that bridge, while
|
||||
`pano_cli plan-canvas-camera-reset`, `pano_cli plan-canvas-view-density`, and
|
||||
`pano_cli plan-canvas-view-cursor-mode` expose the paths for automation.
|
||||
This also narrows DEBT-0045 for viewport-density and cursor-mode preference
|
||||
execution, though preference persistence remains retained in the legacy
|
||||
canvas-view bridge.
|
||||
- 2026-06-04: DEBT-0036 was narrowed again. Canvas stroke commit,
|
||||
thumbnail, and object-draw history paths now query saved blend state through
|
||||
tested `pp_renderer_gl` capability-state dispatch; CanvasLayer equirect
|
||||
@@ -127,7 +131,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| 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, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge 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, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge 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; Layer menu clear reuses the `DocumentCanvasClearServices` executor; and Layer menu rename/clear/merge now share `src/legacy_document_layer_services.*`, but the bridge still calls the legacy rename dialog path, `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`, `pano_cli plan-canvas-camera-reset`, and the `ToolsMenuServices` boundary, direct command execution is centralized in `src/legacy_app_shell_services.*`, SonarPen availability/startup now routes through `PlatformServices`, and `NodeCanvas::reset_camera()` consumes tested app-core reset defaults before mutating legacy canvas camera state, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, open legacy shortcuts UI, and rely on the legacy platform adapter for the retained iOS SonarPen bridge | Preserve current Tools menu and reset-camera behavior while UI shell actions move toward app/UI/platform/canvas services | `pp_app_core_tools_menu_tests`; `pp_app_core_canvas_view_tests`; `pp_platform_api_tests`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-tools-panel --panel animation --already-visible`; `pano_cli plan-canvas-camera-reset`; `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/canvas services with `App::init_menu_tools` acting only as a UI adapter and no legacy Tools 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`, `pano_cli plan-canvas-camera-reset`, `pano_cli plan-canvas-view-density`, `pano_cli plan-canvas-view-cursor-mode`, and the `ToolsMenuServices` boundary, direct command execution is centralized in `src/legacy_app_shell_services.*`, SonarPen availability/startup now routes through `PlatformServices`, and reset-camera, viewport-density, and cursor-mode execution now share `src/legacy_canvas_view_services.*`, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, open legacy shortcuts UI, mutate retained `Canvas` camera/density/cursor state, write retained `Settings`, and rely on the legacy platform adapter for the retained iOS SonarPen bridge | Preserve current Tools menu and canvas-view behavior while UI shell actions move toward app/UI/platform/canvas services | `pp_app_core_tools_menu_tests`; `pp_app_core_canvas_view_tests`; `pp_platform_api_tests`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-tools-panel --panel animation --already-visible`; `pano_cli plan-canvas-camera-reset`; `pano_cli plan-canvas-view-density --density 1.5`; `pano_cli plan-canvas-view-cursor-mode --mode 3`; `ctest --preset desktop-fast --build-config Debug` | Tools panel creation, submenu routing, grid clear, camera reset, viewport density, cursor mode, shortcuts dialog, and SonarPen dispatch are owned by injected app/UI/platform/canvas services with `App::init_menu_tools` and options callbacks acting only as UI adapters and no legacy Tools/canvas-view 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, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge 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, history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge 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 |
|
||||
| DEBT-0036 | Open | Modernization | `pp_renderer_api`, `pp_paint_renderer`, `pano_cli plan-paint-feedback`, and `pano_cli plan-stroke-composite` can choose backend-neutral complex paint feedback strategies for fixed-function blending, framebuffer-fetch-capable renderers, or ping-pong render targets. OpenGL extension detection now stores `pp::renderer::RenderDeviceFeatures` through `ShaderManager`, using `pp_renderer_gl::query_opengl_capability_detection`, `detect_opengl_feature_state`, and `render_device_features` as the backend conversion point; that feature snapshot now includes float32-linear filtering, so canvas stroke texture format selection, renderer diagnostics, grid lightmap render planning, and grid bake target selection no longer read `ShaderManager::ext_*` flags directly. `pp_paint_renderer::plan_canvas_blend_gate` owns the compatibility mapping from persisted layer/brush blend indices to the extracted stroke-composite planner, and live `Canvas::draw_merge` plus `NodeCanvas` panorama rendering both call it with the stored renderer-neutral feature set for their existing shader-blend gates and destination-copy versus framebuffer-fetch decisions. `pp_paint_renderer::plan_canvas_stroke_feedback` also owns the current destination-feedback decision, and live `Canvas::stroke_draw`, thumbnail layer blending, and `NodeStrokePreview` brush-preview rendering use it for framebuffer-fetch versus destination-copy decisions. The retained `copy_framebuffer_to_texture_2d` utility bridge now routes 2D framebuffer-to-texture copies through tested `pp_renderer_gl` dispatch, retained `RTT::create`/`RTT::destroy` render-target texture parameter setup, optional depth renderbuffer allocation, framebuffer allocation/attachment/status checks, binding restore, and resource deletion now route through tested `pp_renderer_gl` dispatch, retained RTT clear, masked clear with color-write-mask restore, texture bind/unbind, and RGBA8 dirty-region texture writes now route through tested `pp_renderer_gl` dispatch, retained Canvas, NodeCanvas, and NodeStrokePreview texture-unit switches now route through tested active-texture dispatch, retained Canvas, NodeCanvas, NodeStrokePreview, and desktop HMD viewport/scissor/capability execution now route through tested `pp_renderer_gl` dispatch adapters, retained NodeCanvas, CanvasMode, and NodePanelGrid capability-state snapshots now route through tested `pp_renderer_gl` query dispatch, CanvasLayer cube/equirect generation plus frame clears now route blend state, active texture units, viewport execution, color clears, and cube-face framebuffer-to-texture copies through tested `pp_renderer_gl` dispatch adapters, `NodePanelGrid` live heightmap draw and bake setup now route depth/blend state, depth clears, color-write-mask toggles, active texture selection, bake viewport execution, sun-overlay viewport query, and desktop texture-resize readback through tested `pp_renderer_gl` dispatch adapters, retained CanvasMode overlay/mask/transform paths now route active texture, depth/blend state, transform/cut viewport execution, paint-mode blend/depth state snapshots, and canvas-tip pick framebuffer readback through tested `pp_renderer_gl` dispatch adapters, retained simple UI draw paths now share `legacy_ui_gl_dispatch` for blend-state execution, fallback 2D texture unbinds, `NodeViewport` viewport query/restore, color-buffer clears, and clear-color restore, retained `NodeCanvas` plus `NodeStrokePreview` draw-state paths now route viewport query, clear-color query, color-buffer clear, and clear-color restore through tested `pp_renderer_gl` dispatch helpers, and retained `Canvas` plus `CanvasLayer` stroke/object/thumbnail/frame-clear draw-state paths now route saved viewport or clear-color query and restore through the same tested helpers, but actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, brush-preview compositing, and the retained `ShaderManager::ext_*` compatibility fields still use legacy OpenGL canvas/UI execution | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_tests`; `pp_renderer_gl_capabilities_tests`; `pp_paint_renderer_compositor_tests`; `pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-paint-feedback --texture-copy`; `pano_cli plan-stroke-composite --stroke-blend 10 --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-stroke-composite --layer-blend 4 --dual-blend --texture-copy`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Live stroke/layer compositing chooses its feedback path through `pp_paint_renderer` and renderer services, with OpenGL golden parity and Vulkan/Metal lab tests covering framebuffer-fetch and ping-pong behavior |
|
||||
@@ -139,7 +143,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| DEBT-0042 | Open | Modernization | Accepted Save As and Save Version planning/execution dispatch now consumes pure `pp_app_core` through `App::dialog_save`, `App::dialog_save_ver`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `DocumentFileSaveServices`, `DocumentVersionSaveServices`, and `src/legacy_document_session_services.*`, but the bridge still opens legacy overwrite prompts, calls legacy `Canvas::project_save`, mutates app document name/path/directory fields, marks version saves dirty before saving, updates the title, and handles keyboard/dialog cleanup directly | Preserve current Save As and Save Version behavior while document persistence moves toward app/document/storage/UI services | `pp_app_core_document_session_tests`; `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 simulate-app-session --save-intent save-as`; `pano_cli simulate-app-session --save-intent save-version`; `ctest --preset desktop-fast --build-config Debug` | Save As overwrite prompting, project-save execution, app document metadata updates, title updates, version-save dirty-state handling, and keyboard/dialog cleanup are owned by injected app/document/storage/UI services with `App::dialog_save` and `App::dialog_save_ver` acting only as UI adapters |
|
||||
| DEBT-0043 | Open | Modernization | Equirectangular, layer, animation-frame, depth, and cube-face export planning/execution dispatch now consumes pure `pp_app_core` through `App::dialog_export`, `App::dialog_export_layers`, `App::dialog_export_anim_frames`, `App::dialog_export_depth`, `App::dialog_export_cube_faces`, `pano_cli plan-export-*`, `DocumentExportServices`, and `src/legacy_document_export_services.*`; layer/frame dialogs also consume `plan_document_export_collection_target` plus `PlatformServices::uses_work_directory_document_export_collections()` instead of spelling local iOS branches, but the bridge still calls legacy `Canvas` export methods, owns platform-specific export success messages, creates export directories, handles picker-selected stems, and performs Web prepared-file handoff directly | Preserve current image/collection/depth/cube export behavior while export execution moves toward document/renderer/platform/storage services | `pp_app_core_document_export_tests`; `pp_platform_api_tests`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind layers`; `pano_cli plan-export-target --kind collection --work-dir D:/Paint --doc-name demo --suffix _layers`; `pano_cli simulate-document-export`; `ctest --preset desktop-fast --build-config Debug` | File, collection, stem, depth, and cube export execution, export-directory creation, platform success reporting, Web file handoff, picker-selected stem handling, and legacy canvas export calls are owned by injected document/renderer/platform/storage services with export dialogs acting only as UI adapters |
|
||||
| DEBT-0044 | Open | Modernization | Timelapse and animation MP4 export execution dispatch now consumes pure `pp_app_core` through `App::dialog_timelapse_export`, `App::dialog_export_mp4`, `pano_cli plan-export-menu`, `pano_cli plan-export-target --kind name`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, but the bridge still launches legacy desktop timelapse worker threads, calls `App::rec_export`, calls `Canvas::export_anim_mp4`, owns mobile/Web save callbacks, and emits success messages directly | Preserve current MP4/timelapse export behavior while video export moves toward app/document/renderer/video/platform/storage services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind animation-mp4`; `pano_cli plan-export-menu --kind timelapse`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -animation`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -timelapse`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, mobile/Web save callbacks, and success reporting are owned by injected app/document/renderer/video/platform/storage services with export dialogs acting only as UI adapters |
|
||||
| DEBT-0045 | Open | Modernization | Options-menu preference execution now consumes pure `pp_app_core` through UI scale, viewport scale, RTL direction, VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks plus `AppPreferenceServices` and `src/legacy_app_preference_services.*`, but the bridge still calls legacy `App::set_ui_scale`, `App::set_ui_rtl`, `NodeCanvas::set_density`, `NodeCanvas::set_cursor_visibility`, `App::rec_start`, `App::rec_stop`, and `Settings::save` directly; its VR mode callbacks now call `App` VR wrappers that dispatch to `PlatformServices`, while the actual Windows OpenVR SDK bridge still lives in `WindowsPlatformServices` | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_tests`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Preference persistence, UI/layout direction, viewport density, cursor mode, VR mode start/stop/failure handling, VR-controller state, and auto-timelapse recording side effects are owned by injected app/UI/platform/storage services with options-menu callbacks acting only as UI adapters |
|
||||
| DEBT-0045 | Open | Modernization | Options-menu preference execution now consumes pure `pp_app_core` through UI scale, viewport scale, RTL direction, VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks plus `AppPreferenceServices` and `src/legacy_app_preference_services.*`; viewport-density and cursor-mode execution now delegate to `src/legacy_canvas_view_services.*`, but the bridges still call legacy `App::set_ui_scale`, `App::set_ui_rtl`, `App::rec_start`, `App::rec_stop`, retained canvas view mutation, and `Settings::save` directly; VR mode callbacks now call `App` VR wrappers that dispatch to `PlatformServices`, while the actual Windows OpenVR SDK bridge still lives in `WindowsPlatformServices` | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_tests`; `pp_app_core_canvas_view_tests`; `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-canvas-view-density --density 1.5`; `pano_cli plan-canvas-view-cursor-mode --mode 3`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Preference persistence, UI/layout direction, viewport density, cursor mode, VR mode start/stop/failure handling, VR-controller state, and auto-timelapse recording side effects are owned by injected app/UI/platform/storage services with options-menu callbacks acting only as UI adapters |
|
||||
| DEBT-0046 | Open | Modernization | Startup preference/runtime execution now consumes pure `pp_app_core` through `App::init`, `pano_cli plan-app-startup`, `AppStartupServices`, and `src/legacy_app_startup_services.*`, but the bridge still calls legacy `Settings::set`, `Settings::save`, `App::rec_start`, app VR-controller state mutation, and message-box license warning execution directly | Preserve current startup behavior while app startup moves toward app/preferences/storage/recording/UI services | `pp_app_core_app_startup_tests`; `pano_cli plan-app-startup --run-counter 7 --vr-controllers-disabled --license-invalid`; `pano_cli plan-app-startup --run-counter -1`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Startup preference persistence, auto-timelapse startup, stored VR-controller state, license validation/warning, and startup UI/runtime side effects are owned by injected app/preferences/storage/recording/UI services with `App::init` acting only as orchestration |
|
||||
| DEBT-0047 | Open | Modernization | PPBR brush package export request validation and execution dispatch now consume pure `pp_app_core` through `App::dialog_ppbr_export`, `pano_cli plan-brush-package-export`, `BrushPackageExportServices`, and `src/legacy_brush_package_export_services.*`; PPBR header/path planning now consumes `pp_assets::brush_package`, and the macOS data-directory override now routes through `PlatformServices`, but the bridge still reads `NodeDialogExportPPBR`, carries the legacy `Image` header object outside the pure request, converts to `NodePanelBrushPreset::PPBRInfo`, calls `NodePanelBrushPreset::export_ppbr`, owns desktop worker-thread dispatch, dialog destruction, mobile/Web completion, and success-message behavior directly | Preserve current PPBR export behavior while brush assets, PPBR serialization, picker completion, and UI lifetime move toward asset/storage/UI/platform services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_export_tests`; `pp_platform_api_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --author Artist --dest-path D:/Paint/BrushPreviews --export-data --header-image`; `pano_cli plan-brush-package-export`; `pano_cli plan-brush-package-export --path clouds`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --dest-path D:/Paint/BrushPreviews --no-export-data`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | PPBR metadata collection, header-image ownership, serialization, picker-selected path execution, desktop threading, dialog lifetime, and success UI are owned by injected brush asset/storage/UI/platform services with `App::dialog_ppbr_export` acting only as a UI adapter |
|
||||
| DEBT-0048 | Open | Modernization | ABR/PPBR brush package import execution now consumes pure `pp_app_core` through document-open confirmation callbacks, `pano_cli plan-brush-package-import`, `BrushPackageImportServices`, and `src/legacy_brush_package_import_services.*`; imported brush tip/pattern target paths now consume `pp_assets::brush_package`, but the bridge still launches detached legacy `NodePanelBrushPreset::import_abr`/`import_ppbr` worker threads and depends on the legacy preset panel as the importer/storage owner | Preserve current brush import behavior while brush package parsing, preset storage, progress/error reporting, and UI refresh move toward asset/paint/UI services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_import_tests`; `pano_cli plan-brush-package-import --kind ppbr --path D:/Paint/Brushes/clouds.ppbr`; `pano_cli plan-brush-package-import --kind abr --path D:/Paint/Brushes/clouds.abr`; `pano_cli plan-brush-package-import --kind ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | ABR/PPBR parsing, preset creation/storage, import threading/progress, duplicate asset policy, and UI refresh are owned by injected brush asset/paint/UI services with document-open callbacks only confirming user intent |
|
||||
|
||||
@@ -646,10 +646,13 @@ dispatch through `ToolsMenuServices` in the shared app-shell bridge before the
|
||||
legacy UI/panel/canvas/platform adapters continue execution. The live animation
|
||||
panel route now also checks animation panel visibility and applies animation
|
||||
panel layout state instead of using the grid panel by mistake.
|
||||
`pano_cli plan-canvas-camera-reset` exposes the shared app-core reset-camera
|
||||
state used by live `NodeCanvas::reset_camera()` before retained legacy canvas
|
||||
camera mutation, keeping document-open/session/cloud/tools reset defaults under
|
||||
the same tested policy.
|
||||
`pano_cli plan-canvas-camera-reset`, `pano_cli plan-canvas-view-density`, and
|
||||
`pano_cli plan-canvas-view-cursor-mode` expose shared app-core canvas-view
|
||||
state used by live reset-camera, viewport-density, and cursor-mode paths.
|
||||
Tools reset-camera, document open/new-document reset, cloud download reset, and
|
||||
options viewport/cursor callbacks now dispatch through
|
||||
`src/legacy_canvas_view_services.*` before retained legacy canvas mutation and
|
||||
settings writes.
|
||||
The live SonarPen menu action now asks the active `PlatformServices` instance
|
||||
for availability and startup, removing the local iOS branch from the Tools menu
|
||||
and shared Tools executor while preserving the retained iOS bridge in the
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class CanvasViewCursorMode {
|
||||
never = 0,
|
||||
small_brush = 1,
|
||||
not_painting = 2,
|
||||
always = 3,
|
||||
};
|
||||
|
||||
struct CanvasCameraState {
|
||||
std::array<float, 16> rotation {};
|
||||
std::array<float, 3> position {};
|
||||
@@ -11,6 +21,24 @@ struct CanvasCameraState {
|
||||
std::array<float, 2> pan {};
|
||||
};
|
||||
|
||||
struct CanvasViewDensityPlan {
|
||||
float density = 1.0F;
|
||||
bool recreates_buffers = true;
|
||||
};
|
||||
|
||||
struct CanvasViewCursorModePlan {
|
||||
CanvasViewCursorMode mode = CanvasViewCursorMode::never;
|
||||
};
|
||||
|
||||
class CanvasViewServices {
|
||||
public:
|
||||
virtual ~CanvasViewServices() = default;
|
||||
|
||||
virtual void reset_camera(const CanvasCameraState& state) = 0;
|
||||
virtual void set_density(const CanvasViewDensityPlan& plan) = 0;
|
||||
virtual void set_cursor_mode(const CanvasViewCursorModePlan& plan) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr CanvasCameraState plan_canvas_camera_reset() noexcept
|
||||
{
|
||||
CanvasCameraState state;
|
||||
@@ -26,4 +54,62 @@ struct CanvasCameraState {
|
||||
return state;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<CanvasViewDensityPlan> plan_canvas_view_density(float density)
|
||||
{
|
||||
if (!std::isfinite(density) || density <= 0.0F) {
|
||||
return pp::foundation::Result<CanvasViewDensityPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("canvas view density must be finite and positive"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<CanvasViewDensityPlan>::success(CanvasViewDensityPlan {
|
||||
.density = density,
|
||||
.recreates_buffers = true,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<CanvasViewCursorModePlan> plan_canvas_view_cursor_mode(int mode)
|
||||
{
|
||||
if (mode < static_cast<int>(CanvasViewCursorMode::never)
|
||||
|| mode > static_cast<int>(CanvasViewCursorMode::always)) {
|
||||
return pp::foundation::Result<CanvasViewCursorModePlan>::failure(
|
||||
pp::foundation::Status::out_of_range("canvas cursor mode is out of range"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<CanvasViewCursorModePlan>::success(CanvasViewCursorModePlan {
|
||||
.mode = static_cast<CanvasViewCursorMode>(mode),
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_canvas_camera_reset(CanvasViewServices& services)
|
||||
{
|
||||
services.reset_camera(plan_canvas_camera_reset());
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_canvas_view_density(
|
||||
float density,
|
||||
CanvasViewServices& services)
|
||||
{
|
||||
const auto plan = plan_canvas_view_density(density);
|
||||
if (!plan) {
|
||||
return plan.status();
|
||||
}
|
||||
|
||||
services.set_density(plan.value());
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_canvas_view_cursor_mode(
|
||||
int mode,
|
||||
CanvasViewServices& services)
|
||||
{
|
||||
const auto plan = plan_canvas_view_cursor_mode(mode);
|
||||
if (!plan) {
|
||||
return plan.status();
|
||||
}
|
||||
|
||||
services.set_cursor_mode(plan.value());
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "legacy_app_preference_services.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "node_canvas.h"
|
||||
#include "legacy_canvas_view_services.h"
|
||||
#include "serializer.h"
|
||||
#include "settings.h"
|
||||
|
||||
@@ -24,12 +24,9 @@ public:
|
||||
|
||||
void apply_viewport_scale(const pp::app::ScaleApplicationPlan& plan) override
|
||||
{
|
||||
if (!app_.canvas)
|
||||
return;
|
||||
|
||||
app_.canvas->set_density(plan.scale);
|
||||
Settings::set("vp-scale", Serializer::Float(plan.scale));
|
||||
Settings::save();
|
||||
const auto status = execute_legacy_canvas_view_density(app_, plan.scale);
|
||||
if (!status.ok())
|
||||
LOG("Viewport scale preference failed: %s", status.message);
|
||||
}
|
||||
|
||||
void apply_interface_direction(const pp::app::InterfaceDirectionPlan& plan) override
|
||||
@@ -68,12 +65,9 @@ public:
|
||||
|
||||
void apply_canvas_cursor_mode(const pp::app::StoredIntegerPreferencePlan& plan) override
|
||||
{
|
||||
if (!app_.canvas)
|
||||
return;
|
||||
|
||||
app_.canvas->set_cursor_visibility(static_cast<NodeCanvas::kCursorVisibility>(plan.value));
|
||||
Settings::set("show-cursor", Serializer::Integer(plan.value));
|
||||
Settings::save();
|
||||
const auto status = execute_legacy_canvas_cursor_mode(app_, plan.value);
|
||||
if (!status.ok())
|
||||
LOG("Canvas cursor mode preference failed: %s", status.message);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "app.h"
|
||||
#include "app_core/document_import.h"
|
||||
#include "legacy_canvas_view_services.h"
|
||||
#include "legacy_document_canvas_services.h"
|
||||
#include "legacy_history_services.h"
|
||||
|
||||
@@ -304,8 +305,9 @@ public:
|
||||
|
||||
void reset_camera() override
|
||||
{
|
||||
if (app_.canvas)
|
||||
app_.canvas->reset_camera();
|
||||
const auto status = execute_legacy_canvas_camera_reset(app_);
|
||||
if (!status.ok())
|
||||
LOG("Canvas camera reset failed: %s", status.message);
|
||||
}
|
||||
|
||||
void show_shortcuts_dialog() override
|
||||
|
||||
90
src/legacy_canvas_view_services.cpp
Normal file
90
src/legacy_canvas_view_services.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "legacy_canvas_view_services.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "node_canvas.h"
|
||||
#include "serializer.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace pp::panopainter {
|
||||
namespace {
|
||||
|
||||
class LegacyCanvasViewServices final : public pp::app::CanvasViewServices {
|
||||
public:
|
||||
explicit LegacyCanvasViewServices(App& app) noexcept
|
||||
: app_(app)
|
||||
{
|
||||
}
|
||||
|
||||
void reset_camera(const pp::app::CanvasCameraState& state) override
|
||||
{
|
||||
if (!app_.canvas || !app_.canvas->m_canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
app_.canvas->m_canvas->m_cam_rot = glm::mat4(
|
||||
state.rotation[0], state.rotation[1], state.rotation[2], state.rotation[3],
|
||||
state.rotation[4], state.rotation[5], state.rotation[6], state.rotation[7],
|
||||
state.rotation[8], state.rotation[9], state.rotation[10], state.rotation[11],
|
||||
state.rotation[12], state.rotation[13], state.rotation[14], state.rotation[15]);
|
||||
app_.canvas->m_canvas->m_cam_pos = {
|
||||
state.position[0],
|
||||
state.position[1],
|
||||
state.position[2],
|
||||
};
|
||||
app_.canvas->m_canvas->m_cam_fov = state.field_of_view_degrees;
|
||||
app_.canvas->m_canvas->m_pan = {
|
||||
state.pan[0],
|
||||
state.pan[1],
|
||||
};
|
||||
}
|
||||
|
||||
void set_density(const pp::app::CanvasViewDensityPlan& plan) override
|
||||
{
|
||||
if (!app_.canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
app_.canvas->set_density(plan.density);
|
||||
Settings::set("vp-scale", Serializer::Float(plan.density));
|
||||
Settings::save();
|
||||
}
|
||||
|
||||
void set_cursor_mode(const pp::app::CanvasViewCursorModePlan& plan) override
|
||||
{
|
||||
if (!app_.canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto mode = static_cast<int>(plan.mode);
|
||||
app_.canvas->set_cursor_visibility(static_cast<NodeCanvas::kCursorVisibility>(mode));
|
||||
Settings::set("show-cursor", Serializer::Integer(mode));
|
||||
Settings::save();
|
||||
}
|
||||
|
||||
private:
|
||||
App& app_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
pp::foundation::Status execute_legacy_canvas_camera_reset(App& app)
|
||||
{
|
||||
LegacyCanvasViewServices services(app);
|
||||
return pp::app::execute_canvas_camera_reset(services);
|
||||
}
|
||||
|
||||
pp::foundation::Status execute_legacy_canvas_view_density(App& app, float density)
|
||||
{
|
||||
LegacyCanvasViewServices services(app);
|
||||
return pp::app::execute_canvas_view_density(density, services);
|
||||
}
|
||||
|
||||
pp::foundation::Status execute_legacy_canvas_cursor_mode(App& app, int mode)
|
||||
{
|
||||
LegacyCanvasViewServices services(app);
|
||||
return pp::app::execute_canvas_view_cursor_mode(mode, services);
|
||||
}
|
||||
|
||||
} // namespace pp::panopainter
|
||||
13
src/legacy_canvas_view_services.h
Normal file
13
src/legacy_canvas_view_services.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/canvas_view.h"
|
||||
|
||||
class App;
|
||||
|
||||
namespace pp::panopainter {
|
||||
|
||||
[[nodiscard]] pp::foundation::Status execute_legacy_canvas_camera_reset(App& app);
|
||||
[[nodiscard]] pp::foundation::Status execute_legacy_canvas_view_density(App& app, float density);
|
||||
[[nodiscard]] pp::foundation::Status execute_legacy_canvas_cursor_mode(App& app, int mode);
|
||||
|
||||
} // namespace pp::panopainter
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "app.h"
|
||||
#include "canvas.h"
|
||||
#include "legacy_canvas_view_services.h"
|
||||
#include "node_dialog_cloud.h"
|
||||
#include "node_progress_bar.h"
|
||||
#include "util.h"
|
||||
@@ -122,7 +123,9 @@ public:
|
||||
m->m_message->set_text(progress);
|
||||
});
|
||||
|
||||
app->canvas->reset_camera();
|
||||
const auto reset_status = execute_legacy_canvas_camera_reset(*app);
|
||||
if (!reset_status.ok())
|
||||
LOG("Cloud download camera reset failed: %s", reset_status.message);
|
||||
app->layers->clear();
|
||||
|
||||
app->canvas->m_canvas->project_open_thread(request.selected_path);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "app.h"
|
||||
#include "legacy_brush_package_import_services.h"
|
||||
#include "legacy_canvas_view_services.h"
|
||||
#include "legacy_history_services.h"
|
||||
#include "log.h"
|
||||
#include "node_panel_brush.h"
|
||||
@@ -17,7 +18,9 @@ void open_legacy_project(App& app, const pp::app::DocumentOpenRoute& route)
|
||||
app.doc_name = route.name;
|
||||
app.doc_dir = route.directory;
|
||||
app.doc_path = route.path;
|
||||
app.canvas->reset_camera();
|
||||
const auto reset_status = execute_legacy_canvas_camera_reset(app);
|
||||
if (!reset_status.ok())
|
||||
LOG("Project open camera reset failed: %s", reset_status.message);
|
||||
app.layers->clear();
|
||||
app.canvas->m_canvas->project_open(route.path, [&app](bool success) {
|
||||
if (success)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "legacy_document_session_services.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "legacy_canvas_view_services.h"
|
||||
#include "legacy_history_services.h"
|
||||
#include "node_dialog_open.h"
|
||||
|
||||
@@ -24,7 +25,9 @@ void create_legacy_new_document(
|
||||
app.layers->clear();
|
||||
app.canvas->m_canvas->m_layers.clear();
|
||||
app.canvas->m_canvas->resize(plan.resolution, plan.resolution);
|
||||
app.canvas->reset_camera();
|
||||
const auto reset_status = execute_legacy_canvas_camera_reset(app);
|
||||
if (!reset_status.ok())
|
||||
LOG("New document camera reset failed: %s", reset_status.message);
|
||||
pp::panopainter::clear_legacy_history();
|
||||
|
||||
app.layers->add_layer("Default", false, true);
|
||||
|
||||
@@ -1650,6 +1650,30 @@ if(TARGET pano_cli)
|
||||
LABELS "app;ui;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_view_density_smoke
|
||||
COMMAND pano_cli plan-canvas-view-density --density 1.5)
|
||||
set_tests_properties(pano_cli_plan_canvas_view_density_smoke PROPERTIES
|
||||
LABELS "app;ui;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-view-density\".*\"density\":1.5.*\"recreatesBuffers\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_view_density_rejects_bad_float
|
||||
COMMAND pano_cli plan-canvas-view-density --bad-float)
|
||||
set_tests_properties(pano_cli_plan_canvas_view_density_rejects_bad_float PROPERTIES
|
||||
LABELS "app;ui;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_view_cursor_mode_smoke
|
||||
COMMAND pano_cli plan-canvas-view-cursor-mode --mode 3)
|
||||
set_tests_properties(pano_cli_plan_canvas_view_cursor_mode_smoke PROPERTIES
|
||||
LABELS "app;ui;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-view-cursor-mode\".*\"mode\":3")
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_view_cursor_mode_rejects_invalid
|
||||
COMMAND pano_cli plan-canvas-view-cursor-mode --mode 4)
|
||||
set_tests_properties(pano_cli_plan_canvas_view_cursor_mode_rejects_invalid PROPERTIES
|
||||
LABELS "app;ui;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_cursor_small_brush_smoke
|
||||
COMMAND pano_cli plan-canvas-cursor --mode draw --visibility small-brush --brush-size 9.5)
|
||||
set_tests_properties(pano_cli_plan_canvas_cursor_small_brush_smoke PROPERTIES
|
||||
|
||||
@@ -1,10 +1,46 @@
|
||||
#include "app_core/canvas_view.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
class FakeCanvasViewServices final : public pp::app::CanvasViewServices {
|
||||
public:
|
||||
void reset_camera(const pp::app::CanvasCameraState& state) override
|
||||
{
|
||||
camera_state = state;
|
||||
reset_calls += 1;
|
||||
call_order += "reset;";
|
||||
}
|
||||
|
||||
void set_density(const pp::app::CanvasViewDensityPlan& plan) override
|
||||
{
|
||||
density = plan.density;
|
||||
recreates_buffers = plan.recreates_buffers;
|
||||
density_calls += 1;
|
||||
call_order += "density;";
|
||||
}
|
||||
|
||||
void set_cursor_mode(const pp::app::CanvasViewCursorModePlan& plan) override
|
||||
{
|
||||
cursor_mode = plan.mode;
|
||||
cursor_calls += 1;
|
||||
call_order += "cursor;";
|
||||
}
|
||||
|
||||
pp::app::CanvasCameraState camera_state;
|
||||
float density = 0.0F;
|
||||
bool recreates_buffers = false;
|
||||
pp::app::CanvasViewCursorMode cursor_mode = pp::app::CanvasViewCursorMode::never;
|
||||
int reset_calls = 0;
|
||||
int density_calls = 0;
|
||||
int cursor_calls = 0;
|
||||
std::string call_order;
|
||||
};
|
||||
|
||||
void camera_reset_projects_legacy_defaults(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto state = pp::app::plan_canvas_camera_reset();
|
||||
@@ -22,11 +58,84 @@ void camera_reset_projects_legacy_defaults(pp::tests::Harness& harness)
|
||||
PP_EXPECT(harness, state.pan[1] == 0.0F);
|
||||
}
|
||||
|
||||
void density_rejects_non_positive_or_non_finite_values(pp::tests::Harness& harness)
|
||||
{
|
||||
PP_EXPECT(harness, !pp::app::plan_canvas_view_density(0.0F));
|
||||
PP_EXPECT(harness, !pp::app::plan_canvas_view_density(-1.0F));
|
||||
PP_EXPECT(harness, !pp::app::plan_canvas_view_density(std::nanf("")));
|
||||
}
|
||||
|
||||
void density_projects_buffer_recreation(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_canvas_view_density(1.5F);
|
||||
PP_EXPECT(harness, plan);
|
||||
if (plan) {
|
||||
PP_EXPECT(harness, plan.value().density == 1.5F);
|
||||
PP_EXPECT(harness, plan.value().recreates_buffers);
|
||||
}
|
||||
}
|
||||
|
||||
void cursor_mode_rejects_out_of_range_values(pp::tests::Harness& harness)
|
||||
{
|
||||
PP_EXPECT(harness, !pp::app::plan_canvas_view_cursor_mode(-1));
|
||||
PP_EXPECT(harness, !pp::app::plan_canvas_view_cursor_mode(4));
|
||||
}
|
||||
|
||||
void cursor_mode_projects_legacy_integer_modes(pp::tests::Harness& harness)
|
||||
{
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::plan_canvas_view_cursor_mode(0).value().mode == pp::app::CanvasViewCursorMode::never);
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::plan_canvas_view_cursor_mode(1).value().mode == pp::app::CanvasViewCursorMode::small_brush);
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::plan_canvas_view_cursor_mode(2).value().mode == pp::app::CanvasViewCursorMode::not_painting);
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::plan_canvas_view_cursor_mode(3).value().mode == pp::app::CanvasViewCursorMode::always);
|
||||
}
|
||||
|
||||
void executor_dispatches_canvas_view_services(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeCanvasViewServices services;
|
||||
|
||||
PP_EXPECT(harness, pp::app::execute_canvas_camera_reset(services).ok());
|
||||
PP_EXPECT(harness, pp::app::execute_canvas_view_density(2.0F, services).ok());
|
||||
PP_EXPECT(harness, pp::app::execute_canvas_view_cursor_mode(3, services).ok());
|
||||
|
||||
PP_EXPECT(harness, services.reset_calls == 1);
|
||||
PP_EXPECT(harness, services.density_calls == 1);
|
||||
PP_EXPECT(harness, services.cursor_calls == 1);
|
||||
PP_EXPECT(harness, services.camera_state.field_of_view_degrees == 85.0F);
|
||||
PP_EXPECT(harness, services.density == 2.0F);
|
||||
PP_EXPECT(harness, services.recreates_buffers);
|
||||
PP_EXPECT(harness, services.cursor_mode == pp::app::CanvasViewCursorMode::always);
|
||||
PP_EXPECT(harness, services.call_order == "reset;density;cursor;");
|
||||
}
|
||||
|
||||
void executor_rejects_invalid_canvas_view_requests(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeCanvasViewServices services;
|
||||
|
||||
PP_EXPECT(harness, !pp::app::execute_canvas_view_density(0.0F, services).ok());
|
||||
PP_EXPECT(harness, !pp::app::execute_canvas_view_cursor_mode(99, services).ok());
|
||||
PP_EXPECT(harness, services.density_calls == 0);
|
||||
PP_EXPECT(harness, services.cursor_calls == 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run("camera reset projects legacy defaults", camera_reset_projects_legacy_defaults);
|
||||
harness.run("density rejects non-positive or non-finite values", density_rejects_non_positive_or_non_finite_values);
|
||||
harness.run("density projects buffer recreation", density_projects_buffer_recreation);
|
||||
harness.run("cursor mode rejects out of range values", cursor_mode_rejects_out_of_range_values);
|
||||
harness.run("cursor mode projects legacy integer modes", cursor_mode_projects_legacy_integer_modes);
|
||||
harness.run("executor dispatches canvas view services", executor_dispatches_canvas_view_services);
|
||||
harness.run("executor rejects invalid canvas view requests", executor_rejects_invalid_canvas_view_requests);
|
||||
return harness.finish();
|
||||
}
|
||||
|
||||
@@ -2033,6 +2033,8 @@ void print_help()
|
||||
<< " 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"
|
||||
<< " plan-canvas-camera-reset\n"
|
||||
<< " plan-canvas-view-density [--density N] [--bad-float]\n"
|
||||
<< " plan-canvas-view-cursor-mode [--mode N]\n"
|
||||
<< " plan-canvas-cursor [--mode draw|erase|line|camera|grid|copy|cut|fill|mask-free|mask-line|bucket] [--visibility never|small-brush|not-painting|always] [--brush-size N] [--no-brush] [--drawing] [--alt] [--resizing] [--picking] [--bad-size]\n"
|
||||
<< " plan-grid-operation --kind pick|load|reload|clear|render|commit [--path FILE] [--no-heightmap] [--no-canvas] [--float32] [--float16] [--texture-resolution N] [--samples N]\n"
|
||||
<< " plan-history-operation --kind undo|redo|clear [--undo-count N] [--redo-count N] [--memory-bytes N]\n"
|
||||
@@ -6770,6 +6772,78 @@ int plan_canvas_camera_reset(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int plan_canvas_view_density(int argc, char** argv)
|
||||
{
|
||||
float density = 1.0F;
|
||||
bool bad_float = false;
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const std::string_view key(argv[i]);
|
||||
if (key == "--density") {
|
||||
if (i + 1 >= argc) {
|
||||
print_error("plan-canvas-view-density", "missing value for option");
|
||||
return 2;
|
||||
}
|
||||
const auto value = parse_float_arg(argv[++i]);
|
||||
if (!value) {
|
||||
print_error("plan-canvas-view-density", value.status().message);
|
||||
return 2;
|
||||
}
|
||||
density = value.value();
|
||||
} else if (key == "--bad-float") {
|
||||
bad_float = true;
|
||||
} else {
|
||||
print_error("plan-canvas-view-density", "unknown option");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
const auto plan = pp::app::plan_canvas_view_density(bad_float ? std::nanf("") : density);
|
||||
if (!plan) {
|
||||
print_error("plan-canvas-view-density", plan.status().message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-canvas-view-density\""
|
||||
<< ",\"density\":" << plan.value().density
|
||||
<< ",\"recreatesBuffers\":" << json_bool(plan.value().recreates_buffers)
|
||||
<< "}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
int plan_canvas_view_cursor_mode(int argc, char** argv)
|
||||
{
|
||||
int mode = 0;
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const std::string_view key(argv[i]);
|
||||
if (key == "--mode") {
|
||||
if (i + 1 >= argc) {
|
||||
print_error("plan-canvas-view-cursor-mode", "missing value for option");
|
||||
return 2;
|
||||
}
|
||||
const auto value = parse_i32_arg(argv[++i]);
|
||||
if (!value) {
|
||||
print_error("plan-canvas-view-cursor-mode", value.status().message);
|
||||
return 2;
|
||||
}
|
||||
mode = value.value();
|
||||
} else {
|
||||
print_error("plan-canvas-view-cursor-mode", "unknown option");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
const auto plan = pp::app::plan_canvas_view_cursor_mode(mode);
|
||||
if (!plan) {
|
||||
print_error("plan-canvas-view-cursor-mode", plan.status().message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-canvas-view-cursor-mode\""
|
||||
<< ",\"mode\":" << static_cast<int>(plan.value().mode)
|
||||
<< "}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_canvas_cursor_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
@@ -9933,6 +10007,14 @@ int main(int argc, char** argv)
|
||||
return plan_canvas_camera_reset(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-canvas-view-density") {
|
||||
return plan_canvas_view_density(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-canvas-view-cursor-mode") {
|
||||
return plan_canvas_view_cursor_mode(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-canvas-cursor") {
|
||||
return plan_canvas_cursor(argc, argv);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user