From 5d5bb247117b2578c5678d28b7b40ee55ae1320b Mon Sep 17 00:00:00 2001 From: omigamedev Date: Wed, 3 Jun 2026 10:03:34 +0200 Subject: [PATCH] Extract document resize planning --- CMakeLists.txt | 1 + docs/modernization/debt.md | 1 + docs/modernization/roadmap.md | 9 ++++ src/app_core/document_resize.h | 57 ++++++++++++++++++++ src/app_dialogs.cpp | 11 +++- src/node_dialog_resize.cpp | 14 +++-- tests/CMakeLists.txt | 22 ++++++++ tests/app_core/document_resize_tests.cpp | 50 +++++++++++++++++ tools/pano_cli/main.cpp | 68 ++++++++++++++++++++++++ 9 files changed, 226 insertions(+), 7 deletions(-) create mode 100644 src/app_core/document_resize.h create mode 100644 tests/app_core/document_resize_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e505ef2..5059ea8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,6 +229,7 @@ add_library(pp_app_core STATIC src/app_core/document_export.cpp src/app_core/document_platform_io.h src/app_core/document_recording.h + src/app_core/document_resize.h src/app_core/document_route.cpp src/app_core/document_sharing.h src/app_core/document_session.cpp) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index f082fa7..30d1ca9 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -37,6 +37,7 @@ agent or engineer to remove them without reconstructing context from chat. | 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`, and Windows live execution uses injected `WindowsPlatformServices`, but Apple/Android clipboard execution still reaches retained fallback adapter branches 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 injected `pp_platform_*` services for every supported platform | | DEBT-0017 | Open | Modernization | Startup storage path preparation, `App::clipboard_get_text`, `App::clipboard_set_text`, `App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, `App::hideKeyboard`, `App::display_file`, `App::share_file`, native app/window close, UI-thread lifecycle hooks, render-context acquire/release/present hooks, render-target binding hooks, render platform hint hooks, render debug callback hooks, render-capture frame hooks, recording cleanup, live asset/layout reload policy, diagnostic stacktrace/crash hooks, per-frame platform hooks, `App::pick_image`, `App::pick_file`, the non-writer `App::pick_file_save`, `App::pick_dir`, and prepared-file save/download handoff now call the SDK-free `pp::platform::PlatformServices` interface, and Windows injects `WindowsPlatformServices` from `src/platform_windows/windows_platform_services.*`; non-Windows live implementations still use `src/platform_legacy/legacy_platform_services.*`, a named fallback adapter that forwards to retained Apple/Android/Linux/Web 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 `src/platform_legacy/legacy_platform_services.*` with injected `pp_platform_*` service implementations owned by each non-Windows platform shell | | DEBT-0019 | Open | Modernization | MSVC warning C4100 is muted globally through `pp_project_warnings` with `/wd4100` | Legacy callbacks, virtual hooks, serializer methods, and platform/API compatibility functions carry many intentionally unused parameters during the component split; muting this keeps stricter warning builds focused on higher-signal migration issues | `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter`; `ctest --preset desktop-fast --build-config Debug` | Remove `/wd4100`, mark intentionally unused parameters with names/comments or `[[maybe_unused]]`, and make the Windows app and headless tests pass without C4100 warnings | +| DEBT-0020 | Open | Modernization | Document resize dialog state and selected-resolution planning now consume pure `pp_app_core` through `NodeDialogResize`, `App::dialog_resize`, and `pano_cli plan-document-resize`, but live resize execution still calls legacy `Canvas::resize` and clears legacy `ActionManager` history directly | Preserve existing layer/frame GPU resize behavior while the document model and canvas execution boundary are extracted incrementally | `pp_app_core_document_resize_tests`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `ctest --preset desktop-fast --build-config Debug` | Document resize execution is owned by a document/app boundary with legacy `Canvas` acting only as an adapter or removed entirely | ## Closed Debt diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 7e74b00..1d10f49a 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -478,6 +478,9 @@ cursor bridges continue. `pano_cli plan-clipboard-read` and `pano_cli plan-clipboard-write` expose the app-core clipboard text decisions used by live clipboard get/set requests before retained platform clipboard bridges continue. +`pano_cli plan-document-resize` exposes the app-core resize dialog state and +selected-resolution commit plan used by the live document resize dialog before +legacy `Canvas` resize execution and `ActionManager` history clearing continue. `pp_platform_api` now owns a headless `PlatformServices` interface for startup storage path preparation, clipboard text, cursor visibility, virtual-keyboard visibility, UI-thread lifecycle hooks, render-context @@ -1078,6 +1081,12 @@ Results: `pano_cli_plan_recording_session_running_smoke`, and `pano_cli_plan_recording_session_platform_cleanup_smoke` passed and expose app-core recording lifecycle/export decisions as JSON. +- `pp_app_core_document_resize_tests` passed, covering resize dialog state, + unknown current-resolution labeling, selected-resolution mapping, square + canvas sizing, history-clearing intent, and invalid selection rejection. +- `pano_cli_plan_document_resize_smoke` and + `pano_cli_plan_document_resize_rejects_invalid_selection` passed and expose + live document-resize planning as JSON automation. - `pp_app_core_document_sharing_tests` passed, covering saved-path gating before platform share execution. - `pano_cli_plan_share_file_unsaved_smoke` and diff --git a/src/app_core/document_resize.h b/src/app_core/document_resize.h new file mode 100644 index 0000000..d0d10c4 --- /dev/null +++ b/src/app_core/document_resize.h @@ -0,0 +1,57 @@ +#pragma once + +#include "app_core/app_status.h" +#include "foundation/result.h" + +#include +#include + +namespace pp::app { + +struct DocumentResizeDialogState { + int current_resolution = 0; + std::string current_resolution_text; + int current_resolution_index = 0; +}; + +struct DocumentResizePlan { + int resolution = 0; + int width = 0; + int height = 0; + bool clears_history = false; +}; + +[[nodiscard]] inline DocumentResizeDialogState make_document_resize_dialog_state( + int current_resolution) +{ + const auto label = document_resolution_label(current_resolution); + const auto index = document_resolution_to_index(current_resolution); + std::string text = "Current: "; + text.append(label ? std::string_view(label.value()) : std::string_view("unknown")); + + return { + current_resolution, + text, + index ? static_cast(index.value()) : static_cast(document_resolution_values.size()), + }; +} + +[[nodiscard]] inline pp::foundation::Result plan_document_resize( + int selected_resolution_index) +{ + const auto resolution = display_resolution_from_index(selected_resolution_index); + if (!resolution) { + return pp::foundation::Result::failure(resolution.status()); + } + + const auto value = resolution.value(); + return pp::foundation::Result::success( + DocumentResizePlan { + value, + value, + value, + true, + }); +} + +} diff --git a/src/app_dialogs.cpp b/src/app_dialogs.cpp index 7a14de6..635f4ea 100644 --- a/src/app_dialogs.cpp +++ b/src/app_dialogs.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "app.h" #include "action.h" +#include "app_core/document_resize.h" #include "app_core/document_export.h" #include "app_core/document_session.h" #include "settings.h" @@ -558,9 +559,15 @@ void App::dialog_resize() dialog->btn_ok->on_click = [this,dialog](Node*) { - int res = dialog->get_resolution(); + const auto plan = pp::app::plan_document_resize( + dialog->combo ? dialog->combo->m_current_index : 0); + if (!plan) + { + dialog->destroy(); + return; + } if (canvas) - canvas->m_canvas->resize(res, res); + canvas->m_canvas->resize(plan.value().width, plan.value().height); App::I->title_update(); ActionManager::clear(); dialog->destroy(); diff --git a/src/node_dialog_resize.cpp b/src/node_dialog_resize.cpp index 8791fd2..28cea33 100644 --- a/src/node_dialog_resize.cpp +++ b/src/node_dialog_resize.cpp @@ -1,9 +1,9 @@ #include "pch.h" #include "log.h" #include "node_dialog_resize.h" +#include "app_core/document_resize.h" #include "canvas.h" #include "node_image_texture.h" -#include "app.h" #include Node* NodeDialogResize::clone_instantiate() const @@ -30,9 +30,12 @@ void NodeDialogResize::init_controls() combo = find("resolution"); text = find("current-res"); resolution = Canvas::I->m_width; - static char txt[128]; - sprintf(txt, "Current: %s", App::I->res_to_string(resolution).c_str()); - text->set_text(txt); + const auto state = pp::app::make_document_resize_dialog_state(resolution); + text->set_text(state.current_resolution_text.c_str()); + if (combo && state.current_resolution_index >= 0 + && state.current_resolution_index < static_cast(combo->m_items.size())) { + combo->m_current_index = state.current_resolution_index; + } btn_cancel->on_click = [this](Node*) { destroy(); }; @@ -47,5 +50,6 @@ void NodeDialogResize::loaded() int NodeDialogResize::get_resolution() { - return combo ? App::I->res_from_index(combo->m_current_index) : 512; + const auto plan = pp::app::plan_document_resize(combo ? combo->m_current_index : 0); + return plan ? plan.value().resolution : pp::app::document_resolution_values.front(); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a07c9b5..50f1817 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -318,6 +318,16 @@ add_test(NAME pp_app_core_document_recording_tests COMMAND pp_app_core_document_ set_tests_properties(pp_app_core_document_recording_tests PROPERTIES LABELS "app;desktop-fast;fuzz") +add_executable(pp_app_core_document_resize_tests + app_core/document_resize_tests.cpp) +target_link_libraries(pp_app_core_document_resize_tests PRIVATE + pp_app_core + pp_test_harness) + +add_test(NAME pp_app_core_document_resize_tests COMMAND pp_app_core_document_resize_tests) +set_tests_properties(pp_app_core_document_resize_tests PROPERTIES + LABELS "app;desktop-fast;fuzz") + add_executable(pp_app_core_app_preferences_tests app_core/app_preferences_tests.cpp) target_link_libraries(pp_app_core_app_preferences_tests PRIVATE @@ -668,6 +678,18 @@ if(TARGET pano_cli) LABELS "app;integration;desktop-fast;fuzz" PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-status\".*\"title\":\"Panodoc: demo \\(unknown\\)\".*\"recording\":\\{\"visible\":false,\"text\":\"\"\\}.*\"fromIndexValid\":false.*\"toIndexValid\":false.*\"labelValid\":false.*\"label\":\"\"") + add_test(NAME pano_cli_plan_document_resize_smoke + COMMAND pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4) + set_tests_properties(pano_cli_plan_document_resize_smoke PROPERTIES + LABELS "app;integration;desktop-fast" + PASS_REGULAR_EXPRESSION "\"command\":\"plan-document-resize\".*\"currentResolutionText\":\"Current: 8K\".*\"currentResolutionIndex\":3.*\"selectedResolutionIndex\":4.*\"resolution\":4096.*\"width\":4096.*\"height\":4096.*\"clearsHistory\":true") + + add_test(NAME pano_cli_plan_document_resize_rejects_invalid_selection + COMMAND pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 9) + set_tests_properties(pano_cli_plan_document_resize_rejects_invalid_selection PROPERTIES + LABELS "app;integration;desktop-fast;fuzz" + WILL_FAIL TRUE) + add_test(NAME pano_cli_plan_share_file_unsaved_smoke COMMAND pano_cli plan-share-file) set_tests_properties(pano_cli_plan_share_file_unsaved_smoke PROPERTIES diff --git a/tests/app_core/document_resize_tests.cpp b/tests/app_core/document_resize_tests.cpp new file mode 100644 index 0000000..2bae75d --- /dev/null +++ b/tests/app_core/document_resize_tests.cpp @@ -0,0 +1,50 @@ +#include "app_core/document_resize.h" +#include "test_harness.h" + +namespace { + +void dialog_state_labels_current_resolution(pp::tests::Harness& harness) +{ + const auto state = pp::app::make_document_resize_dialog_state(2048); + PP_EXPECT(harness, state.current_resolution == 2048); + PP_EXPECT(harness, state.current_resolution_text == "Current: 8K"); + PP_EXPECT(harness, state.current_resolution_index == 3); +} + +void dialog_state_survives_unknown_resolution(pp::tests::Harness& harness) +{ + const auto state = pp::app::make_document_resize_dialog_state(1234); + PP_EXPECT(harness, state.current_resolution == 1234); + PP_EXPECT(harness, state.current_resolution_text == "Current: unknown"); + PP_EXPECT(harness, state.current_resolution_index == 6); +} + +void resize_plan_maps_selection_to_square_canvas(pp::tests::Harness& harness) +{ + const auto plan = pp::app::plan_document_resize(4); + PP_EXPECT(harness, plan); + if (plan) { + PP_EXPECT(harness, plan.value().resolution == 4096); + PP_EXPECT(harness, plan.value().width == 4096); + PP_EXPECT(harness, plan.value().height == 4096); + PP_EXPECT(harness, plan.value().clears_history); + } +} + +void resize_plan_rejects_invalid_selection(pp::tests::Harness& harness) +{ + PP_EXPECT(harness, !pp::app::plan_document_resize(-1)); + PP_EXPECT(harness, !pp::app::plan_document_resize(6)); +} + +} + +int main() +{ + pp::tests::Harness harness; + harness.run("dialog state labels current resolution", dialog_state_labels_current_resolution); + harness.run("dialog state survives unknown resolution", dialog_state_survives_unknown_resolution); + harness.run("resize plan maps selection to square canvas", resize_plan_maps_selection_to_square_canvas); + harness.run("resize plan rejects invalid selection", resize_plan_rejects_invalid_selection); + return harness.finish(); +} diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index 7e3d214..caf5254 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -4,6 +4,7 @@ #include "app_core/document_cloud.h" #include "app_core/document_platform_io.h" #include "app_core/document_recording.h" +#include "app_core/document_resize.h" #include "app_core/document_route.h" #include "app_core/document_sharing.h" #include "app_core/document_session.h" @@ -213,6 +214,11 @@ struct PlanAppStatusArgs { std::uint32_t encoded_frames = 0; }; +struct PlanDocumentResizeArgs { + int current_resolution = 512; + int selected_resolution_index = 0; +}; + struct SimulateAppSessionArgs { bool has_canvas = true; bool new_document = false; @@ -682,6 +688,7 @@ void print_help() << " plan-recording-session [--running] [--frame-count N] [--platform-deletes-recorded-files]\n" << " plan-app-preferences [--ui-scale N] [--display-density N] [--current-scale N] [--scale-option N] [--viewport-scale N] [--rtl] [--timelapse-disabled] [--recording-running] [--vr-controllers-disabled] [--cursor-mode N]\n" << " plan-app-status [--doc-name NAME] [--unsaved] [--resolution N] [--resolution-index N] [--zoom N] [--history-bytes N] [--recording-running] [--encoder-available] [--encoded-frames N]\n" + << " plan-document-resize [--current-resolution N] [--selected-resolution-index N]\n" << " plan-share-file [--path FILE]\n" << " plan-picked-path [--path FILE]\n" << " plan-display-file [--path FILE]\n" @@ -2219,6 +2226,63 @@ int plan_app_status(int argc, char** argv) return 0; } +pp::foundation::Status parse_plan_document_resize_args( + int argc, + char** argv, + PlanDocumentResizeArgs& args) +{ + for (int i = 2; i < argc; ++i) { + const std::string_view key(argv[i]); + if (key == "--current-resolution" || key == "--selected-resolution-index") { + if (i + 1 >= argc) { + return pp::foundation::Status::invalid_argument("missing value for option"); + } + const auto value = pp::foundation::parse_u32(argv[++i]); + if (!value) { + return value.status(); + } + if (key == "--current-resolution") { + args.current_resolution = static_cast(value.value()); + } else { + args.selected_resolution_index = static_cast(value.value()); + } + } else { + return pp::foundation::Status::invalid_argument("unknown option"); + } + } + + return pp::foundation::Status::success(); +} + +int plan_document_resize(int argc, char** argv) +{ + PlanDocumentResizeArgs args; + const auto status = parse_plan_document_resize_args(argc, argv, args); + if (!status.ok()) { + print_error("plan-document-resize", status.message); + return 2; + } + + const auto state = pp::app::make_document_resize_dialog_state(args.current_resolution); + const auto plan = pp::app::plan_document_resize(args.selected_resolution_index); + if (!plan) { + print_error("plan-document-resize", plan.status().message); + return 2; + } + + std::cout << "{\"ok\":true,\"command\":\"plan-document-resize\"" + << ",\"state\":{\"currentResolution\":" << state.current_resolution + << ",\"currentResolutionText\":\"" << json_escape(state.current_resolution_text) + << "\",\"currentResolutionIndex\":" << state.current_resolution_index + << ",\"selectedResolutionIndex\":" << args.selected_resolution_index + << "},\"plan\":{\"resolution\":" << plan.value().resolution + << ",\"width\":" << plan.value().width + << ",\"height\":" << plan.value().height + << ",\"clearsHistory\":" << json_bool(plan.value().clears_history) + << "}}\n"; + return 0; +} + pp::foundation::Status parse_plan_share_file_args( int argc, char** argv, @@ -4619,6 +4683,10 @@ int main(int argc, char** argv) return plan_app_status(argc, argv); } + if (command == "plan-document-resize") { + return plan_document_resize(argc, argv); + } + if (command == "plan-share-file") { return plan_share_file(argc, argv); }