Add brush UI service boundary
This commit is contained in:
@@ -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, but the live adapter still calls legacy `Canvas::resize`, updates the legacy app title, and clears legacy `ActionManager` history | 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 and layer panel operation planning now consume pure `pp_app_core` through `App::dialog_layer_rename`, `App::init_sidebar` layer callbacks, `pano_cli plan-layer-rename`, and `pano_cli plan-layer-operation`, but live execution still mutates legacy `Canvas` layer state, `NodeLayer`/`NodePanelLayer`, and `ActionManager` undo entries directly | 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 now consumes pure `pp_app_core` through `NodePanelAnimation` and `pano_cli plan-animation-operation`, and `pp_legacy_ui_core` temporarily links `pp_app_core`, but live execution still mutates legacy `Canvas`/`Layer` frame state and animation playback state directly | 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 next --total-duration 5 --current-frame 4`; `ctest --preset desktop-fast --build-config Debug` | Animation frame/timeline execution is owned by the document/app command boundary with legacy `Canvas`/`Layer`/UI nodes acting only as adapters or removed entirely |
|
||||
| DEBT-0023 | Open | Modernization | Brush/color/preset UI planning now consumes pure `pp_app_core` through `App::init_sidebar`, restored/docked floating-panel callbacks, and `pano_cli plan-brush-operation`, but live execution still mutates legacy `Brush`, calls legacy brush texture loading, and refreshes legacy quick/stroke/color widgets directly | Preserve existing brush UI behavior while brush commands move toward a brush/app command boundary and asset-managed texture 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`; `ctest --preset desktop-fast --build-config Debug` | Brush color/texture/preset execution is owned by a brush/app command boundary with legacy `Brush`/UI nodes acting only as adapters or removed entirely |
|
||||
| DEBT-0023 | Open | Modernization | Brush/color/preset/stroke-settings UI planning and execution dispatch now consume pure `pp_app_core` through `App::init_sidebar`, restored/docked floating-panel callbacks, `pano_cli plan-brush-operation`, and the `BrushUiServices` boundary, but the live adapter still mutates legacy `Brush`, calls legacy brush texture loading, and refreshes legacy quick/stroke/color widgets | Preserve existing brush UI behavior while brush commands move toward a brush/app command boundary and asset-managed texture 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`; `ctest --preset desktop-fast --build-config Debug` | Brush color/texture/preset/stroke-settings execution is owned by injected brush/app/asset/UI services with no legacy brush adapter |
|
||||
| DEBT-0024 | Open | Modernization | Grid/heightmap/lightmap UI planning now consumes pure `pp_app_core` through `NodePanelGrid` and `pano_cli plan-grid-operation`, but live execution still performs legacy image loading, OpenGL texture updates, nanort lightmap baking, progress UI, and `Canvas::draw_objects` commit directly | 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 now consumes pure `pp_app_core` through `NodePanelQuick` and `pano_cli plan-quick-operation`, but live execution still mutates legacy quick UI widgets, `Brush` previews, color picker popup state, and preset popup state directly | 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 app/brush/UI services with `NodePanelQuick` acting only as UI adapter |
|
||||
| DEBT-0026 | Open | Modernization | Toolbar history command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `NodeCanvas`, `pano_cli plan-history-operation`, and the `HistoryUiServices` boundary, but the live adapter 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 |
|
||||
|
||||
@@ -503,7 +503,8 @@ before legacy `Canvas`/`Layer` frame execution continues.
|
||||
`pano_cli plan-brush-operation` exposes app-core planning for brush color
|
||||
changes, tip/pattern/dual texture changes, preset brush replacement, and stroke
|
||||
settings refreshes used by the live brush, quick, color, and floating panel
|
||||
callbacks before legacy `Brush` mutation and resource loading continue.
|
||||
callbacks. Brush UI execution now dispatches through `BrushUiServices` before
|
||||
the legacy `Brush`/panel adapter mutates brush state or loads brush resources.
|
||||
`pano_cli plan-canvas-tool` exposes app-core planning for draw/erase/line,
|
||||
camera, grid, copy, cut, fill, mask, flood-fill, pick, and touch-lock toolbar
|
||||
commands before legacy `Canvas` mode, pen picking, touch-lock, and transform
|
||||
@@ -1205,7 +1206,9 @@ Results:
|
||||
expose live animation-panel planning as JSON automation.
|
||||
- `pp_app_core_brush_ui_tests` passed, covering brush color channel validation,
|
||||
invalid color rejection, texture-path validation, preset-brush availability,
|
||||
preserve-current-color intent, and stroke-settings refresh intent.
|
||||
preserve-current-color intent, stroke-settings refresh intent, service
|
||||
dispatch ordering, texture/preset execution payloads, and invalid execution
|
||||
payload rejection.
|
||||
- `pano_cli_plan_brush_operation_color_smoke`,
|
||||
`pano_cli_plan_brush_operation_texture_smoke`,
|
||||
`pano_cli_plan_brush_operation_preset_smoke`,
|
||||
|
||||
@@ -37,6 +37,16 @@ struct BrushUiPlan {
|
||||
bool update_brush_ui = false;
|
||||
};
|
||||
|
||||
class BrushUiServices {
|
||||
public:
|
||||
virtual ~BrushUiServices() = default;
|
||||
|
||||
virtual void set_tip_color(float r, float g, float b, float a) = 0;
|
||||
virtual void set_texture(BrushUiTextureSlot slot, std::string_view path, std::string_view thumbnail_path) = 0;
|
||||
virtual void replace_brush_from_preset(bool preserve_existing_color, bool load_resources) = 0;
|
||||
virtual void refresh_brush_ui(bool update_color_ui, bool update_brush_ui) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_brush_ui_color_channel(float value) noexcept
|
||||
{
|
||||
if (!std::isfinite(value) || value < 0.0F || value > 1.0F) {
|
||||
@@ -119,4 +129,43 @@ struct BrushUiPlan {
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_brush_ui_plan(
|
||||
const BrushUiPlan& plan,
|
||||
BrushUiServices& services)
|
||||
{
|
||||
switch (plan.operation) {
|
||||
case BrushUiOperation::set_tip_color:
|
||||
{
|
||||
for (const auto value : { plan.r, plan.g, plan.b, plan.a }) {
|
||||
const auto channel_status = validate_brush_ui_color_channel(value);
|
||||
if (!channel_status.ok()) {
|
||||
return channel_status;
|
||||
}
|
||||
}
|
||||
services.set_tip_color(plan.r, plan.g, plan.b, plan.a);
|
||||
services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
case BrushUiOperation::set_texture:
|
||||
if (plan.path.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("brush texture path must not be empty");
|
||||
}
|
||||
services.set_texture(plan.texture_slot, plan.path, plan.thumbnail_path);
|
||||
services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case BrushUiOperation::replace_brush_from_preset:
|
||||
services.replace_brush_from_preset(plan.preserves_existing_color, plan.loads_brush_resources);
|
||||
services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case BrushUiOperation::stroke_settings_changed:
|
||||
services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown brush UI operation");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
|
||||
@@ -29,22 +29,91 @@
|
||||
|
||||
namespace {
|
||||
|
||||
class LegacyBrushUiServices final : public pp::app::BrushUiServices {
|
||||
public:
|
||||
LegacyBrushUiServices(
|
||||
App& app,
|
||||
bool update_quick = false,
|
||||
bool update_color_panel = false,
|
||||
const std::shared_ptr<Brush>& preset_brush = nullptr) noexcept
|
||||
: app_(app)
|
||||
, update_quick_(update_quick)
|
||||
, update_color_panel_(update_color_panel)
|
||||
, preset_brush_(preset_brush)
|
||||
{
|
||||
}
|
||||
|
||||
void set_tip_color(float r, float g, float b, float a) override
|
||||
{
|
||||
if (!Canvas::I || !Canvas::I->m_current_brush)
|
||||
return;
|
||||
|
||||
Canvas::I->m_current_brush->m_tip_color = glm::vec4(r, g, b, a);
|
||||
if (update_quick_ && app_.quick)
|
||||
app_.quick->set_color(Canvas::I->m_current_brush->m_tip_color);
|
||||
if (update_color_panel_ && app_.color)
|
||||
app_.color->set_color(Canvas::I->m_current_brush->m_tip_color);
|
||||
}
|
||||
|
||||
void set_texture(
|
||||
pp::app::BrushUiTextureSlot slot,
|
||||
std::string_view path,
|
||||
std::string_view thumbnail_path) override
|
||||
{
|
||||
if (!Canvas::I || !Canvas::I->m_current_brush)
|
||||
return;
|
||||
|
||||
const std::string texture_path(path);
|
||||
const std::string thumbnail(thumbnail_path);
|
||||
switch (slot)
|
||||
{
|
||||
case pp::app::BrushUiTextureSlot::tip:
|
||||
Canvas::I->m_current_brush->load_tip(texture_path, thumbnail);
|
||||
break;
|
||||
case pp::app::BrushUiTextureSlot::pattern:
|
||||
Canvas::I->m_current_brush->load_pattern(texture_path, thumbnail);
|
||||
break;
|
||||
case pp::app::BrushUiTextureSlot::dual:
|
||||
Canvas::I->m_current_brush->load_dual(texture_path, thumbnail);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void replace_brush_from_preset(bool preserve_existing_color, bool load_resources) override
|
||||
{
|
||||
if (!Canvas::I || !Canvas::I->m_current_brush || !preset_brush_)
|
||||
return;
|
||||
|
||||
const auto color = Canvas::I->m_current_brush->m_tip_color;
|
||||
*Canvas::I->m_current_brush = *preset_brush_;
|
||||
if (preserve_existing_color)
|
||||
Canvas::I->m_current_brush->m_tip_color = color;
|
||||
if (load_resources)
|
||||
Canvas::I->m_current_brush->load();
|
||||
}
|
||||
|
||||
void refresh_brush_ui(bool update_color_ui, bool update_brush_ui) override
|
||||
{
|
||||
app_.brush_update(update_color_ui, update_brush_ui);
|
||||
}
|
||||
|
||||
private:
|
||||
App& app_;
|
||||
bool update_quick_ = false;
|
||||
bool update_color_panel_ = false;
|
||||
std::shared_ptr<Brush> preset_brush_;
|
||||
};
|
||||
|
||||
bool apply_brush_color_plan(App& app, glm::vec4 color, bool update_quick, bool update_color_panel)
|
||||
{
|
||||
const auto plan = pp::app::plan_brush_ui_color(color.r, color.g, color.b, color.a);
|
||||
if (!plan)
|
||||
return false;
|
||||
Canvas::I->m_current_brush->m_tip_color = glm::vec4(
|
||||
plan.value().r,
|
||||
plan.value().g,
|
||||
plan.value().b,
|
||||
plan.value().a);
|
||||
if (update_quick)
|
||||
app.quick->set_color(Canvas::I->m_current_brush->m_tip_color);
|
||||
if (update_color_panel)
|
||||
app.color->set_color(Canvas::I->m_current_brush->m_tip_color);
|
||||
app.brush_update(plan.value().update_color_ui, plan.value().update_brush_ui);
|
||||
return true;
|
||||
LegacyBrushUiServices services(app, update_quick, update_color_panel);
|
||||
const auto status = pp::app::execute_brush_ui_plan(plan.value(), services);
|
||||
if (!status.ok())
|
||||
LOG("Brush color action failed: %s", status.message);
|
||||
return status.ok();
|
||||
}
|
||||
|
||||
bool apply_brush_texture_plan(pp::app::BrushUiTextureSlot slot, const std::string& path, const std::string& thumb)
|
||||
@@ -52,20 +121,11 @@ bool apply_brush_texture_plan(pp::app::BrushUiTextureSlot slot, const std::strin
|
||||
const auto plan = pp::app::plan_brush_ui_texture(slot, path, thumb);
|
||||
if (!plan)
|
||||
return false;
|
||||
switch (plan.value().texture_slot)
|
||||
{
|
||||
case pp::app::BrushUiTextureSlot::tip:
|
||||
Canvas::I->m_current_brush->load_tip(plan.value().path, plan.value().thumbnail_path);
|
||||
break;
|
||||
case pp::app::BrushUiTextureSlot::pattern:
|
||||
Canvas::I->m_current_brush->load_pattern(plan.value().path, plan.value().thumbnail_path);
|
||||
break;
|
||||
case pp::app::BrushUiTextureSlot::dual:
|
||||
Canvas::I->m_current_brush->load_dual(plan.value().path, plan.value().thumbnail_path);
|
||||
break;
|
||||
}
|
||||
App::I->brush_update(plan.value().update_color_ui, plan.value().update_brush_ui);
|
||||
return true;
|
||||
LegacyBrushUiServices services(*App::I);
|
||||
const auto status = pp::app::execute_brush_ui_plan(plan.value(), services);
|
||||
if (!status.ok())
|
||||
LOG("Brush texture action failed: %s", status.message);
|
||||
return status.ok();
|
||||
}
|
||||
|
||||
bool apply_brush_preset_plan(App& app, const std::shared_ptr<Brush>& brush)
|
||||
@@ -73,14 +133,11 @@ bool apply_brush_preset_plan(App& app, const std::shared_ptr<Brush>& brush)
|
||||
const auto plan = pp::app::plan_brush_ui_preset_replace(static_cast<bool>(brush));
|
||||
if (!plan)
|
||||
return false;
|
||||
auto color = Canvas::I->m_current_brush->m_tip_color;
|
||||
*Canvas::I->m_current_brush = *brush;
|
||||
if (plan.value().preserves_existing_color)
|
||||
Canvas::I->m_current_brush->m_tip_color = color;
|
||||
if (plan.value().loads_brush_resources)
|
||||
Canvas::I->m_current_brush->load();
|
||||
app.brush_update(plan.value().update_color_ui, plan.value().update_brush_ui);
|
||||
return true;
|
||||
LegacyBrushUiServices services(app, false, false, brush);
|
||||
const auto status = pp::app::execute_brush_ui_plan(plan.value(), services);
|
||||
if (!status.ok())
|
||||
LOG("Brush preset action failed: %s", status.message);
|
||||
return status.ok();
|
||||
}
|
||||
|
||||
bool apply_document_export_menu_plan(App& app, pp::app::DocumentExportMenuKind kind)
|
||||
@@ -750,7 +807,10 @@ void App::init_sidebar()
|
||||
};
|
||||
stroke->on_stroke_change = [this](Node*) {
|
||||
const auto plan = pp::app::plan_brush_ui_stroke_settings_changed();
|
||||
brush_update(plan.update_color_ui, plan.update_brush_ui);
|
||||
LegacyBrushUiServices services(*this);
|
||||
const auto status = pp::app::execute_brush_ui_plan(plan, services);
|
||||
if (!status.ok())
|
||||
LOG("Brush stroke settings action failed: %s", status.message);
|
||||
};
|
||||
|
||||
quick->on_color_change = [this](Node*, glm::vec3 c) {
|
||||
|
||||
@@ -2,9 +2,69 @@
|
||||
#include "test_harness.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
class FakeBrushUiServices final : public pp::app::BrushUiServices {
|
||||
public:
|
||||
void set_tip_color(float r, float g, float b, float a) override
|
||||
{
|
||||
color_sets += 1;
|
||||
last_r = r;
|
||||
last_g = g;
|
||||
last_b = b;
|
||||
last_a = a;
|
||||
call_order += "color;";
|
||||
}
|
||||
|
||||
void set_texture(
|
||||
pp::app::BrushUiTextureSlot slot,
|
||||
std::string_view path,
|
||||
std::string_view thumbnail_path) override
|
||||
{
|
||||
texture_sets += 1;
|
||||
last_slot = slot;
|
||||
last_path = std::string(path);
|
||||
last_thumbnail_path = std::string(thumbnail_path);
|
||||
call_order += "texture;";
|
||||
}
|
||||
|
||||
void replace_brush_from_preset(bool preserve_existing_color, bool load_resources) override
|
||||
{
|
||||
preset_replacements += 1;
|
||||
preserved_color = preserve_existing_color;
|
||||
loaded_resources = load_resources;
|
||||
call_order += "preset;";
|
||||
}
|
||||
|
||||
void refresh_brush_ui(bool update_color_ui, bool update_brush_ui) override
|
||||
{
|
||||
refreshes += 1;
|
||||
last_update_color_ui = update_color_ui;
|
||||
last_update_brush_ui = update_brush_ui;
|
||||
call_order += "refresh;";
|
||||
}
|
||||
|
||||
int color_sets = 0;
|
||||
int texture_sets = 0;
|
||||
int preset_replacements = 0;
|
||||
int refreshes = 0;
|
||||
float last_r = 0.0F;
|
||||
float last_g = 0.0F;
|
||||
float last_b = 0.0F;
|
||||
float last_a = 0.0F;
|
||||
pp::app::BrushUiTextureSlot last_slot = pp::app::BrushUiTextureSlot::tip;
|
||||
std::string last_path;
|
||||
std::string last_thumbnail_path;
|
||||
bool preserved_color = false;
|
||||
bool loaded_resources = false;
|
||||
bool last_update_color_ui = false;
|
||||
bool last_update_brush_ui = false;
|
||||
std::string call_order;
|
||||
};
|
||||
|
||||
void color_plan_validates_all_channels(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_brush_ui_color(0.25F, 0.5F, 0.75F, 1.0F);
|
||||
@@ -72,6 +132,94 @@ void stroke_settings_plan_updates_brush_preview(pp::tests::Harness& harness)
|
||||
PP_EXPECT(harness, !plan.loads_brush_resources);
|
||||
}
|
||||
|
||||
void executor_dispatches_color_and_refresh(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeBrushUiServices services;
|
||||
const auto plan = pp::app::plan_brush_ui_color(0.25F, 0.5F, 0.75F, 1.0F);
|
||||
PP_EXPECT(harness, plan);
|
||||
if (plan) {
|
||||
PP_EXPECT(harness, pp::app::execute_brush_ui_plan(plan.value(), services).ok());
|
||||
}
|
||||
|
||||
PP_EXPECT(harness, services.color_sets == 1);
|
||||
PP_EXPECT(harness, services.last_r == 0.25F);
|
||||
PP_EXPECT(harness, services.last_g == 0.5F);
|
||||
PP_EXPECT(harness, services.last_b == 0.75F);
|
||||
PP_EXPECT(harness, services.last_a == 1.0F);
|
||||
PP_EXPECT(harness, services.refreshes == 1);
|
||||
PP_EXPECT(harness, services.last_update_color_ui);
|
||||
PP_EXPECT(harness, !services.last_update_brush_ui);
|
||||
PP_EXPECT(harness, services.call_order == "color;refresh;");
|
||||
}
|
||||
|
||||
void executor_dispatches_texture_and_preset(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeBrushUiServices services;
|
||||
|
||||
const auto texture = pp::app::plan_brush_ui_texture(
|
||||
pp::app::BrushUiTextureSlot::dual,
|
||||
"data/brushes/dual.png",
|
||||
"data/brushes/thumbs/dual.png");
|
||||
PP_EXPECT(harness, texture);
|
||||
if (texture) {
|
||||
PP_EXPECT(harness, pp::app::execute_brush_ui_plan(texture.value(), services).ok());
|
||||
}
|
||||
|
||||
const auto preset = pp::app::plan_brush_ui_preset_replace(true);
|
||||
PP_EXPECT(harness, preset);
|
||||
if (preset) {
|
||||
PP_EXPECT(harness, pp::app::execute_brush_ui_plan(preset.value(), services).ok());
|
||||
}
|
||||
|
||||
PP_EXPECT(harness, services.texture_sets == 1);
|
||||
PP_EXPECT(harness, services.last_slot == pp::app::BrushUiTextureSlot::dual);
|
||||
PP_EXPECT(harness, services.last_path == "data/brushes/dual.png");
|
||||
PP_EXPECT(harness, services.last_thumbnail_path == "data/brushes/thumbs/dual.png");
|
||||
PP_EXPECT(harness, services.preset_replacements == 1);
|
||||
PP_EXPECT(harness, services.preserved_color);
|
||||
PP_EXPECT(harness, services.loaded_resources);
|
||||
PP_EXPECT(harness, services.refreshes == 2);
|
||||
PP_EXPECT(harness, services.call_order == "texture;refresh;preset;refresh;");
|
||||
}
|
||||
|
||||
void executor_dispatches_stroke_refresh_only(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeBrushUiServices services;
|
||||
const auto plan = pp::app::plan_brush_ui_stroke_settings_changed();
|
||||
PP_EXPECT(harness, pp::app::execute_brush_ui_plan(plan, services).ok());
|
||||
|
||||
PP_EXPECT(harness, services.color_sets == 0);
|
||||
PP_EXPECT(harness, services.texture_sets == 0);
|
||||
PP_EXPECT(harness, services.preset_replacements == 0);
|
||||
PP_EXPECT(harness, services.refreshes == 1);
|
||||
PP_EXPECT(harness, services.last_update_color_ui);
|
||||
PP_EXPECT(harness, services.last_update_brush_ui);
|
||||
PP_EXPECT(harness, services.call_order == "refresh;");
|
||||
}
|
||||
|
||||
void executor_rejects_invalid_plan_payloads(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeBrushUiServices services;
|
||||
|
||||
pp::app::BrushUiPlan color;
|
||||
color.operation = pp::app::BrushUiOperation::set_tip_color;
|
||||
color.r = std::nanf("");
|
||||
color.g = 0.0F;
|
||||
color.b = 0.0F;
|
||||
color.a = 1.0F;
|
||||
PP_EXPECT(harness, !pp::app::execute_brush_ui_plan(color, services).ok());
|
||||
|
||||
pp::app::BrushUiPlan texture;
|
||||
texture.operation = pp::app::BrushUiOperation::set_texture;
|
||||
texture.texture_slot = pp::app::BrushUiTextureSlot::tip;
|
||||
texture.path.clear();
|
||||
PP_EXPECT(harness, !pp::app::execute_brush_ui_plan(texture, services).ok());
|
||||
|
||||
PP_EXPECT(harness, services.color_sets == 0);
|
||||
PP_EXPECT(harness, services.texture_sets == 0);
|
||||
PP_EXPECT(harness, services.refreshes == 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
@@ -81,5 +229,9 @@ int main()
|
||||
harness.run("texture plan validates path and slot", texture_plan_validates_path_and_slot);
|
||||
harness.run("preset plan preserves color and requires brush", preset_plan_preserves_color_and_requires_brush);
|
||||
harness.run("stroke settings plan updates brush preview", stroke_settings_plan_updates_brush_preview);
|
||||
harness.run("executor dispatches color and refresh", executor_dispatches_color_and_refresh);
|
||||
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("executor rejects invalid plan payloads", executor_rejects_invalid_plan_payloads);
|
||||
return harness.finish();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user