Add brush preset list executor bridge
This commit is contained in:
@@ -194,8 +194,9 @@ Known local toolchain state:
|
||||
- `src/legacy_brush_ui_services.*` is the current UI-shell bridge for brush
|
||||
color, texture, preset, stroke-refresh, brush texture-list, and stroke-control
|
||||
execution. `NodePanelBrushPreset` now consumes `pp_app_core` preset-list
|
||||
planning for add/select/move/remove/clear before directly mutating retained
|
||||
legacy child nodes. These paths stay on the `pp_app_core` contracts while
|
||||
planning and `BrushPresetListServices` execution for add/select/move/remove/
|
||||
clear before the retained legacy bridge mutates child nodes. These paths stay
|
||||
on the `pp_app_core` contracts while
|
||||
legacy `Brush`, `Canvas::I`, image load/save, `NodePanelBrush`,
|
||||
`NodePanelStroke`, quick/color refreshes, direct preset child-node mutation,
|
||||
and the temporary `NodePanelBrush` friend adapter remain tracked by
|
||||
|
||||
@@ -40,7 +40,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| DEBT-0020 | Open | Modernization | Document resize dialog state, selected-resolution planning, and execution dispatch now consume pure `pp_app_core` through `NodeDialogResize`, `App::dialog_resize`, `pano_cli plan-document-resize`, and the `DocumentResizeServices` boundary, and live resize shares `src/legacy_document_canvas_services.*` with canvas clear commands, but the shared live bridge still calls legacy `Canvas::resize`, updates the legacy app title, and clears legacy `ActionManager` history through the history bridge | 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 injected document/app services with no legacy resize adapter, title shim, or direct `ActionManager` history clearing |
|
||||
| DEBT-0021 | Open | Modernization | Layer rename planning/execution dispatch and layer panel operation planning/execution dispatch now consume pure `pp_app_core` through `App::dialog_layer_rename`, `App::init_sidebar` layer callbacks, `pano_cli plan-layer-rename`, `pano_cli plan-layer-operation`, `DocumentLayerRenameServices`, and `DocumentLayerOperationServices`, and the live execution adapters are centralized in `src/legacy_document_layer_services.*`, but that shared bridge still mutates legacy `Canvas` layer state, `NodeLayer`/`NodePanelLayer`, and `ActionManager` undo entries | Preserve existing UI/canvas behavior while document layer commands and undo history are extracted incrementally | `pp_app_core_document_layer_tests`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-layer-operation --kind add --layer-count 2 --index 1 --name Paint`; `ctest --preset desktop-fast --build-config Debug` | Layer command execution is owned by the document/app command boundary with legacy `Canvas`/UI nodes acting only as adapters or removed entirely |
|
||||
| DEBT-0022 | Open | Modernization | Animation panel frame command planning, panel action 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`, `pano_cli plan-animation-operation`, `pano_cli plan-animation-panel-action`, 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`; `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, and execution dispatch now consume pure `pp_app_core` through `App::init_sidebar`, `NodePanelBrush`, `NodePanelBrushPreset`, `NodePanelStroke`, restored/docked floating-panel callbacks, `pano_cli plan-brush-operation`, `pano_cli plan-brush-texture-list`, `pano_cli plan-brush-preset-list`, `pano_cli plan-brush-stroke-control`, `BrushUiServices`, `BrushTextureListServices`, and `BrushStrokeControlServices`, and live execution is centralized in `src/legacy_brush_ui_services.*` where possible, but preset-list execution still mutates legacy `NodePanelBrushPreset` child nodes directly while the bridge still mutates legacy `Brush`/`Canvas::I`, loads/saves legacy brush texture images, refreshes legacy quick/stroke/color widgets, and uses a temporary `NodePanelBrush` friend adapter to reach private list state | Preserve existing brush UI behavior while brush commands 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-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`; `ctest --preset desktop-fast --build-config Debug` | Brush color/texture/preset/stroke-settings, texture-list, preset-list, and stroke-control execution are owned by injected brush/app/asset/UI services with no legacy brush/canvas adapter, direct `NodePanelBrushPreset` child mutation, or `NodePanelBrush` friend access |
|
||||
| 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, and execution dispatch now consume pure `pp_app_core` through `App::init_sidebar`, `NodePanelBrush`, `NodePanelBrushPreset`, `NodePanelStroke`, restored/docked floating-panel callbacks, `pano_cli plan-brush-operation`, `pano_cli plan-brush-texture-list`, `pano_cli plan-brush-preset-list`, `pano_cli plan-brush-stroke-control`, `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 still mutates legacy `Brush`/`Canvas::I`, loads/saves legacy brush texture images, refreshes legacy quick/stroke/color widgets, and uses temporary `NodePanelBrush`/`NodePanelBrushPreset` friend adapters to reach private list state | Preserve existing brush UI behavior while brush commands 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-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`; `ctest --preset desktop-fast --build-config Debug` | Brush color/texture/preset/stroke-settings, texture-list, preset-list, and stroke-control execution are owned by injected brush/app/asset/UI services with no legacy brush/canvas adapter, direct `NodePanelBrushPreset` child mutation, 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.*`, 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-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 |
|
||||
|
||||
@@ -745,10 +745,11 @@ behind the renderer boundary.
|
||||
Brush preset-list add/select/move/remove/clear decisions now consume
|
||||
`pp_app_core` through `NodePanelBrushPreset` and
|
||||
`pano_cli plan-brush-preset-list`, so preset UI callbacks share tested
|
||||
headless index/selection planning before mutating legacy child nodes. The
|
||||
remaining direct `NodePanelBrushPreset` child-node execution, legacy
|
||||
`Brush` cloning, and preset save/reload behavior stay tracked under
|
||||
`DEBT-0023`.
|
||||
headless index/selection planning. Live preset-list execution now also
|
||||
dispatches through `BrushPresetListServices` and the shared app-core executor
|
||||
before the retained legacy bridge mutates child nodes. The remaining direct
|
||||
`NodePanelBrushPreset` child-node execution, legacy `Brush` cloning, friend
|
||||
adapter, and preset save/reload behavior stay tracked under `DEBT-0023`.
|
||||
`App::open_document` now routes through the app-core document-open executor and
|
||||
`src/legacy_document_open_services.*`, preserving ABR/PPBR import prompts,
|
||||
unsaved-project discard prompts, project open, layer refresh, title updates,
|
||||
|
||||
@@ -201,6 +201,23 @@ public:
|
||||
virtual void save_texture_list() = 0;
|
||||
};
|
||||
|
||||
class BrushPresetListServices {
|
||||
public:
|
||||
virtual ~BrushPresetListServices() = default;
|
||||
|
||||
virtual pp::foundation::Status add_current_brush_preset(int target_index) = 0;
|
||||
virtual void remove_brush_preset(
|
||||
int current_index,
|
||||
int target_index,
|
||||
bool selects_target,
|
||||
bool clears_selection) = 0;
|
||||
virtual void move_brush_preset(int from_index, int to_index) = 0;
|
||||
virtual void select_brush_preset(int index, bool notify_brush_changed) = 0;
|
||||
virtual void clear_brush_presets(bool clears_selection) = 0;
|
||||
virtual void update_preset_empty_notification() = 0;
|
||||
virtual void save_preset_list() = 0;
|
||||
};
|
||||
|
||||
class BrushStrokeControlServices {
|
||||
public:
|
||||
virtual ~BrushStrokeControlServices() = default;
|
||||
@@ -763,4 +780,83 @@ public:
|
||||
return pp::foundation::Status::invalid_argument("unknown brush texture list operation");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_brush_preset_list_plan(
|
||||
const BrushPresetListPlan& plan,
|
||||
BrushPresetListServices& services)
|
||||
{
|
||||
switch (plan.operation) {
|
||||
case BrushPresetListOperation::add_current_brush:
|
||||
{
|
||||
if (plan.item_count < 0 || plan.target_index != plan.item_count) {
|
||||
return pp::foundation::Status::out_of_range("brush preset add plan has invalid target");
|
||||
}
|
||||
|
||||
const auto add_status = services.add_current_brush_preset(plan.target_index);
|
||||
if (!add_status.ok()) {
|
||||
return add_status;
|
||||
}
|
||||
if (plan.updates_empty_notification) {
|
||||
services.update_preset_empty_notification();
|
||||
}
|
||||
if (plan.saves_list) {
|
||||
services.save_preset_list();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
case BrushPresetListOperation::remove_preset:
|
||||
if (plan.item_count <= 0 || plan.current_index < 0 || plan.current_index >= plan.item_count) {
|
||||
return pp::foundation::Status::out_of_range("brush preset remove plan has invalid selection");
|
||||
}
|
||||
if (plan.selects_target && (plan.target_index < 0 || plan.target_index >= plan.item_count - 1)) {
|
||||
return pp::foundation::Status::out_of_range("brush preset remove plan has invalid target");
|
||||
}
|
||||
services.remove_brush_preset(
|
||||
plan.current_index,
|
||||
plan.target_index,
|
||||
plan.selects_target,
|
||||
plan.clears_selection);
|
||||
if (plan.updates_empty_notification) {
|
||||
services.update_preset_empty_notification();
|
||||
}
|
||||
if (plan.saves_list) {
|
||||
services.save_preset_list();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case BrushPresetListOperation::move_preset:
|
||||
if (plan.item_count <= 0 || plan.current_index < 0 || plan.current_index >= plan.item_count
|
||||
|| plan.target_index < 0 || plan.target_index >= plan.item_count) {
|
||||
return pp::foundation::Status::out_of_range("brush preset move plan has invalid indices");
|
||||
}
|
||||
services.move_brush_preset(plan.current_index, plan.target_index);
|
||||
if (plan.saves_list) {
|
||||
services.save_preset_list();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case BrushPresetListOperation::select_preset:
|
||||
if (plan.item_count <= 0 || plan.target_index < 0 || plan.target_index >= plan.item_count) {
|
||||
return pp::foundation::Status::out_of_range("brush preset select plan has invalid target");
|
||||
}
|
||||
services.select_brush_preset(plan.target_index, plan.notifies_brush_changed);
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case BrushPresetListOperation::clear_presets:
|
||||
if (plan.item_count < 0) {
|
||||
return pp::foundation::Status::out_of_range("brush preset clear plan has invalid item count");
|
||||
}
|
||||
services.clear_brush_presets(plan.clears_selection);
|
||||
if (plan.updates_empty_notification) {
|
||||
services.update_preset_empty_notification();
|
||||
}
|
||||
if (plan.saves_list) {
|
||||
services.save_preset_list();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown brush preset list operation");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
|
||||
@@ -407,70 +407,111 @@ Node* NodePanelBrushPreset::clone_instantiate() const
|
||||
return new NodePanelBrushPreset();
|
||||
}
|
||||
|
||||
void NodePanelBrushPreset::execute_preset_list_plan(const pp::app::BrushPresetListPlan& plan)
|
||||
{
|
||||
switch (plan.operation)
|
||||
namespace pp::panopainter {
|
||||
|
||||
class LegacyBrushPresetListServices final : public pp::app::BrushPresetListServices {
|
||||
public:
|
||||
explicit LegacyBrushPresetListServices(NodePanelBrushPreset& owner)
|
||||
: owner_(owner)
|
||||
{
|
||||
case pp::app::BrushPresetListOperation::add_current_brush:
|
||||
if (!Canvas::I || !Canvas::I->m_current_brush)
|
||||
return;
|
||||
for (auto p : s_panels)
|
||||
p->add_brush(std::make_shared<Brush>(*Canvas::I->m_current_brush));
|
||||
break;
|
||||
}
|
||||
|
||||
case pp::app::BrushPresetListOperation::move_preset:
|
||||
for (auto p : s_panels)
|
||||
{
|
||||
if (plan.current_index >= 0 && plan.current_index < static_cast<int>(p->m_container->m_children.size()))
|
||||
p->m_container->move_child(p->m_container->m_children[plan.current_index].get(), plan.target_index);
|
||||
pp::foundation::Status add_current_brush_preset(int target_index) override
|
||||
{
|
||||
if (target_index < 0) {
|
||||
return pp::foundation::Status::out_of_range("brush preset add target is invalid");
|
||||
}
|
||||
if (!Canvas::I || !Canvas::I->m_current_brush) {
|
||||
return pp::foundation::Status::invalid_argument("current brush must be available to add a preset");
|
||||
}
|
||||
break;
|
||||
|
||||
case pp::app::BrushPresetListOperation::remove_preset:
|
||||
for (auto p : s_panels)
|
||||
{
|
||||
if (plan.current_index < 0 || plan.current_index >= static_cast<int>(p->m_container->m_children.size()))
|
||||
for (auto p : NodePanelBrushPreset::s_panels) {
|
||||
p->add_brush(std::make_shared<Brush>(*Canvas::I->m_current_brush));
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
void remove_brush_preset(
|
||||
int current_index,
|
||||
int target_index,
|
||||
bool selects_target,
|
||||
bool clears_selection) override
|
||||
{
|
||||
for (auto p : NodePanelBrushPreset::s_panels) {
|
||||
if (current_index < 0 || current_index >= static_cast<int>(p->m_container->m_children.size())) {
|
||||
continue;
|
||||
bool new_current = !p->m_current || p->m_container->get_child_index(p->m_current) == plan.current_index;
|
||||
p->m_container->m_children[plan.current_index]->destroy();
|
||||
if (plan.clears_selection)
|
||||
{
|
||||
p->m_current = nullptr;
|
||||
}
|
||||
else if (new_current && plan.selects_target)
|
||||
{
|
||||
p->m_current = static_cast<NodeBrushPresetItem*>(p->m_container->m_children[plan.target_index].get());
|
||||
|
||||
const bool new_current = !p->m_current || p->m_container->get_child_index(p->m_current) == current_index;
|
||||
p->m_container->m_children[current_index]->destroy();
|
||||
if (clears_selection) {
|
||||
p->m_current = nullptr;
|
||||
} else if (new_current && selects_target) {
|
||||
p->m_current = static_cast<NodeBrushPresetItem*>(p->m_container->m_children[target_index].get());
|
||||
p->m_current->m_selected = true;
|
||||
}
|
||||
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case pp::app::BrushPresetListOperation::select_preset:
|
||||
for (auto p : s_panels)
|
||||
{
|
||||
if (p->m_current)
|
||||
void move_brush_preset(int from_index, int to_index) override
|
||||
{
|
||||
for (auto p : NodePanelBrushPreset::s_panels) {
|
||||
if (from_index >= 0 && from_index < static_cast<int>(p->m_container->m_children.size())) {
|
||||
p->m_container->move_child(p->m_container->m_children[from_index].get(), to_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void select_brush_preset(int index, bool notify_brush_changed) override
|
||||
{
|
||||
for (auto p : NodePanelBrushPreset::s_panels) {
|
||||
if (p->m_current) {
|
||||
p->m_current->m_selected = false;
|
||||
p->m_current = static_cast<NodeBrushPresetItem*>(p->m_container->get_child_at(plan.target_index));
|
||||
}
|
||||
p->m_current = static_cast<NodeBrushPresetItem*>(p->m_container->get_child_at(index));
|
||||
p->m_current->m_selected = true;
|
||||
p->m_interacted = true;
|
||||
}
|
||||
if (plan.notifies_brush_changed && on_brush_changed)
|
||||
on_brush_changed(this, m_current->m_brush);
|
||||
break;
|
||||
|
||||
case pp::app::BrushPresetListOperation::clear_presets:
|
||||
for (auto p : s_panels)
|
||||
{
|
||||
p->m_container->remove_all_children();
|
||||
p->m_current = nullptr;
|
||||
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
|
||||
if (notify_brush_changed && owner_.on_brush_changed) {
|
||||
owner_.on_brush_changed(&owner_, owner_.m_current->m_brush);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (plan.saves_list)
|
||||
save();
|
||||
void clear_brush_presets(bool clears_selection) override
|
||||
{
|
||||
for (auto p : NodePanelBrushPreset::s_panels) {
|
||||
p->m_container->remove_all_children();
|
||||
if (clears_selection) {
|
||||
p->m_current = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_preset_empty_notification() override
|
||||
{
|
||||
for (auto p : NodePanelBrushPreset::s_panels) {
|
||||
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void save_preset_list() override
|
||||
{
|
||||
owner_.save();
|
||||
}
|
||||
|
||||
private:
|
||||
NodePanelBrushPreset& owner_;
|
||||
};
|
||||
|
||||
} // namespace pp::panopainter
|
||||
|
||||
void NodePanelBrushPreset::execute_preset_list_plan(const pp::app::BrushPresetListPlan& plan)
|
||||
{
|
||||
pp::panopainter::LegacyBrushPresetListServices services(*this);
|
||||
const auto status = pp::app::execute_brush_preset_list_plan(plan, services);
|
||||
if (!status.ok()) {
|
||||
LOG("Brush preset list action failed: %s", status.message);
|
||||
}
|
||||
}
|
||||
|
||||
void NodePanelBrushPreset::init()
|
||||
|
||||
@@ -15,6 +15,7 @@ struct BrushPresetListPlan;
|
||||
}
|
||||
namespace pp::panopainter {
|
||||
class LegacyBrushTextureListServices;
|
||||
class LegacyBrushPresetListServices;
|
||||
}
|
||||
|
||||
class NodeButtonBrush : public NodeButtonCustom, public Serializer::Type
|
||||
@@ -88,6 +89,8 @@ public:
|
||||
|
||||
class NodePanelBrushPreset : public Node
|
||||
{
|
||||
friend class pp::panopainter::LegacyBrushPresetListServices;
|
||||
|
||||
static std::vector<NodePanelBrushPreset*> s_panels;
|
||||
bool m_interacted = false;
|
||||
NodeBrushPresetItem* m_current = nullptr;
|
||||
|
||||
@@ -135,6 +135,87 @@ public:
|
||||
std::string call_order;
|
||||
};
|
||||
|
||||
class FakeBrushPresetListServices final : public pp::app::BrushPresetListServices {
|
||||
public:
|
||||
pp::foundation::Status add_current_brush_preset(int target_index) override
|
||||
{
|
||||
if (fail_add) {
|
||||
call_order += "add-failed;";
|
||||
return pp::foundation::Status::invalid_argument("fake preset add failure");
|
||||
}
|
||||
|
||||
adds += 1;
|
||||
last_target_index = target_index;
|
||||
call_order += "add;";
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
void remove_brush_preset(
|
||||
int current_index,
|
||||
int target_index,
|
||||
bool selects_target,
|
||||
bool clears_selection) override
|
||||
{
|
||||
removes += 1;
|
||||
last_index = current_index;
|
||||
last_target_index = target_index;
|
||||
last_selects_target = selects_target;
|
||||
last_remove_clears_selection = clears_selection;
|
||||
call_order += "remove;";
|
||||
}
|
||||
|
||||
void move_brush_preset(int from_index, int to_index) override
|
||||
{
|
||||
moves += 1;
|
||||
last_index = from_index;
|
||||
last_target_index = to_index;
|
||||
call_order += "move;";
|
||||
}
|
||||
|
||||
void select_brush_preset(int index, bool notify_brush_changed) override
|
||||
{
|
||||
selections += 1;
|
||||
last_target_index = index;
|
||||
last_notifies_brush_changed = notify_brush_changed;
|
||||
call_order += "select;";
|
||||
}
|
||||
|
||||
void clear_brush_presets(bool clears_selection) override
|
||||
{
|
||||
clears += 1;
|
||||
last_clear_clears_selection = clears_selection;
|
||||
call_order += "clear;";
|
||||
}
|
||||
|
||||
void update_preset_empty_notification() override
|
||||
{
|
||||
notification_updates += 1;
|
||||
call_order += "notification;";
|
||||
}
|
||||
|
||||
void save_preset_list() override
|
||||
{
|
||||
saves += 1;
|
||||
call_order += "save;";
|
||||
}
|
||||
|
||||
int adds = 0;
|
||||
int removes = 0;
|
||||
int moves = 0;
|
||||
int selections = 0;
|
||||
int clears = 0;
|
||||
int notification_updates = 0;
|
||||
int saves = 0;
|
||||
int last_index = -1;
|
||||
int last_target_index = -1;
|
||||
bool last_selects_target = false;
|
||||
bool last_remove_clears_selection = false;
|
||||
bool last_clear_clears_selection = false;
|
||||
bool last_notifies_brush_changed = false;
|
||||
bool fail_add = false;
|
||||
std::string call_order;
|
||||
};
|
||||
|
||||
class FakeBrushStrokeControlServices final : public pp::app::BrushStrokeControlServices {
|
||||
public:
|
||||
void set_float_setting(pp::app::BrushStrokeFloatSetting setting, float value) override
|
||||
@@ -592,6 +673,68 @@ void texture_list_executor_dispatches_and_preserves_failure(pp::tests::Harness&
|
||||
PP_EXPECT(harness, failing_services.call_order == "add-failed;");
|
||||
}
|
||||
|
||||
void preset_list_executor_dispatches_and_preserves_failure(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeBrushPresetListServices services;
|
||||
|
||||
const auto add = pp::app::plan_brush_preset_list_add(2, true);
|
||||
PP_EXPECT(harness, add);
|
||||
if (add) {
|
||||
PP_EXPECT(harness, pp::app::execute_brush_preset_list_plan(add.value(), services).ok());
|
||||
}
|
||||
|
||||
const auto select = pp::app::plan_brush_preset_list_select(3, 1);
|
||||
PP_EXPECT(harness, select);
|
||||
if (select) {
|
||||
PP_EXPECT(harness, pp::app::execute_brush_preset_list_plan(select.value(), services).ok());
|
||||
}
|
||||
|
||||
const auto move = pp::app::plan_brush_preset_list_move(3, 2, -1);
|
||||
PP_EXPECT(harness, move);
|
||||
if (move) {
|
||||
PP_EXPECT(harness, pp::app::execute_brush_preset_list_plan(move.value(), services).ok());
|
||||
}
|
||||
|
||||
const auto remove = pp::app::plan_brush_preset_list_remove(3, 1);
|
||||
PP_EXPECT(harness, remove);
|
||||
if (remove) {
|
||||
PP_EXPECT(harness, pp::app::execute_brush_preset_list_plan(remove.value(), services).ok());
|
||||
}
|
||||
|
||||
const auto clear = pp::app::plan_brush_preset_list_clear(0);
|
||||
PP_EXPECT(harness, clear);
|
||||
if (clear) {
|
||||
PP_EXPECT(harness, pp::app::execute_brush_preset_list_plan(clear.value(), services).ok());
|
||||
}
|
||||
|
||||
PP_EXPECT(harness, services.adds == 1);
|
||||
PP_EXPECT(harness, services.selections == 1);
|
||||
PP_EXPECT(harness, services.moves == 1);
|
||||
PP_EXPECT(harness, services.removes == 1);
|
||||
PP_EXPECT(harness, services.clears == 1);
|
||||
PP_EXPECT(harness, services.notification_updates == 3);
|
||||
PP_EXPECT(harness, services.saves == 4);
|
||||
PP_EXPECT(harness, services.last_index == 1);
|
||||
PP_EXPECT(harness, services.last_target_index == 1);
|
||||
PP_EXPECT(harness, services.last_selects_target);
|
||||
PP_EXPECT(harness, !services.last_remove_clears_selection);
|
||||
PP_EXPECT(harness, services.last_clear_clears_selection);
|
||||
PP_EXPECT(harness, services.last_notifies_brush_changed);
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
services.call_order == "add;notification;save;select;move;save;remove;notification;save;clear;"
|
||||
"notification;save;");
|
||||
|
||||
FakeBrushPresetListServices failing_services;
|
||||
failing_services.fail_add = true;
|
||||
PP_EXPECT(harness, add);
|
||||
if (add) {
|
||||
PP_EXPECT(harness, !pp::app::execute_brush_preset_list_plan(add.value(), failing_services).ok());
|
||||
}
|
||||
PP_EXPECT(harness, failing_services.saves == 0);
|
||||
PP_EXPECT(harness, failing_services.call_order == "add-failed;");
|
||||
}
|
||||
|
||||
void stroke_control_executor_dispatches_and_rejects_bad_payloads(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeBrushStrokeControlServices services;
|
||||
@@ -681,6 +824,22 @@ void executor_rejects_invalid_plan_payloads(pp::tests::Harness& harness)
|
||||
move.target_index = 1;
|
||||
PP_EXPECT(harness, !pp::app::execute_brush_texture_list_plan(move, list_services).ok());
|
||||
PP_EXPECT(harness, list_services.call_order.empty());
|
||||
|
||||
FakeBrushPresetListServices preset_services;
|
||||
pp::app::BrushPresetListPlan remove_preset;
|
||||
remove_preset.operation = pp::app::BrushPresetListOperation::remove_preset;
|
||||
remove_preset.item_count = 1;
|
||||
remove_preset.current_index = 0;
|
||||
remove_preset.target_index = 1;
|
||||
remove_preset.selects_target = true;
|
||||
PP_EXPECT(harness, !pp::app::execute_brush_preset_list_plan(remove_preset, preset_services).ok());
|
||||
|
||||
pp::app::BrushPresetListPlan select_preset;
|
||||
select_preset.operation = pp::app::BrushPresetListOperation::select_preset;
|
||||
select_preset.item_count = 1;
|
||||
select_preset.target_index = 1;
|
||||
PP_EXPECT(harness, !pp::app::execute_brush_preset_list_plan(select_preset, preset_services).ok());
|
||||
PP_EXPECT(harness, preset_services.call_order.empty());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -701,6 +860,7 @@ int main()
|
||||
harness.run("executor dispatches texture and preset", executor_dispatches_texture_and_preset);
|
||||
harness.run("executor dispatches stroke refresh only", executor_dispatches_stroke_refresh_only);
|
||||
harness.run("texture list executor dispatches and preserves failure", texture_list_executor_dispatches_and_preserves_failure);
|
||||
harness.run("preset list executor dispatches and preserves failure", preset_list_executor_dispatches_and_preserves_failure);
|
||||
harness.run("stroke control executor dispatches and rejects bad payloads", stroke_control_executor_dispatches_and_rejects_bad_payloads);
|
||||
harness.run("executor rejects invalid plan payloads", executor_rejects_invalid_plan_payloads);
|
||||
return harness.finish();
|
||||
|
||||
Reference in New Issue
Block a user