Route quick slider preview through app core
This commit is contained in:
@@ -285,9 +285,13 @@ Known local toolchain state:
|
||||
by `DEBT-0024`.
|
||||
- `src/legacy_quick_ui_services.*` is the current UI-shell bridge for quick
|
||||
brush/color slot selection, popup routing, mini-state restore, and mini-state
|
||||
reset execution. It keeps those live paths on the `pp_app_core` contracts
|
||||
while legacy quick widgets, brush previews, color picker state, and preset
|
||||
popup execution remain tracked by `DEBT-0025`.
|
||||
reset execution. Quick size/flow slider preview cursor placement and pen/line
|
||||
mode tip flags now consume the tested `pp_app_core` planner directly from
|
||||
`NodePanelQuick`, and `pano_cli plan-quick-slider-preview` exposes that path
|
||||
for automation. It keeps those live paths on the `pp_app_core` contracts
|
||||
while legacy quick widgets, brush previews, color picker state, preset popup
|
||||
execution, and direct legacy `CanvasMode*` field writes remain tracked by
|
||||
`DEBT-0025`.
|
||||
- `pano_cli simulate-image-import` decodes an embedded tiny PNG through
|
||||
`pp_assets`, attaches it to `pp_document`, and is covered by
|
||||
`pano_cli_simulate_image_import_smoke`.
|
||||
|
||||
@@ -62,6 +62,12 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
that view model in the live app, and `pano_cli plan-brush-refresh` exposes the
|
||||
same fan-out path for automation. Retained legacy quick/stroke/color widget
|
||||
writes plus `Brush`/`Canvas::I` ownership remain open under DEBT-0023.
|
||||
- 2026-06-05: DEBT-0025 was narrowed. Quick size/flow slider preview planning
|
||||
now goes through tested `pp_app_core`, live `NodePanelQuick` slider callbacks
|
||||
consume that plan for cursor placement and pen/line tip flags, and
|
||||
`pano_cli plan-quick-slider-preview` exposes the path for automation. Legacy
|
||||
quick widgets, brush previews, popup state, and direct `CanvasMode*` field
|
||||
writes remain open under DEBT-0025.
|
||||
- 2026-06-04: DEBT-0036 was narrowed again. Canvas stroke commit,
|
||||
thumbnail, and object-draw history paths now query saved blend state through
|
||||
tested `pp_renderer_gl` capability-state dispatch; CanvasLayer equirect
|
||||
@@ -100,7 +106,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| DEBT-0022 | Open | Modernization | Animation panel frame command planning, panel action planning, panel view-model projection, timeline scrub planning, panel-control/timeline execution dispatch, selected-frame click dispatch, playback tick stepping, and play-mode toggles now consume pure `pp_app_core` through `NodePanelAnimation`, `NodeAnimationTimeline`, `pano_cli plan-animation-operation`, `pano_cli plan-animation-panel-action`, `pano_cli plan-animation-panel-view`, `pano_cli plan-animation-timeline-scrub`, and `DocumentAnimationServices`; live execution is centralized in `src/legacy_document_animation_services.*`, but that bridge still mutates or reads legacy `Canvas`/`Layer` frame state, canvas mode, animation-panel timeline/playback fields, and uses a temporary `NodePanelAnimation` friend adapter | Preserve existing animation panel behavior while timeline/frame commands move toward the document/app command boundary | `pp_app_core_document_animation_tests`; `pano_cli plan-animation-operation --kind add --frame-count 2 --current-frame 0`; `pano_cli plan-animation-operation --kind select --frame-count 3 --selected-frame 1 --layer-index 2 --layer-id 42`; `pano_cli plan-animation-operation --kind playback --total-duration 5 --current-frame 4 --offset 1`; `pano_cli plan-animation-operation --kind toggle-playback --playing`; `pano_cli plan-animation-panel-action --action next --total-duration 5 --current-frame 4`; `pano_cli plan-animation-panel-view --layer-count 2 --frame-count 3 --total-duration 6 --current-layer 1 --current-frame 4`; `pano_cli plan-animation-timeline-scrub --total-duration 5 --cursor-x 174.99`; `ctest --preset desktop-fast --build-config Debug` | Animation frame/timeline/playback execution is owned by injected document/app timeline services with no legacy `Canvas`/`Layer`/canvas-mode adapter and UI nodes acting only as adapters or removed entirely |
|
||||
| DEBT-0023 | Open | Modernization | Brush/color/preset/stroke-settings UI planning, texture-list add/remove/reorder planning, brush preset-list add/select/move/remove/clear planning, stroke-panel slider/toggle/blend/reset planning, stroke-panel view projection, app-level brush refresh view projection, and execution dispatch now consume pure `pp_app_core` through `App::init_sidebar`, `App::brush_update()`, `NodePanelBrush`, `NodePanelBrushPreset`, `NodePanelStroke`, `NodePanelStroke::update_controls()`, restored/docked floating-panel callbacks, `pano_cli plan-brush-operation`, `pano_cli plan-brush-refresh`, `pano_cli plan-brush-texture-list`, `pano_cli plan-brush-preset-list`, `pano_cli plan-brush-stroke-control`, `pano_cli plan-brush-stroke-panel-view`, `BrushUiServices`, `BrushTextureListServices`, `BrushPresetListServices`, and `BrushStrokeControlServices`, and live execution is centralized in `src/legacy_brush_ui_services.*` or narrow legacy service bridges where possible, but preset-list execution still mutates legacy `NodePanelBrushPreset` child nodes directly while the bridge and panel adapter still mutate/read legacy `Brush`/`Canvas::I`, load/save legacy brush texture images, apply retained legacy quick/stroke/color widget writes, own brush thumbnail paths and popup behavior, and use temporary `NodePanelBrush`/`NodePanelBrushPreset` friend adapters to reach private list state | Preserve existing brush UI behavior while brush commands and view projection move toward a brush/app/asset command boundary and asset-managed texture/preset selection | `pp_app_core_brush_ui_tests`; `pano_cli plan-brush-operation --kind color --r 0.25 --g 0.5 --b 0.75 --a 1`; `pano_cli plan-brush-operation --kind pattern --path data/patterns/noise.png --thumb data/patterns/thumbs/noise.png`; `pano_cli plan-brush-refresh --floating-picker --tip-flow 0.8 --tip-size 48 --r 0.2 --g 0.3 --b 0.4 --a 1`; `pano_cli plan-brush-texture-list --kind add --dir brushes --data-path data --source C:/Temp/soft.png`; `pano_cli plan-brush-preset-list --kind remove --item-count 1 --current-index 0`; `pano_cli plan-brush-stroke-control --kind float --setting tip-size --value 42.5`; `pano_cli plan-brush-stroke-control --kind blend --setting pattern --blend-mode 3`; `pano_cli plan-brush-stroke-panel-view --tip-size 64 --jitter-scatter 0.4 --dual-disabled --tip-blend-mode 2 --pattern-blend-mode 5`; `ctest --preset desktop-fast --build-config Debug` | Brush color/texture/preset/stroke-settings, texture-list, preset-list, stroke-control execution, stroke-panel projection, and brush refresh projection are owned by injected brush/app/asset/UI services with no legacy brush/canvas adapter, direct `NodePanelBrushPreset` child mutation, brush thumbnail/popup ownership, legacy quick/stroke/color widget writes, or brush-panel friend access |
|
||||
| DEBT-0024 | Open | Modernization | Grid/heightmap/lightmap UI planning and execution dispatch now consume pure `pp_app_core` through `NodePanelGrid`, `pano_cli plan-grid-operation`, and the `GridUiServices` boundary; live execution is centralized in `src/legacy_grid_ui_services.*`, and retained CPU lightmap row dispatch now uses shared `parallel_for` instead of platform-specific Win32/Apple worker APIs, but the bridge still performs legacy image loading, OpenGL texture updates, nanort lightmap baking/progress, and `Canvas::draw_objects` commit execution | Preserve grid/lightmap behavior while moving renderable grid commands toward app/renderer/document boundaries | `pp_app_core_grid_ui_tests`; `pano_cli plan-grid-operation --kind render --float32 --texture-resolution 1024 --samples 32`; `ctest --preset desktop-fast --build-config Debug` | Grid heightmap/lightmap execution is owned by app/renderer/document services with `NodePanelGrid` acting only as UI adapter |
|
||||
| DEBT-0025 | Open | Modernization | Quick brush/color slot and mini-state planning and execution dispatch now consume pure `pp_app_core` through `NodePanelQuick`, `pano_cli plan-quick-operation`, and the `QuickUiServices` boundary; live execution is centralized in `src/legacy_quick_ui_services.*`, but the bridge still mutates legacy quick UI widgets, `Brush` previews, color picker popup state, and preset popup state | Preserve quick-panel behavior while quick brush/color commands move toward a brush/app command boundary with safer automation coverage | `pp_app_core_quick_ui_tests`; `pano_cli plan-quick-operation --kind brush --current-index 0 --slot-index 2`; `pano_cli plan-quick-operation --kind restore --brush-index 2 --color-index 1 --fire-event`; `ctest --preset desktop-fast --build-config Debug` | Quick-panel selection, popup, restore, reset, brush preview, and color execution are owned by injected app/brush/UI services with no legacy quick-panel adapter |
|
||||
| DEBT-0025 | Open | Modernization | Quick brush/color slot, mini-state, and size/flow slider preview planning now consume pure `pp_app_core` through `NodePanelQuick`, `pano_cli plan-quick-operation`, `pano_cli plan-quick-slider-preview`, and the `QuickUiServices` boundary; live slot/popup/restore/reset execution is centralized in `src/legacy_quick_ui_services.*`, and live slider callbacks now consume `pp_app_core` preview cursor/tip planning directly, but the bridge and panel adapter still mutate legacy quick UI widgets, `Brush` previews, color picker popup state, preset popup state, and direct legacy `CanvasModePen`/`CanvasModeLine` fields | Preserve quick-panel behavior while quick brush/color and slider preview commands move toward a brush/app command boundary with safer automation coverage | `pp_app_core_quick_ui_tests`; `pano_cli plan-quick-operation --kind brush --current-index 0 --slot-index 2`; `pano_cli plan-quick-operation --kind restore --brush-index 2 --color-index 1 --fire-event`; `pano_cli plan-quick-slider-preview --slider-x 10 --slider-y 20 --slider-height 40 --zoom 2 --pen-mode --no-line-mode`; `ctest --preset desktop-fast --build-config Debug` | Quick-panel selection, popup, restore, reset, brush preview, color execution, and slider preview mode updates are owned by injected app/brush/UI/canvas services with no legacy quick-panel adapter, popup adapter, direct brush-preview mutation, or direct `CanvasMode*` field writes |
|
||||
| DEBT-0026 | Open | Modernization | Toolbar history command planning and canvas hotkey history dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `NodeCanvas`, `pano_cli plan-history-operation`, and the `HistoryUiServices` boundary, and both live callers share `src/legacy_history_services.*` for saturated legacy history metrics and execution, but the shared live bridge still mutates legacy `ActionManager` stacks directly | Preserve undo/redo/clear behavior while moving action history toward document/app command services | `pp_app_core_history_ui_tests`; `pano_cli plan-history-operation --kind undo --undo-count 2`; `pano_cli plan-history-operation --kind clear --undo-count 2 --redo-count 1 --memory-bytes 4096`; `ctest --preset desktop-fast --build-config Debug` | Undo/redo/clear execution is owned by injected document/app history services with no legacy `ActionManager` adapter |
|
||||
| DEBT-0027 | Open | Modernization | Canvas draw-tool toolbar command, canvas input mode switching, active-state planning/execution dispatch, and canvas keyboard/touch command planning now consume pure `pp_app_core` through `App::init_toolbar_draw`, `App::update`, `NodeCanvas`, `pano_cli plan-canvas-tool`, `pano_cli plan-canvas-tool-state`, `pano_cli plan-canvas-hotkey`, `CanvasToolServices`, and `CanvasHotkeyServices`, live toolbar/input/hotkey execution is centralized in `src/legacy_canvas_tool_services.*`, and canvas mode tip visibility plus pressure remapping now route through `PlatformServices`, but the bridge still mutates or reads legacy `Canvas` mode state, pen picking state, touch-lock state, transform copy/cut action objects, `ActionManager`, legacy save UI, legacy stroke size controls, and cursor/UI singletons | Preserve current toolbar, stylus eraser, keyboard, and touch command behavior while canvas input/tools move toward an app/document command boundary | `pp_app_core_canvas_tool_ui_tests`; `pp_app_core_canvas_hotkey_tests`; `pp_platform_api_tests`; `pano_cli plan-canvas-tool --kind copy`; `pano_cli plan-canvas-tool-state --mode draw --picking --touch-lock`; `pano_cli plan-canvas-hotkey --event key-up --key z --ctrl --undo-count 2`; `pano_cli plan-canvas-hotkey --event key-up --key s --ctrl --shift`; `ctest --preset desktop-fast --build-config Debug` | Canvas tool selection, toolbar state refresh, picking, touch lock, stylus eraser/key mode switching, hotkey/touch command dispatch, save hotkeys, history hotkeys, brush-size hotkeys, and transform action execution are owned by injected app/document/canvas services with no legacy toolbar/canvas adapter |
|
||||
| DEBT-0028 | Open | Modernization | Canvas clear command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, Layer menu clear, `pano_cli plan-canvas-clear`, and the `DocumentCanvasClearServices` boundary, and toolbar/Layer-menu clear share `src/legacy_document_canvas_services.*`, but the shared live bridge still calls legacy `Canvas::clear`, which records `ActionLayerClear`, clears the current layer/frame, and marks legacy `Canvas::I` unsaved | Preserve clear-current-layer behavior while canvas/document commands move toward document/app command services | `pp_app_core_document_canvas_tests`; `pano_cli plan-canvas-clear --r 0 --g 0.1 --b 0.2 --a 0.3`; `pano_cli plan-canvas-clear --no-canvas`; `pano_cli plan-layer-menu --command clear --current-index 1 --current-name Paint`; `ctest --preset desktop-fast --build-config Debug` | Canvas clear execution, undo recording, dirty-state updates, and clear color handling are owned by injected document/app services with no legacy canvas-clear adapter |
|
||||
|
||||
@@ -630,6 +630,10 @@ slot selection versus popup opening, plus quick mini-state restore/reset
|
||||
validation used by the live quick panel. Quick-panel execution now dispatches
|
||||
through `QuickUiServices` in `src/legacy_quick_ui_services.*` before the legacy
|
||||
`Brush`, color picker, stroke preview, and preset popup adapter continues.
|
||||
`pano_cli plan-quick-slider-preview` exposes app-core planning for quick
|
||||
size/flow slider preview cursor placement, RTL offset handling, and pen/line
|
||||
mode tip flags; live `NodePanelQuick` slider callbacks now consume that plan
|
||||
before the retained `CanvasModePen`/`CanvasModeLine` and brush-preview updates.
|
||||
`pano_cli plan-tools-menu` and `pano_cli plan-tools-panel` expose app-core
|
||||
planning for top-level Tools commands and floating-panel requests, including
|
||||
already-visible no-ops, panel chrome metadata, shortcuts, camera reset,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class QuickUiSlotKind {
|
||||
@@ -35,6 +37,27 @@ struct QuickUiPlan {
|
||||
bool mutates_quick_state = false;
|
||||
};
|
||||
|
||||
struct QuickSliderPreviewInput {
|
||||
bool ui_rtl = false;
|
||||
float slider_x = 0.0F;
|
||||
float slider_y = 0.0F;
|
||||
float slider_height = 0.0F;
|
||||
float zoom = 1.0F;
|
||||
bool has_pen_mode = false;
|
||||
bool has_line_mode = false;
|
||||
};
|
||||
|
||||
struct QuickSliderPreviewPlan {
|
||||
float cursor_x = 0.0F;
|
||||
float cursor_y = 0.0F;
|
||||
bool updates_pen_mode = false;
|
||||
bool updates_line_mode = false;
|
||||
bool draws_tip = false;
|
||||
bool disables_pen_outline = false;
|
||||
bool redraws_brush_preview = false;
|
||||
bool invokes_change_callback = false;
|
||||
};
|
||||
|
||||
class QuickUiServices {
|
||||
public:
|
||||
virtual ~QuickUiServices() = default;
|
||||
@@ -45,6 +68,15 @@ public:
|
||||
virtual void reset_state(bool fire_event) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_quick_slider_float(float value) noexcept
|
||||
{
|
||||
if (!std::isfinite(value)) {
|
||||
return pp::foundation::Status::invalid_argument("quick slider preview value must be finite");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_quick_slot_count(int slot_count) noexcept
|
||||
{
|
||||
if (slot_count <= 0) {
|
||||
@@ -68,6 +100,47 @@ public:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<QuickSliderPreviewPlan> plan_quick_slider_preview(
|
||||
const QuickSliderPreviewInput& input)
|
||||
{
|
||||
const auto x_status = validate_quick_slider_float(input.slider_x);
|
||||
if (!x_status.ok()) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(x_status);
|
||||
}
|
||||
const auto y_status = validate_quick_slider_float(input.slider_y);
|
||||
if (!y_status.ok()) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(y_status);
|
||||
}
|
||||
const auto height_status = validate_quick_slider_float(input.slider_height);
|
||||
if (!height_status.ok()) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(height_status);
|
||||
}
|
||||
if (input.slider_height < 0.0F) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("quick slider preview height must not be negative"));
|
||||
}
|
||||
const auto zoom_status = validate_quick_slider_float(input.zoom);
|
||||
if (!zoom_status.ok()) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(zoom_status);
|
||||
}
|
||||
if (input.zoom <= 0.0F) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("quick slider preview zoom must be positive"));
|
||||
}
|
||||
|
||||
const float offset = input.ui_rtl ? -100.0F : 100.0F;
|
||||
QuickSliderPreviewPlan plan;
|
||||
plan.cursor_x = (input.slider_x + offset) * input.zoom;
|
||||
plan.cursor_y = (input.slider_y + input.slider_height * 0.5F) * input.zoom;
|
||||
plan.updates_pen_mode = input.has_pen_mode;
|
||||
plan.updates_line_mode = input.has_line_mode;
|
||||
plan.draws_tip = input.has_pen_mode || input.has_line_mode;
|
||||
plan.disables_pen_outline = input.has_pen_mode;
|
||||
plan.redraws_brush_preview = true;
|
||||
plan.invokes_change_callback = true;
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<QuickUiPlan> plan_quick_slot_click(
|
||||
QuickUiSlotKind slot_kind,
|
||||
int current_index,
|
||||
|
||||
@@ -121,39 +121,13 @@ void NodePanelQuick::init_controls()
|
||||
|
||||
m_slider_size = find<NodeSliderV>("quick-size");
|
||||
m_slider_size->on_value_changed = [this](Node* target, float value) {
|
||||
float off = App::I->ui_rtl ? -100.f : 100.f;
|
||||
auto newpos = (m_slider_flow->m_pos + glm::vec2(off, m_slider_flow->m_size.y / 2.f)) * App::I->zoom;
|
||||
if (auto m = dynamic_cast<CanvasModePen*>(Canvas::I->modes[(int)Canvas::I->m_current_mode][0]))
|
||||
{
|
||||
m->m_cur_pos = newpos;
|
||||
m->m_draw_tip = true;
|
||||
m->m_draw_outline = false;
|
||||
}
|
||||
if (auto m = dynamic_cast<CanvasModeLine*>(Canvas::I->modes[(int)Canvas::I->m_current_mode][0]))
|
||||
{
|
||||
m->m_cur_pos = newpos;
|
||||
m->m_draw_tip = true;
|
||||
}
|
||||
m_button_brush_current_preview->draw_stroke();
|
||||
update_slider_preview();
|
||||
if (on_size_change)
|
||||
on_size_change(target, value);
|
||||
};
|
||||
m_slider_flow = find<NodeSliderV>("quick-flow");
|
||||
m_slider_flow->on_value_changed = [this](Node* target, float value) {
|
||||
float off = App::I->ui_rtl ? -100.f : 100.f;
|
||||
auto newpos = (m_slider_flow->m_pos + glm::vec2(off, m_slider_flow->m_size.y / 2.f)) * App::I->zoom;
|
||||
if (auto m = dynamic_cast<CanvasModePen*>(Canvas::I->modes[(int)Canvas::I->m_current_mode][0]))
|
||||
{
|
||||
m->m_cur_pos = newpos;
|
||||
m->m_draw_tip = true;
|
||||
m->m_draw_outline = false;
|
||||
}
|
||||
if (auto m = dynamic_cast<CanvasModeLine*>(Canvas::I->modes[(int)Canvas::I->m_current_mode][0]))
|
||||
{
|
||||
m->m_cur_pos = newpos;
|
||||
m->m_draw_tip = true;
|
||||
}
|
||||
m_button_brush_current_preview->draw_stroke();
|
||||
update_slider_preview();
|
||||
if (on_flow_change)
|
||||
on_flow_change(target, value);
|
||||
};
|
||||
@@ -199,6 +173,45 @@ NodeButtonCustom* NodePanelQuick::init_button_brush(const std::string& name, boo
|
||||
return button;
|
||||
}
|
||||
|
||||
void NodePanelQuick::update_slider_preview()
|
||||
{
|
||||
if (!App::I || !Canvas::I || !Canvas::I->m_mode || Canvas::I->m_mode->empty() || !m_slider_flow || !m_button_brush_current_preview)
|
||||
return;
|
||||
|
||||
auto* mode = (*Canvas::I->m_mode)[0];
|
||||
auto* pen_mode = dynamic_cast<CanvasModePen*>(mode);
|
||||
auto* line_mode = dynamic_cast<CanvasModeLine*>(mode);
|
||||
|
||||
const auto plan = pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
|
||||
.ui_rtl = App::I->ui_rtl,
|
||||
.slider_x = m_slider_flow->m_pos.x,
|
||||
.slider_y = m_slider_flow->m_pos.y,
|
||||
.slider_height = m_slider_flow->m_size.y,
|
||||
.zoom = App::I->zoom,
|
||||
.has_pen_mode = pen_mode != nullptr,
|
||||
.has_line_mode = line_mode != nullptr,
|
||||
});
|
||||
if (!plan) {
|
||||
LOG("Quick slider preview failed: %s", plan.status().message);
|
||||
return;
|
||||
}
|
||||
|
||||
const glm::vec2 cursor(plan.value().cursor_x, plan.value().cursor_y);
|
||||
if (plan.value().updates_pen_mode && pen_mode)
|
||||
{
|
||||
pen_mode->m_cur_pos = cursor;
|
||||
pen_mode->m_draw_tip = plan.value().draws_tip;
|
||||
pen_mode->m_draw_outline = !plan.value().disables_pen_outline;
|
||||
}
|
||||
if (plan.value().updates_line_mode && line_mode)
|
||||
{
|
||||
line_mode->m_cur_pos = cursor;
|
||||
line_mode->m_draw_tip = plan.value().draws_tip;
|
||||
}
|
||||
if (plan.value().redraws_brush_preview)
|
||||
m_button_brush_current_preview->draw_stroke();
|
||||
}
|
||||
|
||||
void NodePanelQuick::handle_button_brush_click(Node* button)
|
||||
{
|
||||
const auto clicked = std::find(m_button_brushes.begin(), m_button_brushes.end(), button);
|
||||
|
||||
@@ -51,6 +51,7 @@ public:
|
||||
private:
|
||||
void init_controls();
|
||||
NodeButtonCustom* init_button_brush(const std::string& name, bool szp, bool flp);
|
||||
void update_slider_preview();
|
||||
void handle_button_brush_click(Node* target);
|
||||
void handle_button_color_click(Node* target);
|
||||
};
|
||||
|
||||
@@ -1748,6 +1748,30 @@ if(TARGET pano_cli)
|
||||
LABELS "app;ui;paint;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_quick_slider_preview_pen_smoke
|
||||
COMMAND pano_cli plan-quick-slider-preview --slider-x 10 --slider-y 20 --slider-height 40 --zoom 2 --pen-mode --no-line-mode)
|
||||
set_tests_properties(pano_cli_plan_quick_slider_preview_pen_smoke PROPERTIES
|
||||
LABELS "app;ui;paint;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-quick-slider-preview\".*\"cursorX\":220.*\"cursorY\":80.*\"updatesPenMode\":true.*\"updatesLineMode\":false.*\"drawsTip\":true.*\"disablesPenOutline\":true.*\"redrawsBrushPreview\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_quick_slider_preview_rtl_line_smoke
|
||||
COMMAND pano_cli plan-quick-slider-preview --rtl --slider-x 50 --slider-y 10 --slider-height 20 --zoom 1.5 --no-pen-mode --line-mode)
|
||||
set_tests_properties(pano_cli_plan_quick_slider_preview_rtl_line_smoke PROPERTIES
|
||||
LABELS "app;ui;paint;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-quick-slider-preview\".*\"rtl\":true.*\"cursorX\":-75.*\"cursorY\":30.*\"updatesPenMode\":false.*\"updatesLineMode\":true.*\"drawsTip\":true.*\"disablesPenOutline\":false")
|
||||
|
||||
add_test(NAME pano_cli_plan_quick_slider_preview_rejects_bad_float
|
||||
COMMAND pano_cli plan-quick-slider-preview --bad-float)
|
||||
set_tests_properties(pano_cli_plan_quick_slider_preview_rejects_bad_float PROPERTIES
|
||||
LABELS "app;ui;paint;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_quick_slider_preview_rejects_zero_zoom
|
||||
COMMAND pano_cli plan-quick-slider-preview --zoom 0)
|
||||
set_tests_properties(pano_cli_plan_quick_slider_preview_rejects_zero_zoom PROPERTIES
|
||||
LABELS "app;ui;paint;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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "app_core/quick_ui.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
@@ -125,6 +126,66 @@ void restore_and_reset_validate_state(pp::tests::Harness& harness)
|
||||
PP_EXPECT(harness, !pp::app::plan_quick_state_reset(0, false));
|
||||
}
|
||||
|
||||
void slider_preview_projects_cursor_and_mode_flags(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto ltr = pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
|
||||
.ui_rtl = false,
|
||||
.slider_x = 10.0F,
|
||||
.slider_y = 20.0F,
|
||||
.slider_height = 40.0F,
|
||||
.zoom = 2.0F,
|
||||
.has_pen_mode = true,
|
||||
.has_line_mode = false,
|
||||
});
|
||||
PP_EXPECT(harness, ltr);
|
||||
if (ltr) {
|
||||
PP_EXPECT(harness, ltr.value().cursor_x == 220.0F);
|
||||
PP_EXPECT(harness, ltr.value().cursor_y == 80.0F);
|
||||
PP_EXPECT(harness, ltr.value().updates_pen_mode);
|
||||
PP_EXPECT(harness, !ltr.value().updates_line_mode);
|
||||
PP_EXPECT(harness, ltr.value().draws_tip);
|
||||
PP_EXPECT(harness, ltr.value().disables_pen_outline);
|
||||
PP_EXPECT(harness, ltr.value().redraws_brush_preview);
|
||||
PP_EXPECT(harness, ltr.value().invokes_change_callback);
|
||||
}
|
||||
|
||||
const auto rtl_line = pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
|
||||
.ui_rtl = true,
|
||||
.slider_x = 50.0F,
|
||||
.slider_y = 10.0F,
|
||||
.slider_height = 20.0F,
|
||||
.zoom = 1.5F,
|
||||
.has_pen_mode = false,
|
||||
.has_line_mode = true,
|
||||
});
|
||||
PP_EXPECT(harness, rtl_line);
|
||||
if (rtl_line) {
|
||||
PP_EXPECT(harness, rtl_line.value().cursor_x == -75.0F);
|
||||
PP_EXPECT(harness, rtl_line.value().cursor_y == 30.0F);
|
||||
PP_EXPECT(harness, !rtl_line.value().updates_pen_mode);
|
||||
PP_EXPECT(harness, rtl_line.value().updates_line_mode);
|
||||
PP_EXPECT(harness, rtl_line.value().draws_tip);
|
||||
PP_EXPECT(harness, !rtl_line.value().disables_pen_outline);
|
||||
}
|
||||
}
|
||||
|
||||
void slider_preview_rejects_invalid_geometry(pp::tests::Harness& harness)
|
||||
{
|
||||
PP_EXPECT(harness, !pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
|
||||
.slider_x = std::nanf(""),
|
||||
.slider_height = 1.0F,
|
||||
.zoom = 1.0F,
|
||||
}));
|
||||
PP_EXPECT(harness, !pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
|
||||
.slider_height = -1.0F,
|
||||
.zoom = 1.0F,
|
||||
}));
|
||||
PP_EXPECT(harness, !pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
|
||||
.slider_height = 1.0F,
|
||||
.zoom = 0.0F,
|
||||
}));
|
||||
}
|
||||
|
||||
void executor_dispatches_selection_popup_restore_and_reset(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeQuickUiServices services;
|
||||
@@ -216,6 +277,8 @@ int main()
|
||||
harness.run("slot click selects or opens popup", slot_click_selects_or_opens_popup);
|
||||
harness.run("slot click rejects invalid indices", slot_click_rejects_invalid_indices);
|
||||
harness.run("restore and reset validate state", restore_and_reset_validate_state);
|
||||
harness.run("slider preview projects cursor and mode flags", slider_preview_projects_cursor_and_mode_flags);
|
||||
harness.run("slider preview rejects invalid geometry", slider_preview_rejects_invalid_geometry);
|
||||
harness.run("executor dispatches selection popup restore and reset", executor_dispatches_selection_popup_restore_and_reset);
|
||||
harness.run("executor rejects malformed quick plans", executor_rejects_malformed_quick_plans);
|
||||
return harness.finish();
|
||||
|
||||
@@ -525,6 +525,17 @@ struct PlanQuickOperationArgs {
|
||||
bool fire_event = false;
|
||||
};
|
||||
|
||||
struct PlanQuickSliderPreviewArgs {
|
||||
bool rtl = false;
|
||||
float slider_x = 10.0F;
|
||||
float slider_y = 20.0F;
|
||||
float slider_height = 40.0F;
|
||||
float zoom = 2.0F;
|
||||
bool pen_mode = true;
|
||||
bool line_mode = false;
|
||||
bool bad_float = false;
|
||||
};
|
||||
|
||||
struct PlanToolsMenuArgs {
|
||||
std::string command = "shortcuts";
|
||||
bool sonarpen_available = false;
|
||||
@@ -1996,6 +2007,7 @@ void print_help()
|
||||
<< " plan-history-operation --kind undo|redo|clear [--undo-count N] [--redo-count N] [--memory-bytes N]\n"
|
||||
<< " plan-main-toolbar --command open|save|undo|redo|clear-history|clear-canvas|message-box|settings [--undo-count N] [--redo-count N] [--memory-bytes N] [--no-canvas]\n"
|
||||
<< " plan-quick-operation --kind brush|color|restore|reset [--current-index N] [--slot-index N] [--brush-index N] [--color-index N] [--slot-count N] [--fire-event]\n"
|
||||
<< " plan-quick-slider-preview [--rtl] [--slider-x N] [--slider-y N] [--slider-height N] [--zoom N] [--pen-mode|--no-pen-mode] [--line-mode|--no-line-mode] [--bad-float]\n"
|
||||
<< " plan-share-file [--path FILE]\n"
|
||||
<< " plan-picked-path [--path FILE]\n"
|
||||
<< " plan-display-file [--path FILE]\n"
|
||||
@@ -7129,6 +7141,94 @@ int plan_quick_operation(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_quick_slider_preview_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
PlanQuickSliderPreviewArgs& args)
|
||||
{
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const std::string_view key(argv[i]);
|
||||
if (key == "--slider-x" || key == "--slider-y" || key == "--slider-height" || key == "--zoom") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
const auto value = parse_float_arg(argv[++i]);
|
||||
if (!value) {
|
||||
return value.status();
|
||||
}
|
||||
if (key == "--slider-x") {
|
||||
args.slider_x = value.value();
|
||||
} else if (key == "--slider-y") {
|
||||
args.slider_y = value.value();
|
||||
} else if (key == "--slider-height") {
|
||||
args.slider_height = value.value();
|
||||
} else {
|
||||
args.zoom = value.value();
|
||||
}
|
||||
} else if (key == "--rtl") {
|
||||
args.rtl = true;
|
||||
} else if (key == "--pen-mode") {
|
||||
args.pen_mode = true;
|
||||
} else if (key == "--no-pen-mode") {
|
||||
args.pen_mode = false;
|
||||
} else if (key == "--line-mode") {
|
||||
args.line_mode = true;
|
||||
} else if (key == "--no-line-mode") {
|
||||
args.line_mode = false;
|
||||
} else if (key == "--bad-float") {
|
||||
args.bad_float = true;
|
||||
} else {
|
||||
return pp::foundation::Status::invalid_argument("unknown option");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
int plan_quick_slider_preview(int argc, char** argv)
|
||||
{
|
||||
PlanQuickSliderPreviewArgs args;
|
||||
const auto status = parse_plan_quick_slider_preview_args(argc, argv, args);
|
||||
if (!status.ok()) {
|
||||
print_error("plan-quick-slider-preview", status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto plan = pp::app::plan_quick_slider_preview(pp::app::QuickSliderPreviewInput {
|
||||
.ui_rtl = args.rtl,
|
||||
.slider_x = args.bad_float ? std::nanf("") : args.slider_x,
|
||||
.slider_y = args.slider_y,
|
||||
.slider_height = args.slider_height,
|
||||
.zoom = args.zoom,
|
||||
.has_pen_mode = args.pen_mode,
|
||||
.has_line_mode = args.line_mode,
|
||||
});
|
||||
if (!plan) {
|
||||
print_error("plan-quick-slider-preview", plan.status().message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto& value = plan.value();
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-quick-slider-preview\""
|
||||
<< ",\"state\":{\"rtl\":" << json_bool(args.rtl)
|
||||
<< ",\"sliderX\":" << args.slider_x
|
||||
<< ",\"sliderY\":" << args.slider_y
|
||||
<< ",\"sliderHeight\":" << args.slider_height
|
||||
<< ",\"zoom\":" << args.zoom
|
||||
<< ",\"penMode\":" << json_bool(args.pen_mode)
|
||||
<< ",\"lineMode\":" << json_bool(args.line_mode)
|
||||
<< "},\"plan\":{\"cursorX\":" << value.cursor_x
|
||||
<< ",\"cursorY\":" << value.cursor_y
|
||||
<< ",\"updatesPenMode\":" << json_bool(value.updates_pen_mode)
|
||||
<< ",\"updatesLineMode\":" << json_bool(value.updates_line_mode)
|
||||
<< ",\"drawsTip\":" << json_bool(value.draws_tip)
|
||||
<< ",\"disablesPenOutline\":" << json_bool(value.disables_pen_outline)
|
||||
<< ",\"redrawsBrushPreview\":" << json_bool(value.redraws_brush_preview)
|
||||
<< ",\"invokesChangeCallback\":" << json_bool(value.invokes_change_callback)
|
||||
<< "}}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_share_file_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
@@ -9669,6 +9769,10 @@ int main(int argc, char** argv)
|
||||
return plan_quick_operation(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-quick-slider-preview") {
|
||||
return plan_quick_slider_preview(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-share-file") {
|
||||
return plan_share_file(argc, argv);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user