From 104358bc62cc5695436fbe8cf55079780d9b1c62 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Thu, 4 Jun 2026 17:05:49 +0200 Subject: [PATCH] Route prepared export policy through platform services --- docs/modernization/build-inventory.md | 3 +- docs/modernization/debt.md | 2 +- docs/modernization/roadmap.md | 5 +- src/app.h | 1 + src/app_dialogs.cpp | 131 +++++++++--------- src/app_events.cpp | 5 + src/platform_api/platform_services.h | 1 + .../legacy_platform_services.cpp | 9 ++ .../windows_platform_services.cpp | 5 + .../platform_api/platform_services_tests.cpp | 14 ++ 10 files changed, 111 insertions(+), 65 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index a4d1393..de806ce 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -546,7 +546,8 @@ Known local toolchain state: flushing, document browse roots, native UI/window state saving, live asset/layout reload policy, diagnostic stacktrace/crash hooks, prepared-file writable target selection, and prepared-file save/download - handoff; + handoff; PPBR and MP4 export dialogs consume the same prepared-file policy + at runtime instead of spelling mobile/Web branches locally; Windows live app execution now uses injected `WindowsPlatformServices` from diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 23cb789..c00e1df 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -70,7 +70,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0050 | Open | Modernization | iOS exported-image photo-library publishing and WebGL persistent-storage flushing now dispatch through `PlatformServices`, but non-Windows execution still lives in `src/platform_legacy/legacy_platform_services.*` and forwards to retained `save_image_library`/`webgl_sync` bridges | Preserve current iOS/Web export and save behavior while the Apple/Web platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; platform package smoke once Apple/Web root builds exist | Exported-image publishing and persistent-storage flushing are owned by injected Apple/Web `pp_platform_*` services with no legacy adapter branch | | DEBT-0051 | Open | Modernization | Document browser search roots now dispatch through `PlatformServices`, but iOS `Inbox` inclusion still lives in `src/platform_legacy/legacy_platform_services.*` | Preserve current iOS document import/browse behavior while Apple platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Apple package smoke once root Apple builds exist | Document browse roots are owned by injected Apple and desktop `pp_platform_*` services with no legacy adapter branch | | DEBT-0052 | Open | Modernization | Native UI/window state saving now dispatches through `PlatformServices`, but macOS execution still lives in `src/platform_legacy/legacy_platform_services.*` and forwards to the retained Objective-C app bridge | Preserve current Windows/macOS UI persistence while platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Windows app build; Apple package smoke once root Apple builds exist | UI/window state persistence is owned by injected platform services with no legacy adapter branch | -| DEBT-0053 | Open | Modernization | Prepared-file writable target selection now dispatches through `PlatformServices`, but iOS/Web target selection still lives in `src/platform_legacy/legacy_platform_services.*` | Preserve mobile/Web export handoff behavior while platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Windows app build; Apple/Web package smoke once root package builds exist | Prepared-file target selection and save/download handoff are owned by injected platform services with no legacy adapter branch | +| DEBT-0053 | Open | Modernization | Prepared-file writable target selection and prepared-file export-dialog policy now dispatch through `PlatformServices`, but iOS/Web target selection still lives in `src/platform_legacy/legacy_platform_services.*` | Preserve mobile/Web export handoff behavior while platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Windows app build; Apple/Web package smoke once root package builds exist | Prepared-file target selection, export-dialog policy, and save/download handoff are owned by injected platform services with no legacy adapter branch | ## Closed Debt diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index b16da38..4c9c970 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -643,6 +643,9 @@ Prepared-file writable target selection now also dispatches through `PlatformServices`, preserving the existing iOS temporary background-write path and Web data-path synchronous write path while removing those platform branches from `App::pick_file_save`. +PPBR and MP4 export dialogs now ask `PlatformServices` whether prepared-file +writes are used, so those dialog flows no longer spell local `__IOS__ || +__WEB__` branches for mobile/Web export handoff. Canvas image export publishing and explicit persistent-storage flushes now dispatch through `PlatformServices` too, preserving iOS photo-library export publication and WebGL filesystem sync behavior in the legacy adapter while @@ -1697,7 +1700,7 @@ Results: recording cleanup dispatch, exported-image publish dispatch, persistent storage flush dispatch, document browse-root dispatch, native UI/window state save dispatch, prepared-file writable target dispatch, - live asset/layout reload policy dispatch, + prepared-file export-dialog policy dispatch, live asset/layout reload policy dispatch, diagnostic hook dispatch, per-frame platform hook dispatch, picker callback dispatch, and prepared-file save/download callback dispatch. The live Windows app now diff --git a/src/app.h b/src/app.h index 353bd69..6558737 100644 --- a/src/app.h +++ b/src/app.h @@ -184,6 +184,7 @@ public: void pick_file_save(const std::string& type, const std::string& default_name, std::function writer, std::function callback); void pick_file_save(std::vector types, std::function callback); + [[nodiscard]] bool uses_prepared_file_writes() const; void pick_dir(std::function callback); void display_file(std::string path); void share_file(std::string path); diff --git a/src/app_dialogs.cpp b/src/app_dialogs.cpp index a12f67d..9f5f1c4 100644 --- a/src/app_dialogs.cpp +++ b/src/app_dialogs.cpp @@ -515,25 +515,29 @@ void App::dialog_ppbr_export() auto dialog = root->add_child_ref(); dialog->btn_ok->on_click = [this, dialog] (Node*) { const auto request = pp::panopainter::make_legacy_brush_package_export_request(*dialog); -#if __IOS__ || __WEB__ - App::I->pick_file_save("ppbr", "exported-brushes", - [this, dialog, request] (std::string path) { - const auto status = pp::panopainter::execute_legacy_brush_package_export( - *this, - *dialog, - request, - path, - pp::panopainter::LegacyBrushPackageExportMode::inline_export_only); - if (!status.ok()) - LOG("PPBR export failed: %s", status.message); - }, - [dialog] (const std::string& path, bool saved) { - (void)path; - pp::panopainter::complete_legacy_brush_package_export(*dialog, saved); - } - ); -#else - App::I->pick_file_save({ "ppbr" }, [this, dialog, request] (std::string path) { + + if (uses_prepared_file_writes()) + { + pick_file_save("ppbr", "exported-brushes", + [this, dialog, request] (std::string path) { + const auto status = pp::panopainter::execute_legacy_brush_package_export( + *this, + *dialog, + request, + path, + pp::panopainter::LegacyBrushPackageExportMode::inline_export_only); + if (!status.ok()) + LOG("PPBR export failed: %s", status.message); + }, + [dialog] (const std::string& path, bool saved) { + (void)path; + pp::panopainter::complete_legacy_brush_package_export(*dialog, saved); + } + ); + return; + } + + pick_file_save({ "ppbr" }, [this, dialog, request] (std::string path) { const auto status = pp::panopainter::execute_legacy_brush_package_export( *this, *dialog, @@ -543,7 +547,6 @@ void App::dialog_ppbr_export() if (!status.ok()) LOG("PPBR export failed: %s", status.message); }); -#endif }; } @@ -552,29 +555,32 @@ void App::dialog_timelapse_export() if (!can_start_document_export(*this, false)) return; -#if __IOS__ || __WEB__ - const auto target = pp::app::make_document_export_suggested_name(doc_name, "-timelapse"); - if (!target) { - message_box("Export Timelapse", target.status().message); + if (uses_prepared_file_writes()) + { + const auto target = pp::app::make_document_export_suggested_name(doc_name, "-timelapse"); + if (!target) { + message_box("Export Timelapse", target.status().message); + return; + } + + pick_file_save("mp4", target.value().name, + [this](std::string path) { + const auto status = pp::panopainter::execute_legacy_document_video_export( + *this, + pp::app::DocumentVideoExportKind::timelapse, + path, + false); + if (!status.ok()) + LOG("Document timelapse export failed: %s", status.message); + }, + [](const std::string& path, bool saved) { + (void)path; + (void)saved; + } + ); return; } - pick_file_save("mp4", target.value().name, - [this](std::string path) { - const auto status = pp::panopainter::execute_legacy_document_video_export( - *this, - pp::app::DocumentVideoExportKind::timelapse, - path, - false); - if (!status.ok()) - LOG("Document timelapse export failed: %s", status.message); - }, - [](const std::string& path, bool saved) { - (void)path; - (void)saved; - } - ); -#else pick_file_save({ "mp4" }, [this](std::string path) { const auto status = pp::panopainter::execute_legacy_document_video_export( *this, @@ -584,7 +590,6 @@ void App::dialog_timelapse_export() if (!status.ok()) LOG("Document timelapse export failed: %s", status.message); }); -#endif } void App::dialog_export_mp4() @@ -592,29 +597,32 @@ void App::dialog_export_mp4() if (!can_start_document_export(*this, false)) return; -#if __IOS__ || __WEB__ - const auto target = pp::app::make_document_export_suggested_name(doc_name, "-animation"); - if (!target) { - message_box("Export Animation", target.status().message); + if (uses_prepared_file_writes()) + { + const auto target = pp::app::make_document_export_suggested_name(doc_name, "-animation"); + if (!target) { + message_box("Export Animation", target.status().message); + return; + } + + pick_file_save("mp4", target.value().name, + [this](std::string path) { + const auto status = pp::panopainter::execute_legacy_document_video_export( + *this, + pp::app::DocumentVideoExportKind::animation_mp4, + path, + false); + if (!status.ok()) + LOG("Document animation export failed: %s", status.message); + }, + [](const std::string& path, bool saved) { + (void)path; + (void)saved; + } + ); return; } - pick_file_save("mp4", target.value().name, - [this](std::string path) { - const auto status = pp::panopainter::execute_legacy_document_video_export( - *this, - pp::app::DocumentVideoExportKind::animation_mp4, - path, - false); - if (!status.ok()) - LOG("Document animation export failed: %s", status.message); - }, - [](const std::string& path, bool saved) { - (void)path; - (void)saved; - } - ); -#else pick_file_save({ "mp4" }, [this](std::string path) { const auto status = pp::panopainter::execute_legacy_document_video_export( *this, @@ -624,7 +632,6 @@ void App::dialog_export_mp4() if (!status.ok()) LOG("Document animation export failed: %s", status.message); }); -#endif } void App::dialog_whatsnew(bool force_show) diff --git a/src/app_events.cpp b/src/app_events.cpp index 6a670e9..1f9f9c0 100644 --- a/src/app_events.cpp +++ b/src/app_events.cpp @@ -181,6 +181,11 @@ void App::pick_file_save(std::vector types, std::function callback) { redraw = true; diff --git a/src/platform_api/platform_services.h b/src/platform_api/platform_services.h index 42090f2..8f78426 100644 --- a/src/platform_api/platform_services.h +++ b/src/platform_api/platform_services.h @@ -63,6 +63,7 @@ public: virtual void pick_file(std::vector file_types, PickedPathCallback callback) = 0; virtual void pick_save_file(std::vector file_types, PickedPathCallback callback) = 0; virtual void pick_directory(PickedPathCallback callback) = 0; + [[nodiscard]] virtual bool uses_prepared_file_writes() = 0; [[nodiscard]] virtual PreparedFileTarget prepare_writable_file( std::string_view type, std::string_view default_name, diff --git a/src/platform_legacy/legacy_platform_services.cpp b/src/platform_legacy/legacy_platform_services.cpp index 8d5863a..09ef93c 100644 --- a/src/platform_legacy/legacy_platform_services.cpp +++ b/src/platform_legacy/legacy_platform_services.cpp @@ -431,6 +431,15 @@ public: #endif } + [[nodiscard]] bool uses_prepared_file_writes() override + { +#if __IOS__ || __WEB__ + return true; +#else + return false; +#endif + } + [[nodiscard]] pp::platform::PreparedFileTarget prepare_writable_file( std::string_view type, std::string_view default_name, diff --git a/src/platform_windows/windows_platform_services.cpp b/src/platform_windows/windows_platform_services.cpp index 2970158..de34475 100644 --- a/src/platform_windows/windows_platform_services.cpp +++ b/src/platform_windows/windows_platform_services.cpp @@ -448,6 +448,11 @@ public: invoke_selected_path(path, callback); } + [[nodiscard]] bool uses_prepared_file_writes() override + { + return false; + } + [[nodiscard]] pp::platform::PreparedFileTarget prepare_writable_file( std::string_view type, std::string_view default_name, diff --git a/tests/platform_api/platform_services_tests.cpp b/tests/platform_api/platform_services_tests.cpp index 0a559fe..2a463d2 100644 --- a/tests/platform_api/platform_services_tests.cpp +++ b/tests/platform_api/platform_services_tests.cpp @@ -210,6 +210,12 @@ public: callback(directory_path); } + [[nodiscard]] bool uses_prepared_file_writes() override + { + ++prepared_file_write_policy_checks; + return prepared_file_writes; + } + [[nodiscard]] pp::platform::PreparedFileTarget prepare_writable_file( std::string_view type, std::string_view default_name, @@ -269,11 +275,13 @@ public: int pick_file_requests = 0; int pick_save_file_requests = 0; int pick_directory_requests = 0; + int prepared_file_write_policy_checks = 0; int prepare_writable_file_requests = 0; int save_prepared_file_requests = 0; bool cursor_visible = false; bool keyboard_visible = false; bool prepared_file_saved = true; + bool prepared_file_writes = true; bool deletes_recorded_files = true; bool live_asset_reloading = true; float last_platform_delta = 0.0f; @@ -571,8 +579,14 @@ void platform_services_dispatch_writable_file_target(pp::tests::Harness& harness FakePlatformServices fake("unused"); pp::platform::PlatformServices& services = fake; + PP_EXPECT(harness, services.uses_prepared_file_writes()); + fake.prepared_file_writes = false; + PP_EXPECT(harness, !services.uses_prepared_file_writes()); + fake.prepared_file_writes = true; + const auto target = services.prepare_writable_file("mp4", "export", "D:/Paint", "D:/Paint/tmp"); + PP_EXPECT(harness, fake.prepared_file_write_policy_checks == 2); PP_EXPECT(harness, fake.prepare_writable_file_requests == 1); PP_EXPECT(harness, fake.writable_file_type == "mp4"); PP_EXPECT(harness, fake.writable_file_default_name == "export");