Route canvas camera reset through app core

This commit is contained in:
2026-06-05 01:46:41 +02:00
parent f42a6540be
commit 9373e07d3e
9 changed files with 143 additions and 6 deletions

View File

@@ -248,6 +248,7 @@ add_library(pp_app_core STATIC
src/app_core/brush_ui.h src/app_core/brush_ui.h
src/app_core/canvas_hotkey.h src/app_core/canvas_hotkey.h
src/app_core/canvas_tool_ui.h src/app_core/canvas_tool_ui.h
src/app_core/canvas_view.h
src/app_core/document_animation.h src/app_core/document_animation.h
src/app_core/document_canvas.h src/app_core/document_canvas.h
src/app_core/document_cloud.h src/app_core/document_cloud.h

View File

@@ -226,7 +226,9 @@ Known local toolchain state:
`pp_app_core` contracts while legacy dialogs, pickers, cloud/share/export, `pp_app_core` contracts while legacy dialogs, pickers, cloud/share/export,
Tools, About, history, canvas-clear, settings, and platform SonarPen startup Tools, About, history, canvas-clear, settings, and platform SonarPen startup
execution remain tracked by `DEBT-0029`, `DEBT-0030`, `DEBT-0031`, execution remain tracked by `DEBT-0029`, `DEBT-0030`, `DEBT-0031`,
`DEBT-0033`, `DEBT-0034`, and `DEBT-0035`. `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.
- `src/legacy_app_preference_services.*` is the current app-shell bridge for - `src/legacy_app_preference_services.*` is the current app-shell bridge for
options-menu preference execution. It keeps UI scale, viewport scale, RTL, options-menu preference execution. It keeps UI scale, viewport scale, RTL,
VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks on VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks on

View File

@@ -75,6 +75,12 @@ agent or engineer to remove them without reconstructing context from chat.
small-brush, not-painting, modifier, and malformed-brush states for small-brush, not-painting, modifier, and malformed-brush states for
automation. Legacy `Canvas`/`CanvasModePen` state reads and app cursor automation. Legacy `Canvas`/`CanvasModePen` state reads and app cursor
execution remain open under DEBT-0027. 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-04: DEBT-0036 was narrowed again. Canvas stroke commit, - 2026-06-04: DEBT-0036 was narrowed again. Canvas stroke commit,
thumbnail, and object-draw history paths now query saved blend state through thumbnail, and object-draw history paths now query saved blend state through
tested `pp_renderer_gl` capability-state dispatch; CanvasLayer equirect tested `pp_renderer_gl` capability-state dispatch; CanvasLayer equirect
@@ -121,7 +127,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-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-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-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`, and the `ToolsMenuServices` boundary, and direct command execution is centralized in `src/legacy_app_shell_services.*`; SonarPen availability/startup now routes through `PlatformServices`, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, reset `NodeCanvas` camera state, open legacy shortcuts UI, and rely on the legacy platform adapter for the retained iOS SonarPen bridge | Preserve current Tools menu behavior while UI shell actions move toward app/UI/platform services | `pp_app_core_tools_menu_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`; `ctest --preset desktop-fast --build-config Debug` | Tools panel creation, submenu routing, grid clear, camera reset, shortcuts dialog, and SonarPen dispatch are owned by injected app/UI/platform services with `App::init_menu_tools` acting only as a UI adapter and no legacy Tools adapter | | DEBT-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-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-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-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 | | 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 |

View File

@@ -646,6 +646,10 @@ dispatch through `ToolsMenuServices` in the shared app-shell bridge before the
legacy UI/panel/canvas/platform adapters continue execution. The live animation legacy UI/panel/canvas/platform adapters continue execution. The live animation
panel route now also checks animation panel visibility and applies animation panel route now also checks animation panel visibility and applies animation
panel layout state instead of using the grid panel by mistake. 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.
The live SonarPen menu action now asks the active `PlatformServices` instance The live SonarPen menu action now asks the active `PlatformServices` instance
for availability and startup, removing the local iOS branch from the Tools menu 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 and shared Tools executor while preserving the retained iOS bridge in the

View File

@@ -0,0 +1,29 @@
#pragma once
#include <array>
namespace pp::app {
struct CanvasCameraState {
std::array<float, 16> rotation {};
std::array<float, 3> position {};
float field_of_view_degrees = 85.0F;
std::array<float, 2> pan {};
};
[[nodiscard]] constexpr CanvasCameraState plan_canvas_camera_reset() noexcept
{
CanvasCameraState state;
state.rotation = {
1.0F, 0.0F, 0.0F, 0.0F,
0.0F, 1.0F, 0.0F, 0.0F,
0.0F, 0.0F, 1.0F, 0.0F,
0.0F, 0.0F, 0.0F, 1.0F,
};
state.position = { 0.0F, 0.0F, 0.0F };
state.field_of_view_degrees = 85.0F;
state.pan = { 0.0F, 0.0F };
return state;
}
} // namespace pp::app

View File

@@ -8,6 +8,7 @@
#include "app_core/canvas_hotkey.h" #include "app_core/canvas_hotkey.h"
#include "app_core/canvas_tool_ui.h" #include "app_core/canvas_tool_ui.h"
#include "app_core/canvas_view.h"
#include "app_core/document_animation.h" #include "app_core/document_animation.h"
#include "app.h" #include "app.h"
#include "legacy_canvas_tool_services.h" #include "legacy_canvas_tool_services.h"
@@ -978,10 +979,22 @@ kEventResult NodeCanvas::handle_event(Event* e)
void NodeCanvas::reset_camera() void NodeCanvas::reset_camera()
{ {
m_canvas->m_cam_rot = glm::mat4(1); const auto state = pp::app::plan_canvas_camera_reset();
m_canvas->m_cam_pos = {0, 0, 0}; m_canvas->m_cam_rot = glm::mat4(
m_canvas->m_cam_fov = 85; state.rotation[0], state.rotation[1], state.rotation[2], state.rotation[3],
m_canvas->m_pan = {0, 0}; 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]);
m_canvas->m_cam_pos = {
state.position[0],
state.position[1],
state.position[2],
};
m_canvas->m_cam_fov = state.field_of_view_degrees;
m_canvas->m_pan = {
state.pan[0],
state.pan[1],
};
} }
void NodeCanvas::create_buffers() void NodeCanvas::create_buffers()

View File

@@ -355,6 +355,16 @@ add_test(NAME pp_app_core_canvas_hotkey_tests COMMAND pp_app_core_canvas_hotkey_
set_tests_properties(pp_app_core_canvas_hotkey_tests PROPERTIES set_tests_properties(pp_app_core_canvas_hotkey_tests PROPERTIES
LABELS "app;ui;document;paint;desktop-fast;fuzz") LABELS "app;ui;document;paint;desktop-fast;fuzz")
add_executable(pp_app_core_canvas_view_tests
app_core/canvas_view_tests.cpp)
target_link_libraries(pp_app_core_canvas_view_tests PRIVATE
pp_app_core
pp_test_harness)
add_test(NAME pp_app_core_canvas_view_tests COMMAND pp_app_core_canvas_view_tests)
set_tests_properties(pp_app_core_canvas_view_tests PROPERTIES
LABELS "app;ui;desktop-fast")
add_executable(pp_app_core_grid_ui_tests add_executable(pp_app_core_grid_ui_tests
app_core/grid_ui_tests.cpp) app_core/grid_ui_tests.cpp)
target_link_libraries(pp_app_core_grid_ui_tests PRIVATE target_link_libraries(pp_app_core_grid_ui_tests PRIVATE
@@ -1628,6 +1638,18 @@ if(TARGET pano_cli)
LABELS "app;ui;integration;desktop-fast;fuzz" LABELS "app;ui;integration;desktop-fast;fuzz"
WILL_FAIL TRUE) WILL_FAIL TRUE)
add_test(NAME pano_cli_plan_canvas_camera_reset_smoke
COMMAND pano_cli plan-canvas-camera-reset)
set_tests_properties(pano_cli_plan_canvas_camera_reset_smoke PROPERTIES
LABELS "app;ui;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-camera-reset\".*\"rotation\":\\[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1\\].*\"position\":\\[0,0,0\\].*\"fieldOfViewDegrees\":85.*\"pan\":\\[0,0\\]")
add_test(NAME pano_cli_plan_canvas_camera_reset_rejects_options
COMMAND pano_cli plan-canvas-camera-reset --unexpected)
set_tests_properties(pano_cli_plan_canvas_camera_reset_rejects_options PROPERTIES
LABELS "app;ui;integration;desktop-fast;fuzz"
WILL_FAIL TRUE)
add_test(NAME pano_cli_plan_canvas_cursor_small_brush_smoke 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) 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 set_tests_properties(pano_cli_plan_canvas_cursor_small_brush_smoke PROPERTIES

View File

@@ -0,0 +1,32 @@
#include "app_core/canvas_view.h"
#include "test_harness.h"
#include <cstddef>
namespace {
void camera_reset_projects_legacy_defaults(pp::tests::Harness& harness)
{
const auto state = pp::app::plan_canvas_camera_reset();
for (std::size_t index = 0; index < state.rotation.size(); ++index) {
const bool diagonal = index == 0 || index == 5 || index == 10 || index == 15;
PP_EXPECT(harness, state.rotation[index] == (diagonal ? 1.0F : 0.0F));
}
PP_EXPECT(harness, state.position[0] == 0.0F);
PP_EXPECT(harness, state.position[1] == 0.0F);
PP_EXPECT(harness, state.position[2] == 0.0F);
PP_EXPECT(harness, state.field_of_view_degrees == 85.0F);
PP_EXPECT(harness, state.pan[0] == 0.0F);
PP_EXPECT(harness, state.pan[1] == 0.0F);
}
} // namespace
int main()
{
pp::tests::Harness harness;
harness.run("camera reset projects legacy defaults", camera_reset_projects_legacy_defaults);
return harness.finish();
}

View File

@@ -7,6 +7,7 @@
#include "app_core/brush_ui.h" #include "app_core/brush_ui.h"
#include "app_core/canvas_hotkey.h" #include "app_core/canvas_hotkey.h"
#include "app_core/canvas_tool_ui.h" #include "app_core/canvas_tool_ui.h"
#include "app_core/canvas_view.h"
#include "app_core/document_animation.h" #include "app_core/document_animation.h"
#include "app_core/document_canvas.h" #include "app_core/document_canvas.h"
#include "app_core/document_export.h" #include "app_core/document_export.h"
@@ -2031,6 +2032,7 @@ void print_help()
<< " plan-canvas-hotkey --event key-down|key-up|touch-tap --key e|z|s|tab|alt|android-back|bracket-left|bracket-right [--ctrl] [--shift] [--mouse-focus] [--undo-count N] [--redo-count N] [--touch-fingers N]\n" << " plan-canvas-hotkey --event key-down|key-up|touch-tap --key e|z|s|tab|alt|android-back|bracket-left|bracket-right [--ctrl] [--shift] [--mouse-focus] [--undo-count N] [--redo-count N] [--touch-fingers N]\n"
<< " plan-canvas-tool --kind draw|erase|line|camera|grid|copy|cut|fill|mask-free|mask-line|bucket|pick|touch-lock [--current-mode-draw]\n" << " plan-canvas-tool --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-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-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-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-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" << " plan-history-operation --kind undo|redo|clear [--undo-count N] [--redo-count N] [--memory-bytes N]\n"
@@ -6746,6 +6748,28 @@ int plan_canvas_tool_state(int argc, char** argv)
return 0; return 0;
} }
int plan_canvas_camera_reset(int argc, char** argv)
{
if (argc > 2) {
print_error("plan-canvas-camera-reset", "unknown option");
return 2;
}
const auto state = pp::app::plan_canvas_camera_reset();
std::cout << "{\"ok\":true,\"command\":\"plan-canvas-camera-reset\""
<< ",\"rotation\":[";
for (std::size_t index = 0; index < state.rotation.size(); ++index) {
if (index != 0U) {
std::cout << ",";
}
std::cout << state.rotation[index];
}
std::cout << "],\"position\":[" << state.position[0] << "," << state.position[1] << "," << state.position[2]
<< "],\"fieldOfViewDegrees\":" << state.field_of_view_degrees
<< ",\"pan\":[" << state.pan[0] << "," << state.pan[1] << "]}\n";
return 0;
}
pp::foundation::Status parse_plan_canvas_cursor_args( pp::foundation::Status parse_plan_canvas_cursor_args(
int argc, int argc,
char** argv, char** argv,
@@ -9905,6 +9929,10 @@ int main(int argc, char** argv)
return plan_canvas_tool_state(argc, argv); return plan_canvas_tool_state(argc, argv);
} }
if (command == "plan-canvas-camera-reset") {
return plan_canvas_camera_reset(argc, argv);
}
if (command == "plan-canvas-cursor") { if (command == "plan-canvas-cursor") {
return plan_canvas_cursor(argc, argv); return plan_canvas_cursor(argc, argv);
} }