Route picker callbacks through platform services

This commit is contained in:
2026-06-03 04:07:09 +02:00
parent 1e0500a3f7
commit 0e77ca6ba8
7 changed files with 217 additions and 124 deletions

View File

@@ -10,7 +10,7 @@ Keep it updated as platform paths move to shared CMake targets.
| Platform/Target | Current Entrypoint | Notes | | Platform/Target | Current Entrypoint | Notes |
| --- | --- | --- | | --- | --- | --- |
| Windows desktop | Root `CMakeLists.txt`, preset `windows-msvc-default`; target preset `windows-vs2026-x64` retained for VS 2026 | Raw `.sln/.vcxproj` files removed on 2026-05-31; local machine currently uses Visual Studio 17 2022; `PanoPainter` now links through `pp_platform_windows` and `panopainter_app`, with Windows/vendor link dependencies owned by the platform shell, runtime payload deployment in `cmake/PanoPainterRuntime.cmake`, tested app-level document-open routing plus open/close/save session decisions owned by `pp_app_core`, SDK-free clipboard/cursor/virtual-keyboard/display/share service contracts owned by `pp_platform_api`, retained third-party source dependencies contained by `pp_legacy_vendor`, retained asset/file/serialization sources contained by `pp_legacy_assets_io`, retained paint/document/canvas sources contained by `pp_legacy_paint_document`, retained OpenGL runtime sources contained by `pp_legacy_renderer_gl` and folded into `pp_legacy_engine`, retained runtime shell sources contained by `pp_legacy_engine`, retained base UI controls contained by `pp_legacy_ui_core` and folded into `pp_legacy_app`, app orchestration/version metadata owned by `panopainter_app`, and app-specific modal/dialog/panel/canvas workflow nodes owned by `pp_panopainter_ui` | | Windows desktop | Root `CMakeLists.txt`, preset `windows-msvc-default`; target preset `windows-vs2026-x64` retained for VS 2026 | Raw `.sln/.vcxproj` files removed on 2026-05-31; local machine currently uses Visual Studio 17 2022; `PanoPainter` now links through `pp_platform_windows` and `panopainter_app`, with Windows/vendor link dependencies owned by the platform shell, runtime payload deployment in `cmake/PanoPainterRuntime.cmake`, tested app-level document-open routing plus open/close/save session decisions owned by `pp_app_core`, SDK-free clipboard/cursor/virtual-keyboard/display/share/picker service contracts owned by `pp_platform_api`, retained third-party source dependencies contained by `pp_legacy_vendor`, retained asset/file/serialization sources contained by `pp_legacy_assets_io`, retained paint/document/canvas sources contained by `pp_legacy_paint_document`, retained OpenGL runtime sources contained by `pp_legacy_renderer_gl` and folded into `pp_legacy_engine`, retained runtime shell sources contained by `pp_legacy_engine`, retained base UI controls contained by `pp_legacy_ui_core` and folded into `pp_legacy_app`, app orchestration/version metadata owned by `panopainter_app`, and app-specific modal/dialog/panel/canvas workflow nodes owned by `pp_panopainter_ui` |
| Windows AppX | `PanoPainterPackage/Package.appxmanifest`, `.wapproj` referenced by solution | Distribution packaging | | Windows AppX | `PanoPainterPackage/Package.appxmanifest`, `.wapproj` referenced by solution | Distribution packaging |
| macOS | `PanoPainter-OSX/` project files and `Info.plist` | Uses `NSOpenGLView` today | | macOS | `PanoPainter-OSX/` project files and `Info.plist` | Uses `NSOpenGLView` today |
| iOS | `PanoPainter/Info.plist`, related Apple sources | Uses OpenGL ES today | | iOS | `PanoPainter/Info.plist`, related Apple sources | Uses OpenGL ES today |
@@ -446,9 +446,10 @@ Known local toolchain state:
platform clipboard bridges. platform clipboard bridges.
- `pp_platform_api` exposes the SDK-free `PlatformServices` interface for - `pp_platform_api` exposes the SDK-free `PlatformServices` interface for
clipboard text, cursor visibility, virtual-keyboard visibility, external clipboard text, cursor visibility, virtual-keyboard visibility, external
file display, and file sharing; live app execution now reaches retained file display, file sharing, and picker callbacks; live app execution now
platform bridges through the debt-tracked legacy adapter in reaches retained platform bridges through the debt-tracked legacy adapter in
`app_events.cpp`. `app_events.cpp`. The iOS/Web save-with-writer overload remains a separate
app method until export handoff is isolated.
- `pano_cli plan-cloud-upload` exposes `pp_app_core` cloud upload availability, - `pano_cli plan-cloud-upload` exposes `pp_app_core` cloud upload availability,
new-document warning, publish prompt, and save-before-upload planning as JSON; new-document warning, publish prompt, and save-before-upload planning as JSON;
the live cloud upload command consumes the same start contract before the live cloud upload command consumes the same start contract before
@@ -487,8 +488,8 @@ Known local toolchain state:
planning before platform clipboard callbacks. planning before platform clipboard callbacks.
- `pp_platform_api_tests` covers service dispatch for clipboard read/write, - `pp_platform_api_tests` covers service dispatch for clipboard read/write,
empty clipboard writes, cursor visibility, virtual-keyboard visibility, empty clipboard writes, cursor visibility, virtual-keyboard visibility,
external file display, and file sharing without platform SDK headers or a external file display, file sharing, and picker callbacks without platform
window. SDK headers or a window.
- `pp_app_core_document_cloud_tests` covers cloud upload no-canvas, - `pp_app_core_document_cloud_tests` covers cloud upload no-canvas,
new-document warning, clean publish prompt, and dirty save-before-upload new-document warning, clean publish prompt, and dirty save-before-upload
decisions, plus cloud browse no-canvas/show-browser and selected-download decisions, plus cloud browse no-canvas/show-browser and selected-download

View File

@@ -67,7 +67,7 @@ and validation command.
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Mouse/keyboard/touch/gestures/cursor | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*`, app | Cursor visibility decision tests, platform service dispatch tests, synthetic event playback | | Mouse/keyboard/touch/gestures/cursor | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*`, app | Cursor visibility decision tests, platform service dispatch tests, synthetic event playback |
| Wacom pressure | `WacomTablet` | `pp_platform_windows` | Adapter smoke with fallback | | Wacom pressure | `WacomTablet` | `pp_platform_windows` | Adapter smoke with fallback |
| Clipboard/file picker/share/display | `App` platform methods | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Clipboard read/write, share saved-path, picked-path, and display-file decision tests, platform service display/share dispatch tests, picker callback tests, platform smoke or mocked service | | Clipboard/file picker/share/display | `App` platform methods | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Clipboard read/write, share saved-path, picked-path, and display-file decision tests, platform service display/share/picker dispatch tests, platform smoke or mocked service |
| Virtual keyboard | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Keyboard visibility decision tests, platform service dispatch tests, platform smoke | | Virtual keyboard | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Keyboard visibility decision tests, platform service dispatch tests, platform smoke |
| OpenVR desktop | `HMD`, `Vive`, `app_vr` | `pp_platform_vr`, app | Compile gate and mocked pose tests | | OpenVR desktop | `HMD`, `Vive`, `app_vr` | `pp_platform_vr`, app | Compile gate and mocked pose tests |
| Quest/OVR | Android Quest files | `pp_platform_android_quest` | Compile/package gate | | Quest/OVR | Android Quest files | `pp_platform_android_quest` | Compile/package gate |

View File

@@ -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-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-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, `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-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/share/platform-I/O/display/keyboard/cloud contracts, but document creation/loading, brush import execution, saving, export execution, platform share execution, picker callback execution, display-file platform execution, keyboard platform 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_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-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/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, `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-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/share/platform-I/O/display/keyboard/cloud contracts, but document creation/loading, brush import execution, saving, export execution, 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_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-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-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-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 | | 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 |
@@ -35,7 +35,7 @@ agent or engineer to remove them without reconstructing context from chat.
| DEBT-0014 | Open | Modernization | `windows-clangcl-asan` now configures as a headless Ninja/clang-cl preset and uses the release MSVC runtime required by ASan, but local builds still fail because installed clang-cl 18.1.8 is paired with VS 2026-preview STL headers that require Clang 20 or newer | Sanitizer validation should be local and repeatable, but this machine's compiler/header pairing is incompatible | `cmake --fresh --preset windows-clangcl-asan`; `cmake --build --preset windows-clangcl-asan --target pp_foundation` | Install/use Clang 20+ with the VS 2026 STL, or point the preset at a compatible VS 2022 toolchain, then make `platform-build.ps1 -Presets windows-clangcl-asan` pass for the headless matrix | | DEBT-0014 | Open | Modernization | `windows-clangcl-asan` now configures as a headless Ninja/clang-cl preset and uses the release MSVC runtime required by ASan, but local builds still fail because installed clang-cl 18.1.8 is paired with VS 2026-preview STL headers that require Clang 20 or newer | Sanitizer validation should be local and repeatable, but this machine's compiler/header pairing is incompatible | `cmake --fresh --preset windows-clangcl-asan`; `cmake --build --preset windows-clangcl-asan --target pp_foundation` | Install/use Clang 20+ with the VS 2026 STL, or point the preset at a compatible VS 2022 toolchain, then make `platform-build.ps1 -Presets windows-clangcl-asan` pass for the headless matrix |
| DEBT-0015 | Open | Modernization | Cursor visibility requests now consume pure `pp_app_core` planning through `pano_cli plan-cursor-visibility`, but live cursor execution still reaches retained Win32/macOS platform bridges from `App::show_cursor` and `App::hide_cursor` | Keep canvas cursor behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-cursor-visibility --visible`; `ctest --preset desktop-fast --build-config Debug` | Cursor visibility execution is owned by `pp_platform_*` services and live app code depends on an injected platform interface instead of direct singleton/platform calls | | DEBT-0015 | Open | Modernization | Cursor visibility requests now consume pure `pp_app_core` planning through `pano_cli plan-cursor-visibility`, but live cursor execution still reaches retained Win32/macOS platform bridges from `App::show_cursor` and `App::hide_cursor` | Keep canvas cursor behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-cursor-visibility --visible`; `ctest --preset desktop-fast --build-config Debug` | Cursor visibility execution is owned by `pp_platform_*` services and live app code depends on an injected platform interface instead of direct singleton/platform calls |
| DEBT-0016 | Open | Modernization | Clipboard get/set requests now consume pure `pp_app_core` planning through `pano_cli plan-clipboard-read` and `pano_cli plan-clipboard-write`, but live clipboard execution still reaches retained Win32/Apple/Android platform bridges from `App::clipboard_get_text` and `App::clipboard_set_text` | Keep picker/color text clipboard behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-clipboard-write --text #ff00aa`; `ctest --preset desktop-fast --build-config Debug` | Clipboard execution is owned by `pp_platform_*` services and live app code depends on an injected platform interface instead of direct singleton/platform calls | | DEBT-0016 | Open | Modernization | Clipboard get/set requests now consume pure `pp_app_core` planning through `pano_cli plan-clipboard-read` and `pano_cli plan-clipboard-write`, but live clipboard execution still reaches retained Win32/Apple/Android platform bridges from `App::clipboard_get_text` and `App::clipboard_set_text` | Keep picker/color text clipboard behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-clipboard-write --text #ff00aa`; `ctest --preset desktop-fast --build-config Debug` | Clipboard execution is owned by `pp_platform_*` services and live app code depends on an injected platform interface instead of direct singleton/platform calls |
| DEBT-0017 | Open | Modernization | `App::clipboard_get_text`, `App::clipboard_set_text`, `App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, `App::hideKeyboard`, `App::display_file`, and `App::share_file` now call the SDK-free `pp::platform::PlatformServices` interface, but the live implementation is still a legacy adapter in `app_events.cpp` that forwards to retained Win32/Apple/Android bridge functions and retained no-op branches | Preserve behavior while moving platform execution behind a testable service boundary before platform shell implementations are injected | `pp_platform_api_tests`; `pp_app_core_document_platform_io_tests`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug` | Replace the `app_events.cpp` legacy adapter with injected `pp_platform_*` service implementations owned by each platform shell | | DEBT-0017 | Open | Modernization | `App::clipboard_get_text`, `App::clipboard_set_text`, `App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, `App::hideKeyboard`, `App::display_file`, `App::share_file`, `App::pick_image`, `App::pick_file`, the non-writer `App::pick_file_save`, and `App::pick_dir` now call the SDK-free `pp::platform::PlatformServices` interface, but the live implementation is still a legacy adapter in `app_events.cpp` that forwards to retained Win32/Apple/Android/Linux/Web bridge functions and retained no-op branches; the iOS/Web save-with-writer overload remains separate until export handoff is isolated | Preserve behavior while moving platform execution behind a testable service boundary before platform shell implementations are injected | `pp_platform_api_tests`; `pp_app_core_document_platform_io_tests`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug` | Replace the `app_events.cpp` legacy adapter with injected `pp_platform_*` service implementations owned by each platform shell |
## Closed Debt ## Closed Debt

View File

@@ -470,10 +470,12 @@ app-core clipboard text decisions used by live clipboard get/set requests
before retained platform clipboard bridges continue. before retained platform clipboard bridges continue.
`pp_platform_api` now owns a headless `PlatformServices` interface for `pp_platform_api` now owns a headless `PlatformServices` interface for
clipboard text, cursor visibility, virtual-keyboard visibility, external file clipboard text, cursor visibility, virtual-keyboard visibility, external file
display, and file sharing. Live app clipboard/cursor/keyboard/display/share display, file sharing, image/file/save-file pickers, and directory pickers.
execution routes through a debt-tracked legacy adapter in `app_events.cpp`, so Live app clipboard/cursor/keyboard/display/share/picker execution routes
behavior is preserved while later platform shell implementations can replace through a debt-tracked legacy adapter in `app_events.cpp`, so behavior is
direct bridge calls. preserved while later platform shell implementations can replace direct bridge
calls. The iOS/Web save-with-writer overload remains separate because it writes
a temporary/exported file before handing control to the platform.
`pano_cli plan-cloud-upload` exposes the app-core cloud upload decision used by `pano_cli plan-cloud-upload` exposes the app-core cloud upload decision used by
the live cloud upload command for missing-canvas, new-document warning, publish the live cloud upload command for missing-canvas, new-document warning, publish
prompt, and dirty-document save-before-upload states before legacy UI, canvas, prompt, and dirty-document save-before-upload states before legacy UI, canvas,
@@ -987,9 +989,9 @@ Results:
- `pp_platform_api_tests` passed, covering the SDK-free `PlatformServices` - `pp_platform_api_tests` passed, covering the SDK-free `PlatformServices`
interface for clipboard read/write, empty clipboard writes, cursor interface for clipboard read/write, empty clipboard writes, cursor
visibility dispatch, virtual-keyboard visibility dispatch, external file visibility dispatch, virtual-keyboard visibility dispatch, external file
display dispatch, and file sharing dispatch. The live app now consumes this display dispatch, file sharing dispatch, and picker callback dispatch. The
interface through the legacy platform adapter for live app now consumes this interface through the legacy platform adapter for
clipboard/cursor/keyboard/display/share execution. clipboard/cursor/keyboard/display/share/picker execution.
- `panopainter_validate_shaders` passed, validating 25 shader programs and 7 - `panopainter_validate_shaders` passed, validating 25 shader programs and 7
shader includes for stage markers and include graph integrity. shader includes for stage markers and include graph integrity.
- `pp_renderer_gl_capabilities_tests` passed on default MSVC, vcpkg-headless, - `pp_renderer_gl_capabilities_tests` passed on default MSVC, vcpkg-headless,

View File

@@ -63,6 +63,27 @@ void webgl_sync();
namespace { namespace {
std::string build_supported_files_filter(const std::vector<std::string>& types)
{
std::string filter = "Supported Files (";
bool first_type = true;
for (const auto& t : types)
{
filter.append(std::string(first_type ? "" : " ,") + "*." + t);
first_type = false;
}
filter.append(")");
filter.push_back(0);
first_type = true;
for (const auto& t : types)
{
filter.append(std::string(first_type ? "" : ";") + "*." + t);
first_type = false;
}
filter.push_back(0);
return filter;
}
class LegacyPlatformServices final : public pp::platform::PlatformServices { class LegacyPlatformServices final : public pp::platform::PlatformServices {
public: public:
[[nodiscard]] std::string clipboard_text() override [[nodiscard]] std::string clipboard_text() override
@@ -123,6 +144,108 @@ public:
#endif #endif
} }
void pick_image(pp::platform::PickedPathCallback callback) override
{
#ifdef __IOS__
dispatch_async(dispatch_get_main_queue(), ^{
[App::I->ios_view pick_photo:callback];
});
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
NSArray* fileTypes = [NSArray arrayWithObjects:@"png", @"PNG", @"jpg", @"JPG", @"jpeg", nil];
std::string path = [App::I->osx_view pick_file:fileTypes];
invoke_picked_path_if_selected(path, callback);
});
#elif __ANDROID__
android_pick_file(callback);
#elif _WIN32
std::string path = win32_open_file("Image Files (*.jpg, *.png)\0*.jpg;*.png");
invoke_picked_path_if_selected(path, callback);
#elif __LINUX__
if (auto p = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false))
invoke_picked_path_if_selected(p, callback);
#elif __WEB__
webgl_pick_file(callback);
#else
(void)callback;
#endif
}
void pick_file(std::vector<std::string> file_types, pp::platform::PickedPathCallback callback) override
{
#ifdef __IOS__
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:file_types.size()];
for (const auto& t : file_types)
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
[App::I->ios_view pick_file:fileTypes then:callback];
});
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:file_types.size()];
for (const auto& t : file_types)
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
std::string path = [App::I->osx_view pick_file:fileTypes];
invoke_picked_path_if_selected(path, callback);
});
#elif __ANDROID__
android_pick_file(callback);
#elif _WIN32
const std::string filter = build_supported_files_filter(file_types);
std::string path = win32_open_file(filter.c_str());
invoke_picked_path_if_selected(path, callback);
#elif __LINUX__
if (auto p = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false))
invoke_picked_path_if_selected(p, callback);
#elif __WEB__
webgl_pick_file(callback);
#else
(void)file_types;
(void)callback;
#endif
}
void pick_save_file(std::vector<std::string> file_types, pp::platform::PickedPathCallback callback) override
{
#if __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:file_types.size()];
for (const auto& t : file_types)
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
std::string path = [App::I->osx_view pick_file_save:fileTypes];
invoke_picked_path_if_selected(path, callback);
});
#elif __ANDROID__
android_pick_file_save(callback);
#elif _WIN32
const std::string filter = build_supported_files_filter(file_types);
std::string path = win32_save_file(filter.c_str());
invoke_picked_path_if_selected(path, callback);
#else
(void)file_types;
(void)callback;
#endif
}
void pick_directory(pp::platform::PickedPathCallback callback) override
{
#ifdef __IOS__
(void)callback;
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
std::string path = [App::I->osx_view pick_dir];
invoke_picked_path_if_selected(path, callback);
});
#elif __ANDROID__
(void)callback;
#elif _WIN32
std::string path = win32_open_dir();
invoke_picked_path_if_selected(path, callback);
#else
(void)callback;
#endif
}
void display_file(std::string_view path) override void display_file(std::string_view path) override
{ {
const std::string value(path); const std::string value(path);
@@ -271,74 +394,13 @@ void App::hideKeyboard()
void App::pick_image(std::function<void(std::string path)> callback) void App::pick_image(std::function<void(std::string path)> callback)
{ {
redraw = true; redraw = true;
#ifdef __IOS__ legacy_platform_services().pick_image(std::move(callback));
dispatch_async(dispatch_get_main_queue(), ^{
[ios_view pick_photo:callback];
});
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
NSArray* fileTypes = [NSArray arrayWithObjects:@"png", @"PNG", @"jpg", @"JPG", @"jpeg", nil];
std::string path = [osx_view pick_file:fileTypes];
invoke_picked_path_if_selected(path, callback);
});
#elif __ANDROID__
android_pick_file(callback);
#elif _WIN32
std::string path = win32_open_file("Image Files (*.jpg, *.png)\0*.jpg;*.png");
invoke_picked_path_if_selected(path, callback);
#elif __LINUX__
if (auto p = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false))
invoke_picked_path_if_selected(p, callback);
#elif __WEB__
webgl_pick_file(callback);
#endif
} }
void App::pick_file(std::vector<std::string> types, std::function<void (std::string)> callback) void App::pick_file(std::vector<std::string> types, std::function<void (std::string)> callback)
{ {
redraw = true; redraw = true;
#ifdef __IOS__ legacy_platform_services().pick_file(std::move(types), std::move(callback));
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:types.size()];
for (const auto& t : types)
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
[ios_view pick_file:fileTypes then:callback];
});
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:types.size()];
for (const auto& t : types)
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
std::string path = [osx_view pick_file:fileTypes];
invoke_picked_path_if_selected(path, callback);
});
#elif __ANDROID__
android_pick_file(callback);
#elif _WIN32
std::string filter = "Supported Files (";
bool first_type = true;
for (auto& t : types)
{
filter.append(std::string(first_type ? "" : " ,") + "*." + t);
first_type = false;
}
filter.append(")");
filter.push_back(0);
first_type = true;
for (auto& t : types)
{
filter.append(std::string(first_type ? "" : ";") + "*." + t);
first_type = false;
}
filter.push_back(0);
std::string path = win32_open_file(filter.c_str());
invoke_picked_path_if_selected(path, callback);
#elif __LINUX__
if (auto p = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false))
invoke_picked_path_if_selected(p, callback);
#elif __WEB__
webgl_pick_file(callback);
#endif
} }
#if __IOS__ #if __IOS__
@@ -370,57 +432,14 @@ void App::pick_file_save(const std::string& type, const std::string& default_nam
void App::pick_file_save(std::vector<std::string> types, std::function<void(std::string)> callback) void App::pick_file_save(std::vector<std::string> types, std::function<void(std::string)> callback)
{ {
redraw = true; redraw = true;
#if __OSX__ legacy_platform_services().pick_save_file(std::move(types), std::move(callback));
dispatch_async(dispatch_get_main_queue(), ^{
//NSArray* fileTypes = [NSArray arrayWithObjects:@"ppi", @"PPI", nil];
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:types.size()];
for (const auto& t : types)
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
std::string path = [osx_view pick_file_save:fileTypes];
invoke_picked_path_if_selected(path, callback);
});
#elif __ANDROID__
android_pick_file_save(callback);
#elif _WIN32
std::string filter = "Supported Files (";
bool first_type = true;
for (auto& t : types)
{
filter.append(std::string(first_type ? "" : " ,") + "*." + t);
first_type = false;
}
filter.append(")");
filter.push_back(0);
first_type = true;
for (auto& t : types)
{
filter.append(std::string(first_type ? "" : ";") + "*." + t);
first_type = false;
}
filter.push_back(0);
std::string path = win32_save_file(filter.c_str());
invoke_picked_path_if_selected(path, callback);
#endif
} }
#endif #endif
void App::pick_dir(std::function<void(std::string path)> callback) void App::pick_dir(std::function<void(std::string path)> callback)
{ {
redraw = true; redraw = true;
#ifdef __IOS__ legacy_platform_services().pick_directory(std::move(callback));
// NOT IMPLEMENTED
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
std::string path = [osx_view pick_dir];
invoke_picked_path_if_selected(path, callback);
});
#elif __ANDROID__
// NOT IMPLEMENTED
#elif _WIN32
// TODO: to be implemented
std::string path = win32_open_dir();
invoke_picked_path_if_selected(path, callback);
#endif
} }
void App::display_file(std::string path) void App::display_file(std::string path)

View File

@@ -1,10 +1,14 @@
#pragma once #pragma once
#include <functional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector>
namespace pp::platform { namespace pp::platform {
using PickedPathCallback = std::function<void(std::string path)>;
class PlatformServices { class PlatformServices {
public: public:
virtual ~PlatformServices() = default; virtual ~PlatformServices() = default;
@@ -15,6 +19,10 @@ public:
virtual void set_virtual_keyboard_visible(bool visible) = 0; virtual void set_virtual_keyboard_visible(bool visible) = 0;
virtual void display_file(std::string_view path) = 0; virtual void display_file(std::string_view path) = 0;
virtual void share_file(std::string_view path) = 0; virtual void share_file(std::string_view path) = 0;
virtual void pick_image(PickedPathCallback callback) = 0;
virtual void pick_file(std::vector<std::string> file_types, PickedPathCallback callback) = 0;
virtual void pick_save_file(std::vector<std::string> file_types, PickedPathCallback callback) = 0;
virtual void pick_directory(PickedPathCallback callback) = 0;
}; };
} }

View File

@@ -4,6 +4,7 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <utility> #include <utility>
#include <vector>
namespace { namespace {
@@ -51,16 +52,51 @@ public:
shared_path.assign(path); shared_path.assign(path);
} }
void pick_image(pp::platform::PickedPathCallback callback) override
{
++pick_image_requests;
callback(picker_path);
}
void pick_file(std::vector<std::string> file_types, pp::platform::PickedPathCallback callback) override
{
++pick_file_requests;
picked_file_types = std::move(file_types);
callback(picker_path);
}
void pick_save_file(std::vector<std::string> file_types, pp::platform::PickedPathCallback callback) override
{
++pick_save_file_requests;
save_file_types = std::move(file_types);
callback(save_path);
}
void pick_directory(pp::platform::PickedPathCallback callback) override
{
++pick_directory_requests;
callback(directory_path);
}
int clipboard_reads = 0; int clipboard_reads = 0;
int clipboard_writes = 0; int clipboard_writes = 0;
int cursor_updates = 0; int cursor_updates = 0;
int keyboard_updates = 0; int keyboard_updates = 0;
int display_file_requests = 0; int display_file_requests = 0;
int share_file_requests = 0; int share_file_requests = 0;
int pick_image_requests = 0;
int pick_file_requests = 0;
int pick_save_file_requests = 0;
int pick_directory_requests = 0;
bool cursor_visible = false; bool cursor_visible = false;
bool keyboard_visible = false; bool keyboard_visible = false;
std::string displayed_path; std::string displayed_path;
std::string shared_path; std::string shared_path;
std::string picker_path = "D:/Paint/import.png";
std::string save_path = "D:/Paint/export.ppi";
std::string directory_path = "D:/Paint";
std::vector<std::string> picked_file_types;
std::vector<std::string> save_file_types;
private: private:
std::string clipboard_value_; std::string clipboard_value_;
@@ -118,6 +154,32 @@ void platform_services_dispatch_file_actions(pp::tests::Harness& harness)
PP_EXPECT(harness, fake.shared_path == "D:/Paint/demo.ppi"); PP_EXPECT(harness, fake.shared_path == "D:/Paint/demo.ppi");
} }
void platform_services_dispatch_picker_callbacks(pp::tests::Harness& harness)
{
FakePlatformServices fake("unused");
pp::platform::PlatformServices& services = fake;
std::string image_path;
std::string file_path;
std::string save_path;
std::string directory_path;
services.pick_image([&](std::string path) { image_path = std::move(path); });
services.pick_file({ "ppi", "ppbr" }, [&](std::string path) { file_path = std::move(path); });
services.pick_save_file({ "ppi" }, [&](std::string path) { save_path = std::move(path); });
services.pick_directory([&](std::string path) { directory_path = std::move(path); });
PP_EXPECT(harness, fake.pick_image_requests == 1);
PP_EXPECT(harness, fake.pick_file_requests == 1);
PP_EXPECT(harness, fake.pick_save_file_requests == 1);
PP_EXPECT(harness, fake.pick_directory_requests == 1);
PP_EXPECT(harness, image_path == "D:/Paint/import.png");
PP_EXPECT(harness, file_path == "D:/Paint/import.png");
PP_EXPECT(harness, save_path == "D:/Paint/export.ppi");
PP_EXPECT(harness, directory_path == "D:/Paint");
PP_EXPECT(harness, fake.picked_file_types.size() == 2);
PP_EXPECT(harness, fake.save_file_types.size() == 1);
}
} }
int main() int main()
@@ -127,5 +189,6 @@ int main()
harness.run("platform services preserve empty clipboard writes", platform_services_preserve_empty_clipboard_writes); harness.run("platform services preserve empty clipboard writes", platform_services_preserve_empty_clipboard_writes);
harness.run("platform services dispatch visibility updates", platform_services_dispatch_visibility_updates); harness.run("platform services dispatch visibility updates", platform_services_dispatch_visibility_updates);
harness.run("platform services dispatch file actions", platform_services_dispatch_file_actions); harness.run("platform services dispatch file actions", platform_services_dispatch_file_actions);
harness.run("platform services dispatch picker callbacks", platform_services_dispatch_picker_callbacks);
return harness.finish(); return harness.finish();
} }