Route file actions through platform services

This commit is contained in:
2026-06-03 04:03:25 +02:00
parent 4ed72ebc80
commit 1e0500a3f7
7 changed files with 84 additions and 45 deletions

View File

@@ -10,7 +10,7 @@ Keep it updated as platform paths move to shared CMake targets.
| 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 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 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 |
| macOS | `PanoPainter-OSX/` project files and `Info.plist` | Uses `NSOpenGLView` today |
| iOS | `PanoPainter/Info.plist`, related Apple sources | Uses OpenGL ES today |
@@ -445,9 +445,10 @@ Known local toolchain state:
live clipboard get/set requests consume the same contracts before retained
platform clipboard bridges.
- `pp_platform_api` exposes the SDK-free `PlatformServices` interface for
clipboard text, cursor visibility, and virtual-keyboard visibility; live
app execution now reaches retained platform bridges through the
debt-tracked legacy adapter in `app_events.cpp`.
clipboard text, cursor visibility, virtual-keyboard visibility, external
file display, and file sharing; live app execution now reaches retained
platform bridges through the debt-tracked legacy adapter in
`app_events.cpp`.
- `pano_cli plan-cloud-upload` exposes `pp_app_core` cloud upload availability,
new-document warning, publish prompt, and save-before-upload planning as JSON;
the live cloud upload command consumes the same start contract before
@@ -485,8 +486,9 @@ Known local toolchain state:
planning before platform cursor callbacks, plus clipboard read/write
planning before platform clipboard callbacks.
- `pp_platform_api_tests` covers service dispatch for clipboard read/write,
empty clipboard writes, cursor visibility, and virtual-keyboard visibility
without platform SDK headers or a window.
empty clipboard writes, cursor visibility, virtual-keyboard visibility,
external file display, and file sharing without platform SDK headers or a
window.
- `pp_app_core_document_cloud_tests` covers cloud upload no-canvas,
new-document warning, clean publish prompt, and dirty save-before-upload
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 |
| 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 dispatch 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 dispatch tests, picker callback 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 |
| 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 |

View File

@@ -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-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-0017 | Open | Modernization | `App::clipboard_get_text`, `App::clipboard_set_text`, `App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, and `App::hideKeyboard` 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 | 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`, 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 |
## Closed Debt

View File

@@ -469,10 +469,11 @@ cursor bridges continue.
app-core clipboard text decisions used by live clipboard get/set requests
before retained platform clipboard bridges continue.
`pp_platform_api` now owns a headless `PlatformServices` interface for
clipboard text, cursor visibility, and virtual-keyboard visibility. Live app
clipboard/cursor/keyboard execution routes through a debt-tracked legacy
adapter in `app_events.cpp`, so behavior is preserved while later platform
shell implementations can replace direct bridge calls.
clipboard text, cursor visibility, virtual-keyboard visibility, external file
display, and file sharing. Live app clipboard/cursor/keyboard/display/share
execution routes through a debt-tracked legacy adapter in `app_events.cpp`, so
behavior is preserved while later platform shell implementations can replace
direct bridge calls.
`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
prompt, and dirty-document save-before-upload states before legacy UI, canvas,
@@ -985,9 +986,10 @@ Results:
clipboard decisions as JSON, including empty write text.
- `pp_platform_api_tests` passed, covering the SDK-free `PlatformServices`
interface for clipboard read/write, empty clipboard writes, cursor
visibility dispatch, and virtual-keyboard visibility dispatch. The live app
now consumes this interface through the legacy platform adapter for
clipboard/cursor/keyboard execution.
visibility dispatch, virtual-keyboard visibility dispatch, external file
display dispatch, and file sharing dispatch. The live app now consumes this
interface through the legacy platform adapter for
clipboard/cursor/keyboard/display/share execution.
- `panopainter_validate_shaders` passed, validating 25 shader programs and 7
shader includes for stage markers and include graph integrity.
- `pp_renderer_gl_capabilities_tests` passed on default MSVC, vcpkg-headless,

View File

@@ -120,6 +120,36 @@ public:
displayKeyboard(visible);
#else
(void)visible;
#endif
}
void display_file(std::string_view path) override
{
const std::string value(path);
#ifdef __IOS__
dispatch_async(dispatch_get_main_queue(), ^{
[App::I->ios_view display_file:value];
});
#elif __OSX__
[[NSWorkspace sharedWorkspace] openFile:[NSString stringWithUTF8String:value.c_str()]];
#else
(void)value;
#endif
}
void share_file(std::string_view path) override
{
const std::string value(path);
#ifdef __IOS__
dispatch_async(dispatch_get_main_queue(), ^{
[App::I->ios_view share_file:[NSString stringWithUTF8String:value.c_str()]];
});
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
[App::I->osx_view share_file:[NSString stringWithUTF8String:value.c_str()]];
});
#else
(void)value;
#endif
}
};
@@ -398,24 +428,7 @@ void App::display_file(std::string path)
if (pp::app::plan_display_file(path) == pp::app::DisplayFileAction::ignore_empty_path)
return;
#ifdef __IOS__
dispatch_async(dispatch_get_main_queue(), ^{
[ios_view display_file:path];
});
#elif __OSX__
[[NSWorkspace sharedWorkspace] openFile:[NSString stringWithUTF8String:path.c_str()]];
// dispatch_async(dispatch_get_main_queue(), ^{
// std::string path = [osx_view pick_file];
// if (!path.empty())
// callback(path);
// });
#elif __ANDROID__
//displayKeyboard(and_app, false);
#elif _WIN32
// std::string path = win32_open_file();
// if (!path.empty())
// callback(path);
#endif
legacy_platform_services().display_file(path);
}
void App::share_file(std::string path)
@@ -426,18 +439,7 @@ void App::share_file(std::string path)
message_box("Sharing failed", "Please save the document before sharing it.");
return;
}
#ifdef __IOS__
dispatch_async(dispatch_get_main_queue(), ^{
[ios_view share_file:[NSString stringWithUTF8String:path.c_str()]];
});
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
[osx_view share_file:[NSString stringWithUTF8String:path.c_str()]];
});
#elif __ANDROID__
#elif _WIN32
// not implemented
#endif
legacy_platform_services().share_file(path);
}
bool App::mouse_down(int button, float x, float y, float pressure, kEventSource source, bool eraser)

View File

@@ -13,6 +13,8 @@ public:
[[nodiscard]] virtual bool set_clipboard_text(std::string_view text) = 0;
virtual void set_cursor_visible(bool visible) = 0;
virtual void set_virtual_keyboard_visible(bool visible) = 0;
virtual void display_file(std::string_view path) = 0;
virtual void share_file(std::string_view path) = 0;
};
}

View File

@@ -39,12 +39,28 @@ public:
keyboard_visible = visible;
}
void display_file(std::string_view path) override
{
++display_file_requests;
displayed_path.assign(path);
}
void share_file(std::string_view path) override
{
++share_file_requests;
shared_path.assign(path);
}
int clipboard_reads = 0;
int clipboard_writes = 0;
int cursor_updates = 0;
int keyboard_updates = 0;
int display_file_requests = 0;
int share_file_requests = 0;
bool cursor_visible = false;
bool keyboard_visible = false;
std::string displayed_path;
std::string shared_path;
private:
std::string clipboard_value_;
@@ -88,6 +104,20 @@ void platform_services_dispatch_visibility_updates(pp::tests::Harness& harness)
PP_EXPECT(harness, !fake.keyboard_visible);
}
void platform_services_dispatch_file_actions(pp::tests::Harness& harness)
{
FakePlatformServices fake("unused");
pp::platform::PlatformServices& services = fake;
services.display_file("D:/Paint/export.png");
services.share_file("D:/Paint/demo.ppi");
PP_EXPECT(harness, fake.display_file_requests == 1);
PP_EXPECT(harness, fake.share_file_requests == 1);
PP_EXPECT(harness, fake.displayed_path == "D:/Paint/export.png");
PP_EXPECT(harness, fake.shared_path == "D:/Paint/demo.ppi");
}
}
int main()
@@ -96,5 +126,6 @@ int main()
harness.run("platform services dispatch clipboard reads and writes", platform_services_dispatch_clipboard_reads_and_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 file actions", platform_services_dispatch_file_actions);
return harness.finish();
}