Centralize legacy app preferences

This commit is contained in:
2026-06-04 14:18:18 +02:00
parent ca5b94b044
commit f8243566c4
9 changed files with 352 additions and 22 deletions

View File

@@ -82,6 +82,8 @@ set(PP_PANOPAINTER_APP_SOURCES
src/app_layout.cpp src/app_layout.cpp
src/app_shaders.cpp src/app_shaders.cpp
src/app_vr.cpp src/app_vr.cpp
src/legacy_app_preference_services.cpp
src/legacy_app_preference_services.h
src/legacy_cloud_services.cpp src/legacy_cloud_services.cpp
src/legacy_cloud_services.h src/legacy_cloud_services.h
src/legacy_document_export_services.cpp src/legacy_document_export_services.cpp

View File

@@ -167,6 +167,12 @@ Known local toolchain state:
Tools, About, history, canvas-clear, and settings execution remain tracked by Tools, About, history, canvas-clear, and settings execution remain tracked by
`DEBT-0029`, `DEBT-0030`, `DEBT-0031`, `DEBT-0033`, `DEBT-0034`, and `DEBT-0029`, `DEBT-0030`, `DEBT-0031`, `DEBT-0033`, `DEBT-0034`, and
`DEBT-0035`. `DEBT-0035`.
- `src/legacy_app_preference_services.*` is the current app-shell bridge for
options-menu preference execution. It keeps UI scale, viewport scale, RTL,
VR-controller, auto-timelapse, and canvas cursor-mode callbacks on the
`pp_app_core` `AppPreferenceServices` contract while retained settings
persistence, recording lifecycle, and legacy canvas/UI execution remain
tracked by `DEBT-0045`.
- `src/legacy_canvas_tool_services.*` is the current app-shell bridge for - `src/legacy_canvas_tool_services.*` is the current app-shell bridge for
canvas toolbar tool selection, NodeCanvas stylus/input mode switching, and canvas toolbar tool selection, NodeCanvas stylus/input mode switching, and
canvas hotkey/touch execution. It keeps those live paths on the `pp_app_core` canvas hotkey/touch execution. It keeps those live paths on the `pp_app_core`
@@ -576,6 +582,10 @@ Known local toolchain state:
show/hide planning before platform keyboard callbacks, plus cursor visibility show/hide planning before platform keyboard callbacks, plus cursor visibility
planning before platform cursor callbacks, plus clipboard read/write planning before platform cursor callbacks, plus clipboard read/write
planning before platform clipboard callbacks. planning before platform clipboard callbacks.
- `pp_app_core_app_preferences_tests` covers UI scale/font-scale planning,
scale-option selection, viewport scale planning, RTL direction planning,
timelapse start/stop/no-op decisions, simple stored preferences, and
`AppPreferenceServices` execution dispatch for options-menu side effects.
- `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, file sharing, and picker callbacks without platform external file display, file sharing, and picker callbacks without platform

View File

@@ -62,6 +62,7 @@ agent or engineer to remove them without reconstructing context from chat.
| DEBT-0042 | Open | Modernization | Accepted Save As and Save Version planning/execution dispatch now consumes pure `pp_app_core` through `App::dialog_save`, `App::dialog_save_ver`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `DocumentFileSaveServices`, `DocumentVersionSaveServices`, and `src/legacy_document_session_services.*`, but the bridge still opens legacy overwrite prompts, calls legacy `Canvas::project_save`, mutates app document name/path/directory fields, marks version saves dirty before saving, updates the title, and handles keyboard/dialog cleanup directly | Preserve current Save As and Save Version behavior while document persistence moves toward app/document/storage/UI services | `pp_app_core_document_session_tests`; `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 simulate-app-session --save-intent save-as`; `pano_cli simulate-app-session --save-intent save-version`; `ctest --preset desktop-fast --build-config Debug` | Save As overwrite prompting, project-save execution, app document metadata updates, title updates, version-save dirty-state handling, and keyboard/dialog cleanup are owned by injected app/document/storage/UI services with `App::dialog_save` and `App::dialog_save_ver` acting only as UI adapters | | DEBT-0042 | Open | Modernization | Accepted Save As and Save Version planning/execution dispatch now consumes pure `pp_app_core` through `App::dialog_save`, `App::dialog_save_ver`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `DocumentFileSaveServices`, `DocumentVersionSaveServices`, and `src/legacy_document_session_services.*`, but the bridge still opens legacy overwrite prompts, calls legacy `Canvas::project_save`, mutates app document name/path/directory fields, marks version saves dirty before saving, updates the title, and handles keyboard/dialog cleanup directly | Preserve current Save As and Save Version behavior while document persistence moves toward app/document/storage/UI services | `pp_app_core_document_session_tests`; `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 simulate-app-session --save-intent save-as`; `pano_cli simulate-app-session --save-intent save-version`; `ctest --preset desktop-fast --build-config Debug` | Save As overwrite prompting, project-save execution, app document metadata updates, title updates, version-save dirty-state handling, and keyboard/dialog cleanup are owned by injected app/document/storage/UI services with `App::dialog_save` and `App::dialog_save_ver` acting only as UI adapters |
| DEBT-0043 | Open | Modernization | Equirectangular, layer, animation-frame, depth, and cube-face export planning/execution dispatch now consumes pure `pp_app_core` through `App::dialog_export`, `App::dialog_export_layers`, `App::dialog_export_anim_frames`, `App::dialog_export_depth`, `App::dialog_export_cube_faces`, `pano_cli plan-export-*`, `DocumentExportServices`, and `src/legacy_document_export_services.*`, but the bridge still calls legacy `Canvas` export methods, owns platform-specific export success messages, creates export directories, handles picker-selected stems, and performs Web prepared-file handoff directly | Preserve current image/collection/depth/cube export behavior while export execution moves toward document/renderer/platform/storage services | `pp_app_core_document_export_tests`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind layers`; `pano_cli plan-export-target --kind collection --work-dir D:/Paint --doc-name demo --suffix _layers`; `pano_cli simulate-document-export`; `ctest --preset desktop-fast --build-config Debug` | File, collection, stem, depth, and cube export execution, export-directory creation, platform success reporting, Web file handoff, and legacy canvas export calls are owned by injected document/renderer/platform/storage services with export dialogs acting only as UI adapters | | DEBT-0043 | Open | Modernization | Equirectangular, layer, animation-frame, depth, and cube-face export planning/execution dispatch now consumes pure `pp_app_core` through `App::dialog_export`, `App::dialog_export_layers`, `App::dialog_export_anim_frames`, `App::dialog_export_depth`, `App::dialog_export_cube_faces`, `pano_cli plan-export-*`, `DocumentExportServices`, and `src/legacy_document_export_services.*`, but the bridge still calls legacy `Canvas` export methods, owns platform-specific export success messages, creates export directories, handles picker-selected stems, and performs Web prepared-file handoff directly | Preserve current image/collection/depth/cube export behavior while export execution moves toward document/renderer/platform/storage services | `pp_app_core_document_export_tests`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind layers`; `pano_cli plan-export-target --kind collection --work-dir D:/Paint --doc-name demo --suffix _layers`; `pano_cli simulate-document-export`; `ctest --preset desktop-fast --build-config Debug` | File, collection, stem, depth, and cube export execution, export-directory creation, platform success reporting, Web file handoff, and legacy canvas export calls are owned by injected document/renderer/platform/storage services with export dialogs acting only as UI adapters |
| DEBT-0044 | Open | Modernization | Timelapse and animation MP4 export execution dispatch now consumes pure `pp_app_core` through `App::dialog_timelapse_export`, `App::dialog_export_mp4`, `pano_cli plan-export-menu`, `pano_cli plan-export-target --kind name`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, but the bridge still launches legacy desktop timelapse worker threads, calls `App::rec_export`, calls `Canvas::export_anim_mp4`, owns mobile/Web save callbacks, and emits success messages directly | Preserve current MP4/timelapse export behavior while video export moves toward app/document/renderer/video/platform/storage services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind animation-mp4`; `pano_cli plan-export-menu --kind timelapse`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -animation`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -timelapse`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, mobile/Web save callbacks, and success reporting are owned by injected app/document/renderer/video/platform/storage services with export dialogs acting only as UI adapters | | DEBT-0044 | Open | Modernization | Timelapse and animation MP4 export execution dispatch now consumes pure `pp_app_core` through `App::dialog_timelapse_export`, `App::dialog_export_mp4`, `pano_cli plan-export-menu`, `pano_cli plan-export-target --kind name`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, but the bridge still launches legacy desktop timelapse worker threads, calls `App::rec_export`, calls `Canvas::export_anim_mp4`, owns mobile/Web save callbacks, and emits success messages directly | Preserve current MP4/timelapse export behavior while video export moves toward app/document/renderer/video/platform/storage services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind animation-mp4`; `pano_cli plan-export-menu --kind timelapse`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -animation`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -timelapse`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, mobile/Web save callbacks, and success reporting are owned by injected app/document/renderer/video/platform/storage services with export dialogs acting only as UI adapters |
| DEBT-0045 | Open | Modernization | Options-menu preference execution now consumes pure `pp_app_core` through UI scale, viewport scale, RTL direction, VR-controller, auto-timelapse, and canvas cursor-mode callbacks plus `AppPreferenceServices` and `src/legacy_app_preference_services.*`, but the bridge still calls legacy `App::set_ui_scale`, `App::set_ui_rtl`, `NodeCanvas::set_density`, `NodeCanvas::set_cursor_visibility`, `App::rec_start`, `App::rec_stop`, and `Settings::save` directly | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_tests`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Preference persistence, UI/layout direction, viewport density, cursor mode, VR-controller state, and auto-timelapse recording side effects are owned by injected app/UI/platform/storage services with options-menu callbacks acting only as UI adapters |
## Closed Debt ## Closed Debt

View File

@@ -179,7 +179,10 @@ contracts while legacy canvas/project loading remains in place.
scale option selection, viewport scale, RTL layout direction, timelapse scale option selection, viewport scale, RTL layout direction, timelapse
recording toggles, VR controller enablement, and canvas cursor mode; recording toggles, VR controller enablement, and canvas cursor mode;
the live tools/options menu and `pano_cli plan-app-preferences` consume those the live tools/options menu and `pano_cli plan-app-preferences` consume those
contracts while legacy widgets and settings persistence execute them. contracts. Options-menu preference execution now dispatches through
`AppPreferenceServices` and `src/legacy_app_preference_services.*` before
legacy widgets, settings persistence, recording toggles, and canvas cursor
updates continue.
It also owns tested app status/display plans for document title text, It also owns tested app status/display plans for document title text,
resolution mapping/labels, DPI text, history-memory text, and recording-frame resolution mapping/labels, DPI text, history-memory text, and recording-frame
status text, plus renderer diagnostic indicator labels for framebuffer fetch status text, plus renderer diagnostic indicator labels for framebuffer fetch
@@ -602,6 +605,11 @@ dispatch through `ToolsMenuServices` in the shared app-shell bridge before the
legacy UI/panel/canvas/platform adapters continue execution. The live animation legacy UI/panel/canvas/platform adapters continue execution. The live animation
panel route now also checks animation panel visibility and applies animation panel route now also checks animation panel visibility and applies animation
panel layout state instead of using the grid panel by mistake. panel layout state instead of using the grid panel by mistake.
Options-menu preference callbacks now dispatch UI scale, viewport scale, RTL,
VR-controller, auto-timelapse, and cursor-mode side effects through
`AppPreferenceServices` in `src/legacy_app_preference_services.*` before
retained settings writes, recording lifecycle calls, and legacy canvas/UI
adapters continue.
`pano_cli plan-about-menu` exposes app-core planning for About menu help, `pano_cli plan-about-menu` exposes app-core planning for About menu help,
about, what's-new, crash-test, and performance-test commands, including about, what's-new, crash-test, and performance-test commands, including
versioned what's-new labels, diagnostic gating, and no-canvas performance-test versioned what's-new labels, diagnostic gating, and no-canvas performance-test
@@ -1345,6 +1353,11 @@ Results:
`pp_app_core_document_export_tests`, `pano_cli_plan_export_menu_*`, `pp_app_core_document_export_tests`, `pano_cli_plan_export_menu_*`,
`pano_cli_plan_export_target_name_smoke`, and `pano_cli_plan_export_target_name_smoke`, and
`pano_cli_simulate_document_export_smoke`. `pano_cli_simulate_document_export_smoke`.
- `PanoPainter`, `pp_app_core_app_preferences_tests`, and `pano_cli` built
after options-menu preference execution moved behind app preference services.
- Focused preference CTest coverage passed for
`pp_app_core_app_preferences_tests` and the app-preferences CLI smoke tests
after the live bridge split.
- `pp_app_core_document_recording_tests` passed, covering recording start/stop, - `pp_app_core_document_recording_tests` passed, covering recording start/stop,
clear, platform recorded-file cleanup, frame-count reset, export progress clear, platform recorded-file cleanup, frame-count reset, export progress
totals, and oversized progress-total clamping. totals, and oversized progress-total clamping.

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include "foundation/result.h"
#include <cstddef> #include <cstddef>
#include <span> #include <span>
@@ -44,6 +46,18 @@ struct StoredBooleanPreferencePlan {
bool value = false; bool value = false;
}; };
class AppPreferenceServices {
public:
virtual ~AppPreferenceServices() = default;
virtual void apply_ui_scale(const ScaleApplicationPlan& plan) = 0;
virtual void apply_viewport_scale(const ScaleApplicationPlan& plan) = 0;
virtual void apply_interface_direction(const InterfaceDirectionPlan& plan) = 0;
virtual void apply_vr_controllers_preference(const StoredBooleanPreferencePlan& plan) = 0;
virtual void apply_timelapse_preference(const TimelapsePreferencePlan& plan) = 0;
virtual void apply_canvas_cursor_mode(const StoredIntegerPreferencePlan& plan) = 0;
};
[[nodiscard]] constexpr ScaleApplicationPlan plan_ui_scale( [[nodiscard]] constexpr ScaleApplicationPlan plan_ui_scale(
float requested_scale, float requested_scale,
float display_density) noexcept float display_density) noexcept
@@ -111,4 +125,55 @@ struct StoredBooleanPreferencePlan {
return { mode }; return { mode };
} }
[[nodiscard]] inline pp::foundation::Status execute_ui_scale_preference(
float requested_scale,
float display_density,
AppPreferenceServices& services)
{
services.apply_ui_scale(plan_ui_scale(requested_scale, display_density));
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_viewport_scale_preference(
float requested_scale,
float display_density,
AppPreferenceServices& services)
{
services.apply_viewport_scale(plan_viewport_scale(requested_scale, display_density));
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_interface_direction_preference(
bool right_to_left,
AppPreferenceServices& services)
{
services.apply_interface_direction(plan_interface_direction(right_to_left));
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_vr_controllers_preference(
bool enabled,
AppPreferenceServices& services)
{
services.apply_vr_controllers_preference(plan_vr_controllers_preference(enabled));
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_timelapse_preference(
bool enabled,
bool recording_running,
AppPreferenceServices& services)
{
services.apply_timelapse_preference(plan_timelapse_preference(enabled, recording_running));
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_canvas_cursor_mode_preference(
int mode,
AppPreferenceServices& services)
{
services.apply_canvas_cursor_mode(plan_canvas_cursor_mode(mode));
return pp::foundation::Status::success();
}
} }

View File

@@ -17,6 +17,7 @@
#include "app_core/app_status.h" #include "app_core/app_status.h"
#include "app_core/main_toolbar.h" #include "app_core/main_toolbar.h"
#include "app_core/tools_menu.h" #include "app_core/tools_menu.h"
#include "legacy_app_preference_services.h"
#include "legacy_app_shell_services.h" #include "legacy_app_shell_services.h"
#include "legacy_brush_ui_services.h" #include "legacy_brush_ui_services.h"
#include "legacy_canvas_tool_services.h" #include "legacy_canvas_tool_services.h"
@@ -1095,7 +1096,11 @@ void App::init_menu_tools()
ui_scale->on_select = [ui_scale](Node* target, int index) ui_scale->on_select = [ui_scale](Node* target, int index)
{ {
App::I->set_ui_scale(ui_scale->get_float(index)); const auto status = pp::panopainter::execute_legacy_ui_scale_preference(
*App::I,
ui_scale->get_float(index));
if (!status.ok())
LOG("UI scale preference failed: %s", status.message);
}; };
} }
@@ -1112,10 +1117,11 @@ void App::init_menu_tools()
vp_scale->on_select = [vp_scale](Node* target, int index) vp_scale->on_select = [vp_scale](Node* target, int index)
{ {
const auto plan = pp::app::plan_viewport_scale(vp_scale->get_float(index)); const auto status = pp::panopainter::execute_legacy_viewport_scale_preference(
App::I->canvas->set_density(plan.scale); *App::I,
Settings::set("vp-scale", Serializer::Float(plan.scale)); vp_scale->get_float(index));
Settings::save(); if (!status.ok())
LOG("Viewport scale preference failed: %s", status.message);
}; };
} }
@@ -1132,7 +1138,11 @@ void App::init_menu_tools()
rtl_btn->find<NodeCheckBox>("tools-rtl-check")->on_value_changed = [this, main](Node*, bool checked) rtl_btn->find<NodeCheckBox>("tools-rtl-check")->on_value_changed = [this, main](Node*, bool checked)
{ {
set_ui_rtl(checked); const auto status = pp::panopainter::execute_legacy_interface_direction_preference(
*this,
checked);
if (!status.ok())
LOG("Interface direction preference failed: %s", status.message);
}; };
} }
@@ -1178,10 +1188,11 @@ void App::init_menu_tools()
vr_btn->find<NodeCheckBox>("tools-vr-controllers-check")->on_value_changed = [this, main](Node* target, bool checked) vr_btn->find<NodeCheckBox>("tools-vr-controllers-check")->on_value_changed = [this, main](Node* target, bool checked)
{ {
const auto plan = pp::app::plan_vr_controllers_preference(checked); const auto status = pp::panopainter::execute_legacy_vr_controllers_preference(
vr_controllers_enabled = plan.value; *this,
Settings::set("vr-controllers-enabled", Serializer::Boolean(plan.value)); checked);
Settings::save(); if (!status.ok())
LOG("VR controllers preference failed: %s", status.message);
}; };
} }
@@ -1198,13 +1209,11 @@ void App::init_menu_tools()
btn->find<NodeCheckBox>("tools-timelapse-check")->on_value_changed = [this, main](Node*, bool checked) btn->find<NodeCheckBox>("tools-timelapse-check")->on_value_changed = [this, main](Node*, bool checked)
{ {
const auto plan = pp::app::plan_timelapse_preference(checked, App::I->rec_running); const auto status = pp::panopainter::execute_legacy_timelapse_preference(
if (plan.recording_action == pp::app::TimelapseRecordingAction::stop_recording) *this,
App::I->rec_stop(); checked);
else if (plan.recording_action == pp::app::TimelapseRecordingAction::start_recording) if (!status.ok())
App::I->rec_start(); LOG("Timelapse preference failed: %s", status.message);
Settings::set("auto-timelapse", Serializer::Boolean(plan.enabled));
Settings::save();
}; };
} }
@@ -1214,10 +1223,11 @@ void App::init_menu_tools()
mode->on_select = [mode](Node* target, int index) mode->on_select = [mode](Node* target, int index)
{ {
const auto plan = pp::app::plan_canvas_cursor_mode(index); const auto status = pp::panopainter::execute_legacy_canvas_cursor_mode_preference(
App::I->canvas->set_cursor_visibility((NodeCanvas::kCursorVisibility)plan.value); *App::I,
Settings::set("show-cursor", Serializer::Integer(plan.value)); index);
Settings::save(); if (!status.ok())
LOG("Cursor mode preference failed: %s", status.message);
}; };
} }
}; };

View File

@@ -0,0 +1,111 @@
#include "pch.h"
#include "legacy_app_preference_services.h"
#include "app.h"
#include "node_canvas.h"
#include "serializer.h"
#include "settings.h"
namespace pp::panopainter {
namespace {
class LegacyAppPreferenceServices final : public pp::app::AppPreferenceServices {
public:
explicit LegacyAppPreferenceServices(App& app) noexcept
: app_(app)
{
}
void apply_ui_scale(const pp::app::ScaleApplicationPlan& plan) override
{
app_.set_ui_scale(plan.scale);
}
void apply_viewport_scale(const pp::app::ScaleApplicationPlan& plan) override
{
if (!app_.canvas)
return;
app_.canvas->set_density(plan.scale);
Settings::set("vp-scale", Serializer::Float(plan.scale));
Settings::save();
}
void apply_interface_direction(const pp::app::InterfaceDirectionPlan& plan) override
{
app_.set_ui_rtl(plan.direction == pp::app::InterfaceDirection::right_to_left);
}
void apply_vr_controllers_preference(const pp::app::StoredBooleanPreferencePlan& plan) override
{
app_.vr_controllers_enabled = plan.value;
Settings::set("vr-controllers-enabled", Serializer::Boolean(plan.value));
Settings::save();
}
void apply_timelapse_preference(const pp::app::TimelapsePreferencePlan& plan) override
{
if (plan.recording_action == pp::app::TimelapseRecordingAction::stop_recording) {
app_.rec_stop();
} else if (plan.recording_action == pp::app::TimelapseRecordingAction::start_recording) {
app_.rec_start();
}
Settings::set("auto-timelapse", Serializer::Boolean(plan.enabled));
Settings::save();
}
void apply_canvas_cursor_mode(const pp::app::StoredIntegerPreferencePlan& plan) override
{
if (!app_.canvas)
return;
app_.canvas->set_cursor_visibility(static_cast<NodeCanvas::kCursorVisibility>(plan.value));
Settings::set("show-cursor", Serializer::Integer(plan.value));
Settings::save();
}
private:
App& app_;
};
} // namespace
pp::foundation::Status execute_legacy_ui_scale_preference(App& app, float requested_scale)
{
LegacyAppPreferenceServices services(app);
return pp::app::execute_ui_scale_preference(requested_scale, app.display_density, services);
}
pp::foundation::Status execute_legacy_viewport_scale_preference(App& app, float requested_scale)
{
LegacyAppPreferenceServices services(app);
return pp::app::execute_viewport_scale_preference(requested_scale, 1.0F, services);
}
pp::foundation::Status execute_legacy_interface_direction_preference(App& app, bool right_to_left)
{
LegacyAppPreferenceServices services(app);
return pp::app::execute_interface_direction_preference(right_to_left, services);
}
pp::foundation::Status execute_legacy_vr_controllers_preference(App& app, bool enabled)
{
LegacyAppPreferenceServices services(app);
return pp::app::execute_vr_controllers_preference(enabled, services);
}
pp::foundation::Status execute_legacy_timelapse_preference(App& app, bool enabled)
{
LegacyAppPreferenceServices services(app);
return pp::app::execute_timelapse_preference(enabled, app.rec_running, services);
}
pp::foundation::Status execute_legacy_canvas_cursor_mode_preference(App& app, int mode)
{
LegacyAppPreferenceServices services(app);
return pp::app::execute_canvas_cursor_mode_preference(mode, services);
}
} // namespace pp::panopainter

View File

@@ -0,0 +1,28 @@
#pragma once
#include "app_core/app_preferences.h"
class App;
namespace pp::panopainter {
[[nodiscard]] pp::foundation::Status execute_legacy_ui_scale_preference(
App& app,
float requested_scale);
[[nodiscard]] pp::foundation::Status execute_legacy_viewport_scale_preference(
App& app,
float requested_scale);
[[nodiscard]] pp::foundation::Status execute_legacy_interface_direction_preference(
App& app,
bool right_to_left);
[[nodiscard]] pp::foundation::Status execute_legacy_vr_controllers_preference(
App& app,
bool enabled);
[[nodiscard]] pp::foundation::Status execute_legacy_timelapse_preference(
App& app,
bool enabled);
[[nodiscard]] pp::foundation::Status execute_legacy_canvas_cursor_mode_preference(
App& app,
int mode);
} // namespace pp::panopainter

View File

@@ -2,9 +2,63 @@
#include "test_harness.h" #include "test_harness.h"
#include <array> #include <array>
#include <string>
namespace { namespace {
class FakeAppPreferenceServices final : public pp::app::AppPreferenceServices {
public:
void apply_ui_scale(const pp::app::ScaleApplicationPlan& plan) override
{
ui_scale = plan.scale;
ui_font_scale = plan.font_scale;
call_order += "ui-scale;";
}
void apply_viewport_scale(const pp::app::ScaleApplicationPlan& plan) override
{
viewport_scale = plan.scale;
viewport_font_scale = plan.font_scale;
call_order += "viewport-scale;";
}
void apply_interface_direction(const pp::app::InterfaceDirectionPlan& plan) override
{
interface_direction = plan.direction;
call_order += "direction;";
}
void apply_vr_controllers_preference(const pp::app::StoredBooleanPreferencePlan& plan) override
{
vr_controllers = plan.value;
call_order += "vr-controllers;";
}
void apply_timelapse_preference(const pp::app::TimelapsePreferencePlan& plan) override
{
timelapse_enabled = plan.enabled;
timelapse_action = plan.recording_action;
call_order += "timelapse;";
}
void apply_canvas_cursor_mode(const pp::app::StoredIntegerPreferencePlan& plan) override
{
cursor_mode = plan.value;
call_order += "cursor;";
}
float ui_scale = 0.0F;
float ui_font_scale = 0.0F;
float viewport_scale = 0.0F;
float viewport_font_scale = 0.0F;
pp::app::InterfaceDirection interface_direction = pp::app::InterfaceDirection::left_to_right;
bool vr_controllers = false;
bool timelapse_enabled = false;
pp::app::TimelapseRecordingAction timelapse_action = pp::app::TimelapseRecordingAction::no_op;
int cursor_mode = -1;
std::string call_order;
};
void ui_scale_computes_font_scale_from_display_density(pp::tests::Harness& harness) void ui_scale_computes_font_scale_from_display_density(pp::tests::Harness& harness)
{ {
const auto plan = pp::app::plan_ui_scale(1.5F, 2.0F); const auto plan = pp::app::plan_ui_scale(1.5F, 2.0F);
@@ -71,6 +125,40 @@ void simple_preferences_preserve_values_for_storage(pp::tests::Harness& harness)
PP_EXPECT(harness, pp::app::plan_canvas_cursor_mode(2).value == 2); PP_EXPECT(harness, pp::app::plan_canvas_cursor_mode(2).value == 2);
} }
void preference_executor_dispatches_side_effect_plans(pp::tests::Harness& harness)
{
FakeAppPreferenceServices services;
PP_EXPECT(harness, pp::app::execute_ui_scale_preference(1.5F, 2.0F, services).ok());
PP_EXPECT(harness, pp::app::execute_viewport_scale_preference(1.25F, 1.0F, services).ok());
PP_EXPECT(harness, pp::app::execute_interface_direction_preference(true, services).ok());
PP_EXPECT(harness, pp::app::execute_vr_controllers_preference(true, services).ok());
PP_EXPECT(harness, pp::app::execute_timelapse_preference(true, false, services).ok());
PP_EXPECT(harness, pp::app::execute_canvas_cursor_mode_preference(2, services).ok());
PP_EXPECT(harness, services.ui_scale == 1.5F);
PP_EXPECT(harness, services.ui_font_scale == 3.0F);
PP_EXPECT(harness, services.viewport_scale == 1.25F);
PP_EXPECT(harness, services.viewport_font_scale == 1.25F);
PP_EXPECT(harness, services.interface_direction == pp::app::InterfaceDirection::right_to_left);
PP_EXPECT(harness, services.vr_controllers);
PP_EXPECT(harness, services.timelapse_enabled);
PP_EXPECT(harness, services.timelapse_action == pp::app::TimelapseRecordingAction::start_recording);
PP_EXPECT(harness, services.cursor_mode == 2);
PP_EXPECT(
harness,
services.call_order == "ui-scale;viewport-scale;direction;vr-controllers;timelapse;cursor;");
}
void preference_executor_preserves_timelapse_stop_action(pp::tests::Harness& harness)
{
FakeAppPreferenceServices services;
PP_EXPECT(harness, pp::app::execute_timelapse_preference(false, true, services).ok());
PP_EXPECT(harness, !services.timelapse_enabled);
PP_EXPECT(harness, services.timelapse_action == pp::app::TimelapseRecordingAction::stop_recording);
}
} }
int main() int main()
@@ -88,5 +176,7 @@ int main()
"timelapse preference starts and stops only on state change", "timelapse preference starts and stops only on state change",
timelapse_preference_starts_and_stops_only_on_state_change); timelapse_preference_starts_and_stops_only_on_state_change);
harness.run("simple preferences preserve values for storage", simple_preferences_preserve_values_for_storage); harness.run("simple preferences preserve values for storage", simple_preferences_preserve_values_for_storage);
harness.run("preference executor dispatches side effect plans", preference_executor_dispatches_side_effect_plans);
harness.run("preference executor preserves timelapse stop action", preference_executor_preserves_timelapse_stop_action);
return harness.finish(); return harness.finish();
} }