Add brush stroke control boundary
This commit is contained in:
@@ -32,7 +32,7 @@ and validation command.
|
||||
|
||||
| Capability | Current Area | Target Owner | Required Tests |
|
||||
| --- | --- | --- | --- |
|
||||
| Brush settings serialization | `Brush`, `Serializer` | `pp_paint`, `pp_assets` | Round-trip and boundary values |
|
||||
| Brush settings serialization and stroke-panel controls | `Brush`, `Serializer`, `NodePanelStroke` | `pp_paint`, `pp_assets`, `pp_app_core`, `pp_panopainter_ui` | Round-trip and boundary values; stroke slider/toggle/blend/reset planning and invalid setting tests |
|
||||
| ABR import | `ABR`, `Brush` | `pp_assets`, `pp_paint` | Sample ABR and malformed ABR |
|
||||
| PPBR import/export | brush panel/dialog | `pp_assets`, `pp_panopainter_ui` | Round-trip fixture |
|
||||
| Stroke sampling | `Stroke`, `Canvas` | `pp_paint` | Property tests for spacing, pressure, jitter |
|
||||
|
||||
@@ -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, 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`, and `pp_legacy_ui_core` temporarily links `pp_app_core`, but the live adapter still mutates or reads legacy `Canvas`/`Layer` frame state and canvas mode 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 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, and execution dispatch now consume pure `pp_app_core` through `App::init_sidebar`, `NodePanelBrush`, restored/docked floating-panel callbacks, `pano_cli plan-brush-operation`, `pano_cli plan-brush-texture-list`, `BrushUiServices`, and `BrushTextureListServices`, but the live adapter still mutates legacy `Brush`, loads/saves legacy brush texture images, and refreshes legacy quick/stroke/color widgets | Preserve existing brush UI behavior while brush commands move toward a brush/app/asset 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`; `pano_cli plan-brush-texture-list --kind add --dir brushes --data-path data --source C:/Temp/soft.png`; `ctest --preset desktop-fast --build-config Debug` | Brush color/texture/preset/stroke-settings and texture-list execution are owned by injected brush/app/asset/UI services with no legacy brush adapter |
|
||||
| DEBT-0023 | Open | Modernization | Brush/color/preset/stroke-settings UI planning, texture-list add/remove/reorder planning, stroke-panel slider/toggle/blend/reset planning, and execution dispatch now consume pure `pp_app_core` through `App::init_sidebar`, `NodePanelBrush`, `NodePanelStroke`, restored/docked floating-panel callbacks, `pano_cli plan-brush-operation`, `pano_cli plan-brush-texture-list`, `pano_cli plan-brush-stroke-control`, `BrushUiServices`, `BrushTextureListServices`, and `BrushStrokeControlServices`, but the live adapter still mutates legacy `Brush`/`Canvas::I`, loads/saves legacy brush texture images, and refreshes legacy quick/stroke/color widgets | Preserve existing brush UI behavior while brush commands move toward a brush/app/asset 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`; `pano_cli plan-brush-texture-list --kind add --dir brushes --data-path data --source C:/Temp/soft.png`; `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, and stroke-control execution are owned by injected brush/app/asset/UI services with no legacy brush/canvas 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 and execution dispatch now consume pure `pp_app_core` through `NodePanelQuick`, `pano_cli plan-quick-operation`, and the `QuickUiServices` boundary, but the live adapter 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 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 |
|
||||
|
||||
@@ -515,6 +515,11 @@ the legacy `Brush`/panel adapter mutates brush state or loads brush resources.
|
||||
texture add, remove, and reorder actions, and `NodePanelBrush` now dispatches
|
||||
those actions through `BrushTextureListServices` before the legacy image
|
||||
load/save and UI-list adapter continues.
|
||||
`pano_cli plan-brush-stroke-control` exposes app-core planning for the live
|
||||
stroke panel's slider, checkbox, blend-mode, tip-aspect reset, and default
|
||||
brush reset commands. `NodePanelStroke` now dispatches those controls through
|
||||
`BrushStrokeControlServices` before the legacy `Canvas::I`/`Brush`/stroke-panel
|
||||
adapter continues.
|
||||
`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. Canvas tool execution now dispatches through `CanvasToolServices`
|
||||
@@ -1232,8 +1237,9 @@ Results:
|
||||
invalid color rejection, texture-path validation, preset-brush availability,
|
||||
preserve-current-color intent, stroke-settings refresh intent, texture-list
|
||||
add target path planning, user-texture removal intent, clamped reorder intent,
|
||||
service dispatch ordering, texture/preset/list execution payloads, execution
|
||||
failure preservation, and invalid execution payload rejection.
|
||||
stroke-control slider/toggle/blend/reset planning, service dispatch ordering,
|
||||
texture/preset/list/stroke-control execution payloads, execution failure
|
||||
preservation, 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`,
|
||||
@@ -1245,6 +1251,13 @@ Results:
|
||||
`pano_cli_plan_brush_texture_list_move_edge_smoke`, and
|
||||
`pano_cli_plan_brush_texture_list_rejects_bad_source` passed and expose live
|
||||
brush/pattern texture-list planning as JSON automation.
|
||||
- `pano_cli_plan_brush_stroke_control_float_smoke`,
|
||||
`pano_cli_plan_brush_stroke_control_toggle_smoke`,
|
||||
`pano_cli_plan_brush_stroke_control_blend_smoke`,
|
||||
`pano_cli_plan_brush_stroke_control_reset_smoke`,
|
||||
`pano_cli_plan_brush_stroke_control_rejects_bad_setting`, and
|
||||
`pano_cli_plan_brush_stroke_control_rejects_bad_blend` passed and expose live
|
||||
stroke-panel slider/toggle/blend/reset planning as JSON automation.
|
||||
- `pp_app_core_grid_ui_tests` passed, covering heightmap pick/load/reload/clear
|
||||
planning, lightmap capability and limit checks, missing-heightmap no-op
|
||||
behavior, and commit canvas gating.
|
||||
|
||||
@@ -28,6 +28,83 @@ enum class BrushTextureListOperation {
|
||||
move_texture,
|
||||
};
|
||||
|
||||
enum class BrushStrokeControlOperation {
|
||||
set_float,
|
||||
set_bool,
|
||||
set_blend_mode,
|
||||
reset_tip_aspect,
|
||||
reset_default_brush,
|
||||
};
|
||||
|
||||
enum class BrushStrokeFloatSetting {
|
||||
tip_size,
|
||||
tip_spacing,
|
||||
tip_flow,
|
||||
tip_opacity,
|
||||
tip_angle,
|
||||
tip_angle_smooth,
|
||||
tip_mix,
|
||||
tip_wet,
|
||||
tip_noise,
|
||||
tip_hue,
|
||||
tip_saturation,
|
||||
tip_value,
|
||||
jitter_scale,
|
||||
jitter_angle,
|
||||
jitter_scatter,
|
||||
jitter_flow,
|
||||
jitter_opacity,
|
||||
jitter_hue,
|
||||
jitter_saturation,
|
||||
jitter_value,
|
||||
jitter_aspect,
|
||||
dual_size,
|
||||
dual_spacing,
|
||||
dual_scatter,
|
||||
tip_aspect,
|
||||
dual_opacity,
|
||||
dual_flow,
|
||||
dual_rotate,
|
||||
pattern_scale,
|
||||
pattern_brightness,
|
||||
pattern_contrast,
|
||||
pattern_depth,
|
||||
};
|
||||
|
||||
enum class BrushStrokeBoolSetting {
|
||||
tip_angle_init,
|
||||
tip_angle_follow,
|
||||
tip_flow_pressure,
|
||||
tip_opacity_pressure,
|
||||
tip_size_pressure,
|
||||
jitter_scatter_both_axis,
|
||||
jitter_aspect_both_axis,
|
||||
jitter_hsv_each_sample,
|
||||
tip_invert,
|
||||
tip_flip_x,
|
||||
tip_flip_y,
|
||||
pattern_enabled,
|
||||
dual_enabled,
|
||||
dual_scatter_both_axis,
|
||||
dual_invert,
|
||||
dual_flip_x,
|
||||
dual_flip_y,
|
||||
dual_random_flip,
|
||||
tip_random_flip_x,
|
||||
tip_random_flip_y,
|
||||
pattern_each_sample,
|
||||
pattern_invert,
|
||||
pattern_flip_x,
|
||||
pattern_flip_y,
|
||||
pattern_random_offset,
|
||||
};
|
||||
|
||||
enum class BrushStrokeBlendSetting {
|
||||
tip,
|
||||
dual,
|
||||
pattern,
|
||||
};
|
||||
|
||||
struct BrushUiPlan {
|
||||
BrushUiOperation operation = BrushUiOperation::stroke_settings_changed;
|
||||
BrushUiTextureSlot texture_slot = BrushUiTextureSlot::tip;
|
||||
@@ -62,6 +139,20 @@ struct BrushTextureListPlan {
|
||||
bool no_op = false;
|
||||
};
|
||||
|
||||
struct BrushStrokeControlPlan {
|
||||
BrushStrokeControlOperation operation = BrushStrokeControlOperation::set_float;
|
||||
BrushStrokeFloatSetting float_setting = BrushStrokeFloatSetting::tip_size;
|
||||
BrushStrokeBoolSetting bool_setting = BrushStrokeBoolSetting::tip_angle_init;
|
||||
BrushStrokeBlendSetting blend_setting = BrushStrokeBlendSetting::tip;
|
||||
float float_value = 0.0F;
|
||||
bool bool_value = false;
|
||||
int blend_mode = 0;
|
||||
bool mutates_brush = false;
|
||||
bool updates_controls = false;
|
||||
bool refreshes_preview = false;
|
||||
bool notifies_stroke_change = false;
|
||||
};
|
||||
|
||||
class BrushUiServices {
|
||||
public:
|
||||
virtual ~BrushUiServices() = default;
|
||||
@@ -88,6 +179,20 @@ public:
|
||||
virtual void save_texture_list() = 0;
|
||||
};
|
||||
|
||||
class BrushStrokeControlServices {
|
||||
public:
|
||||
virtual ~BrushStrokeControlServices() = default;
|
||||
|
||||
virtual void set_float_setting(BrushStrokeFloatSetting setting, float value) = 0;
|
||||
virtual void set_bool_setting(BrushStrokeBoolSetting setting, bool value) = 0;
|
||||
virtual void set_blend_mode(BrushStrokeBlendSetting setting, int blend_mode) = 0;
|
||||
virtual void reset_tip_aspect(float value) = 0;
|
||||
virtual void reset_default_brush() = 0;
|
||||
virtual void update_stroke_controls() = 0;
|
||||
virtual void refresh_stroke_preview() = 0;
|
||||
virtual void notify_stroke_changed() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<std::string_view> brush_texture_source_stem(
|
||||
std::string_view source_path) noexcept
|
||||
{
|
||||
@@ -116,6 +221,24 @@ public:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_brush_stroke_float(float value) noexcept
|
||||
{
|
||||
if (!std::isfinite(value)) {
|
||||
return pp::foundation::Status::invalid_argument("brush stroke float setting must be finite");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_brush_stroke_blend_mode(int blend_mode) noexcept
|
||||
{
|
||||
if (blend_mode < 0 || blend_mode > 63) {
|
||||
return pp::foundation::Status::out_of_range("brush stroke blend mode must be within 0..63");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<BrushUiPlan> plan_brush_ui_color(
|
||||
float r,
|
||||
float g,
|
||||
@@ -189,6 +312,81 @@ public:
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<BrushStrokeControlPlan> plan_brush_stroke_float_setting(
|
||||
BrushStrokeFloatSetting setting,
|
||||
float value)
|
||||
{
|
||||
const auto status = validate_brush_stroke_float(value);
|
||||
if (!status.ok()) {
|
||||
return pp::foundation::Result<BrushStrokeControlPlan>::failure(status);
|
||||
}
|
||||
|
||||
BrushStrokeControlPlan plan;
|
||||
plan.operation = BrushStrokeControlOperation::set_float;
|
||||
plan.float_setting = setting;
|
||||
plan.float_value = value;
|
||||
plan.mutates_brush = true;
|
||||
plan.refreshes_preview = true;
|
||||
plan.notifies_stroke_change = true;
|
||||
return pp::foundation::Result<BrushStrokeControlPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr BrushStrokeControlPlan plan_brush_stroke_bool_setting(
|
||||
BrushStrokeBoolSetting setting,
|
||||
bool value) noexcept
|
||||
{
|
||||
BrushStrokeControlPlan plan;
|
||||
plan.operation = BrushStrokeControlOperation::set_bool;
|
||||
plan.bool_setting = setting;
|
||||
plan.bool_value = value;
|
||||
plan.mutates_brush = true;
|
||||
plan.refreshes_preview = true;
|
||||
plan.notifies_stroke_change = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<BrushStrokeControlPlan> plan_brush_stroke_blend_mode(
|
||||
BrushStrokeBlendSetting setting,
|
||||
int blend_mode)
|
||||
{
|
||||
const auto status = validate_brush_stroke_blend_mode(blend_mode);
|
||||
if (!status.ok()) {
|
||||
return pp::foundation::Result<BrushStrokeControlPlan>::failure(status);
|
||||
}
|
||||
|
||||
BrushStrokeControlPlan plan;
|
||||
plan.operation = BrushStrokeControlOperation::set_blend_mode;
|
||||
plan.blend_setting = setting;
|
||||
plan.blend_mode = blend_mode;
|
||||
plan.mutates_brush = true;
|
||||
plan.refreshes_preview = true;
|
||||
plan.notifies_stroke_change = true;
|
||||
return pp::foundation::Result<BrushStrokeControlPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr BrushStrokeControlPlan plan_brush_tip_aspect_reset(float value = 0.5F) noexcept
|
||||
{
|
||||
BrushStrokeControlPlan plan;
|
||||
plan.operation = BrushStrokeControlOperation::reset_tip_aspect;
|
||||
plan.float_setting = BrushStrokeFloatSetting::tip_aspect;
|
||||
plan.float_value = value;
|
||||
plan.mutates_brush = true;
|
||||
plan.refreshes_preview = true;
|
||||
plan.notifies_stroke_change = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr BrushStrokeControlPlan plan_brush_default_settings_reset() noexcept
|
||||
{
|
||||
BrushStrokeControlPlan plan;
|
||||
plan.operation = BrushStrokeControlOperation::reset_default_brush;
|
||||
plan.mutates_brush = true;
|
||||
plan.updates_controls = true;
|
||||
plan.refreshes_preview = true;
|
||||
plan.notifies_stroke_change = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<BrushTextureListPlan> plan_brush_texture_list_add(
|
||||
std::string_view directory_name,
|
||||
std::string_view data_path,
|
||||
@@ -315,6 +513,63 @@ public:
|
||||
return pp::foundation::Status::invalid_argument("unknown brush UI operation");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_brush_stroke_control_plan(
|
||||
const BrushStrokeControlPlan& plan,
|
||||
BrushStrokeControlServices& services)
|
||||
{
|
||||
switch (plan.operation) {
|
||||
case BrushStrokeControlOperation::set_float:
|
||||
{
|
||||
const auto status = validate_brush_stroke_float(plan.float_value);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
services.set_float_setting(plan.float_setting, plan.float_value);
|
||||
break;
|
||||
}
|
||||
|
||||
case BrushStrokeControlOperation::set_bool:
|
||||
services.set_bool_setting(plan.bool_setting, plan.bool_value);
|
||||
break;
|
||||
|
||||
case BrushStrokeControlOperation::set_blend_mode:
|
||||
{
|
||||
const auto status = validate_brush_stroke_blend_mode(plan.blend_mode);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
services.set_blend_mode(plan.blend_setting, plan.blend_mode);
|
||||
break;
|
||||
}
|
||||
|
||||
case BrushStrokeControlOperation::reset_tip_aspect:
|
||||
{
|
||||
const auto status = validate_brush_stroke_float(plan.float_value);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
services.reset_tip_aspect(plan.float_value);
|
||||
break;
|
||||
}
|
||||
|
||||
case BrushStrokeControlOperation::reset_default_brush:
|
||||
services.reset_default_brush();
|
||||
break;
|
||||
}
|
||||
|
||||
if (plan.updates_controls) {
|
||||
services.update_stroke_controls();
|
||||
}
|
||||
if (plan.refreshes_preview) {
|
||||
services.refresh_stroke_preview();
|
||||
}
|
||||
if (plan.notifies_stroke_change) {
|
||||
services.notify_stroke_changed();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_brush_texture_list_plan(
|
||||
const BrushTextureListPlan& plan,
|
||||
BrushTextureListServices& services)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "pch.h"
|
||||
#include "app_core/brush_ui.h"
|
||||
#include "log.h"
|
||||
#include "node_panel_stroke.h"
|
||||
#include "canvas.h"
|
||||
@@ -6,6 +7,137 @@
|
||||
#include "app.h"
|
||||
#include "abr.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class LegacyBrushStrokeControlServices final : public pp::app::BrushStrokeControlServices {
|
||||
public:
|
||||
explicit LegacyBrushStrokeControlServices(NodePanelStroke& panel) : panel_(panel) {}
|
||||
|
||||
void set_float_setting(pp::app::BrushStrokeFloatSetting setting, float value) override
|
||||
{
|
||||
auto& brush = *Canvas::I->m_current_brush;
|
||||
switch (setting) {
|
||||
case pp::app::BrushStrokeFloatSetting::tip_size: brush.m_tip_size = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_spacing: brush.m_tip_spacing = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_flow: brush.m_tip_flow = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_opacity: brush.m_tip_opacity = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_angle: brush.m_tip_angle = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_angle_smooth: brush.m_tip_angle_smooth = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_mix: brush.m_tip_mix = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_wet: brush.m_tip_wet = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_noise: brush.m_tip_noise = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_hue: brush.m_tip_hue = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_saturation: brush.m_tip_sat = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_value: brush.m_tip_val = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_scale: brush.m_jitter_scale = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_angle: brush.m_jitter_angle = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_scatter: brush.m_jitter_scatter = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_flow: brush.m_jitter_flow = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_opacity: brush.m_jitter_opacity = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_hue: brush.m_jitter_hue = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_saturation: brush.m_jitter_sat = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_value: brush.m_jitter_val = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_aspect: brush.m_jitter_aspect = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::dual_size: brush.m_dual_size = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::dual_spacing: brush.m_dual_spacing = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::dual_scatter: brush.m_dual_scatter = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::tip_aspect: brush.m_tip_aspect = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::dual_opacity: brush.m_dual_opacity = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::dual_flow: brush.m_dual_flow = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::dual_rotate: brush.m_dual_rotate = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::pattern_scale: brush.m_pattern_scale = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::pattern_brightness: brush.m_pattern_brightness = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::pattern_contrast: brush.m_pattern_contrast = value; break;
|
||||
case pp::app::BrushStrokeFloatSetting::pattern_depth: brush.m_pattern_depth = value; break;
|
||||
}
|
||||
}
|
||||
|
||||
void set_bool_setting(pp::app::BrushStrokeBoolSetting setting, bool value) override
|
||||
{
|
||||
auto& brush = *Canvas::I->m_current_brush;
|
||||
switch (setting) {
|
||||
case pp::app::BrushStrokeBoolSetting::tip_angle_init: brush.m_tip_angle_init = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::tip_angle_follow: brush.m_tip_angle_follow = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::tip_flow_pressure: brush.m_tip_flow_pressure = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::tip_opacity_pressure: brush.m_tip_opacity_pressure = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::tip_size_pressure: brush.m_tip_size_pressure = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::jitter_scatter_both_axis: brush.m_jitter_scatter_bothaxis = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis: brush.m_jitter_aspect_bothaxis = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample: brush.m_jitter_hsv_eachsample = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::tip_invert: brush.m_tip_invert = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::tip_flip_x: brush.m_tip_flipx = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::tip_flip_y: brush.m_tip_flipy = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_enabled: brush.m_pattern_enabled = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::dual_enabled: brush.m_dual_enabled = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis: brush.m_dual_scatter_bothaxis = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::dual_invert: brush.m_dual_invert = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::dual_flip_x: brush.m_dual_flipx = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::dual_flip_y: brush.m_dual_flipy = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::dual_random_flip: brush.m_dual_randflip = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::tip_random_flip_x: brush.m_tip_randflipx = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::tip_random_flip_y: brush.m_tip_randflipy = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_each_sample: brush.m_pattern_eachsample = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_invert: brush.m_pattern_invert = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_flip_x: brush.m_pattern_flipx = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_flip_y: brush.m_pattern_flipy = value; break;
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_random_offset: brush.m_pattern_rand_offset = value; break;
|
||||
}
|
||||
}
|
||||
|
||||
void set_blend_mode(pp::app::BrushStrokeBlendSetting setting, int blend_mode) override
|
||||
{
|
||||
auto& brush = *Canvas::I->m_current_brush;
|
||||
switch (setting) {
|
||||
case pp::app::BrushStrokeBlendSetting::tip: brush.m_blend_mode = blend_mode; break;
|
||||
case pp::app::BrushStrokeBlendSetting::dual: brush.m_dual_blend_mode = blend_mode; break;
|
||||
case pp::app::BrushStrokeBlendSetting::pattern: brush.m_pattern_blend_mode = blend_mode; break;
|
||||
}
|
||||
}
|
||||
|
||||
void reset_tip_aspect(float value) override
|
||||
{
|
||||
panel_.m_tip_aspect->set_value(value);
|
||||
Canvas::I->m_current_brush->m_tip_aspect = value;
|
||||
}
|
||||
|
||||
void reset_default_brush() override
|
||||
{
|
||||
auto brush = std::make_shared<Brush>();
|
||||
brush->load_tip(
|
||||
panel_.m_brush_popup->get_texture_path(panel_.m_default_brush_index),
|
||||
panel_.m_brush_popup->get_thumb_path(panel_.m_default_brush_index));
|
||||
brush->m_tip_size = 30;
|
||||
brush->m_tip_flow = .9f;
|
||||
brush->m_tip_spacing = .1f;
|
||||
brush->m_tip_opacity = 1.f;
|
||||
Canvas::I->m_current_brush = brush;
|
||||
}
|
||||
|
||||
void update_stroke_controls() override
|
||||
{
|
||||
panel_.update_controls();
|
||||
}
|
||||
|
||||
void refresh_stroke_preview() override
|
||||
{
|
||||
if (panel_.m_preview) {
|
||||
panel_.m_preview->m_brush = Canvas::I->m_current_brush;
|
||||
}
|
||||
}
|
||||
|
||||
void notify_stroke_changed() override
|
||||
{
|
||||
if (panel_.on_stroke_change) {
|
||||
panel_.on_stroke_change(&panel_);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
NodePanelStroke& panel_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Node* NodePanelStroke::clone_instantiate() const
|
||||
{
|
||||
return new NodePanelStroke();
|
||||
@@ -156,7 +288,8 @@ void NodePanelStroke::init_controls()
|
||||
//m_presets_popup->m_flood_events = true;
|
||||
//m_presets_popup->m_capture_children = false;
|
||||
|
||||
int br_idx = std::max(m_brush_popup->find_brush("Round-Hard"), 0);
|
||||
m_default_brush_index = std::max(m_brush_popup->find_brush("Round-Hard"), 0);
|
||||
const int br_idx = m_default_brush_index;
|
||||
|
||||
// init main brush
|
||||
auto b = std::make_shared<Brush>();
|
||||
@@ -331,73 +464,73 @@ void NodePanelStroke::init_controls()
|
||||
|
||||
m_blend_mode = find<NodeComboBox>("blend-mode");
|
||||
m_blend_mode->on_select = [this](Node*, int index) {
|
||||
Canvas::I->m_current_brush->m_blend_mode = index;
|
||||
//m_preview->draw_stroke();
|
||||
if (on_stroke_change)
|
||||
on_stroke_change(this);
|
||||
const auto plan = pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::tip, index);
|
||||
if (plan) {
|
||||
execute_stroke_control_plan(plan.value());
|
||||
}
|
||||
};
|
||||
|
||||
init_slider(m_tip_size, "tip-size", &Brush::m_tip_size);
|
||||
init_slider(m_tip_spacing, "tip-spacing", &Brush::m_tip_spacing);
|
||||
init_slider(m_tip_flow, "tip-flow", &Brush::m_tip_flow);
|
||||
init_slider(m_tip_opacity, "tip-opacity", &Brush::m_tip_opacity);
|
||||
init_slider(m_tip_angle, "tip-angle", &Brush::m_tip_angle);
|
||||
init_slider(m_tip_angle_smooth, "tip-angle-smooth", &Brush::m_tip_angle_smooth);
|
||||
init_slider(m_tip_mix, "tip-mix", &Brush::m_tip_mix);
|
||||
init_slider(m_tip_wet, "tip-wet", &Brush::m_tip_wet);
|
||||
init_slider(m_tip_noise, "tip-noise", &Brush::m_tip_noise);
|
||||
init_slider(m_tip_hue, "tip-hue", &Brush::m_tip_hue);
|
||||
init_slider(m_tip_sat, "tip-sat", &Brush::m_tip_sat);
|
||||
init_slider(m_tip_val, "tip-val", &Brush::m_tip_val);
|
||||
init_slider(m_jitter_scale, "jitter-scale", &Brush::m_jitter_scale);
|
||||
init_slider(m_jitter_angle, "jitter-angle", &Brush::m_jitter_angle);
|
||||
init_slider(m_jitter_scatter, "jitter-scatter", &Brush::m_jitter_scatter);
|
||||
init_slider(m_jitter_flow, "jitter-flow", &Brush::m_jitter_flow);
|
||||
init_slider(m_jitter_opacity, "jitter-opacity", &Brush::m_jitter_opacity);
|
||||
init_slider(m_jitter_hue, "jitter-hue", &Brush::m_jitter_hue);
|
||||
init_slider(m_jitter_sat, "jitter-sat", &Brush::m_jitter_sat);
|
||||
init_slider(m_jitter_val, "jitter-val", &Brush::m_jitter_val);
|
||||
init_slider(m_jitter_aspect, "jitter-aspect", &Brush::m_jitter_aspect);
|
||||
init_slider(m_tip_size, "tip-size", pp::app::BrushStrokeFloatSetting::tip_size, &Brush::m_tip_size);
|
||||
init_slider(m_tip_spacing, "tip-spacing", pp::app::BrushStrokeFloatSetting::tip_spacing, &Brush::m_tip_spacing);
|
||||
init_slider(m_tip_flow, "tip-flow", pp::app::BrushStrokeFloatSetting::tip_flow, &Brush::m_tip_flow);
|
||||
init_slider(m_tip_opacity, "tip-opacity", pp::app::BrushStrokeFloatSetting::tip_opacity, &Brush::m_tip_opacity);
|
||||
init_slider(m_tip_angle, "tip-angle", pp::app::BrushStrokeFloatSetting::tip_angle, &Brush::m_tip_angle);
|
||||
init_slider(m_tip_angle_smooth, "tip-angle-smooth", pp::app::BrushStrokeFloatSetting::tip_angle_smooth, &Brush::m_tip_angle_smooth);
|
||||
init_slider(m_tip_mix, "tip-mix", pp::app::BrushStrokeFloatSetting::tip_mix, &Brush::m_tip_mix);
|
||||
init_slider(m_tip_wet, "tip-wet", pp::app::BrushStrokeFloatSetting::tip_wet, &Brush::m_tip_wet);
|
||||
init_slider(m_tip_noise, "tip-noise", pp::app::BrushStrokeFloatSetting::tip_noise, &Brush::m_tip_noise);
|
||||
init_slider(m_tip_hue, "tip-hue", pp::app::BrushStrokeFloatSetting::tip_hue, &Brush::m_tip_hue);
|
||||
init_slider(m_tip_sat, "tip-sat", pp::app::BrushStrokeFloatSetting::tip_saturation, &Brush::m_tip_sat);
|
||||
init_slider(m_tip_val, "tip-val", pp::app::BrushStrokeFloatSetting::tip_value, &Brush::m_tip_val);
|
||||
init_slider(m_jitter_scale, "jitter-scale", pp::app::BrushStrokeFloatSetting::jitter_scale, &Brush::m_jitter_scale);
|
||||
init_slider(m_jitter_angle, "jitter-angle", pp::app::BrushStrokeFloatSetting::jitter_angle, &Brush::m_jitter_angle);
|
||||
init_slider(m_jitter_scatter, "jitter-scatter", pp::app::BrushStrokeFloatSetting::jitter_scatter, &Brush::m_jitter_scatter);
|
||||
init_slider(m_jitter_flow, "jitter-flow", pp::app::BrushStrokeFloatSetting::jitter_flow, &Brush::m_jitter_flow);
|
||||
init_slider(m_jitter_opacity, "jitter-opacity", pp::app::BrushStrokeFloatSetting::jitter_opacity, &Brush::m_jitter_opacity);
|
||||
init_slider(m_jitter_hue, "jitter-hue", pp::app::BrushStrokeFloatSetting::jitter_hue, &Brush::m_jitter_hue);
|
||||
init_slider(m_jitter_sat, "jitter-sat", pp::app::BrushStrokeFloatSetting::jitter_saturation, &Brush::m_jitter_sat);
|
||||
init_slider(m_jitter_val, "jitter-val", pp::app::BrushStrokeFloatSetting::jitter_value, &Brush::m_jitter_val);
|
||||
init_slider(m_jitter_aspect, "jitter-aspect", pp::app::BrushStrokeFloatSetting::jitter_aspect, &Brush::m_jitter_aspect);
|
||||
|
||||
init_checkbox(m_tip_angle_init, "tip-angle-init", &Brush::m_tip_angle_init);
|
||||
init_checkbox(m_tip_angle_follow, "tip-angle-follow", &Brush::m_tip_angle_follow);
|
||||
init_checkbox(m_tip_flow_pressure, "tip-flow-pressure", &Brush::m_tip_flow_pressure);
|
||||
init_checkbox(m_tip_opacity_pressure, "tip-opacity-pressure", &Brush::m_tip_opacity_pressure);
|
||||
init_checkbox(m_tip_size_pressure, "tip-size-pressure", &Brush::m_tip_size_pressure);
|
||||
init_checkbox(m_jitter_scatter_bothaxis, "jitter-scatter-bothaxis", &Brush::m_jitter_scatter_bothaxis);
|
||||
init_checkbox(m_jitter_aspect_bothaxis, "jitter-aspect-bothaxis", &Brush::m_jitter_aspect_bothaxis);
|
||||
init_checkbox(m_jitter_hsv_eachsample, "jitter-hsv-eachsample", &Brush::m_jitter_hsv_eachsample);
|
||||
init_checkbox(m_tip_angle_init, "tip-angle-init", pp::app::BrushStrokeBoolSetting::tip_angle_init, &Brush::m_tip_angle_init);
|
||||
init_checkbox(m_tip_angle_follow, "tip-angle-follow", pp::app::BrushStrokeBoolSetting::tip_angle_follow, &Brush::m_tip_angle_follow);
|
||||
init_checkbox(m_tip_flow_pressure, "tip-flow-pressure", pp::app::BrushStrokeBoolSetting::tip_flow_pressure, &Brush::m_tip_flow_pressure);
|
||||
init_checkbox(m_tip_opacity_pressure, "tip-opacity-pressure", pp::app::BrushStrokeBoolSetting::tip_opacity_pressure, &Brush::m_tip_opacity_pressure);
|
||||
init_checkbox(m_tip_size_pressure, "tip-size-pressure", pp::app::BrushStrokeBoolSetting::tip_size_pressure, &Brush::m_tip_size_pressure);
|
||||
init_checkbox(m_jitter_scatter_bothaxis, "jitter-scatter-bothaxis", pp::app::BrushStrokeBoolSetting::jitter_scatter_both_axis, &Brush::m_jitter_scatter_bothaxis);
|
||||
init_checkbox(m_jitter_aspect_bothaxis, "jitter-aspect-bothaxis", pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis, &Brush::m_jitter_aspect_bothaxis);
|
||||
init_checkbox(m_jitter_hsv_eachsample, "jitter-hsv-eachsample", pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample, &Brush::m_jitter_hsv_eachsample);
|
||||
|
||||
init_checkbox(m_tip_invert, "tip-invert", &Brush::m_tip_invert);
|
||||
init_checkbox(m_tip_flipx, "tip-flipx", &Brush::m_tip_flipx);
|
||||
init_checkbox(m_tip_flipy, "tip-flipy", &Brush::m_tip_flipy);
|
||||
init_checkbox(m_pattern_enabled, "pattern-enabled", &Brush::m_pattern_enabled);
|
||||
init_checkbox(m_dual_enabled, "dual-enabled", &Brush::m_dual_enabled);
|
||||
init_checkbox(m_dual_scatter_bothaxis, "dual-scatter-bothaxis", &Brush::m_dual_scatter_bothaxis);
|
||||
init_checkbox(m_dual_invert, "dual-invert", &Brush::m_dual_invert);
|
||||
init_checkbox(m_dual_flipx, "dual-flipx", &Brush::m_dual_flipx);
|
||||
init_checkbox(m_dual_flipy, "dual-flipy", &Brush::m_dual_flipy);
|
||||
init_checkbox(m_dual_randflip, "dual-randflip", &Brush::m_dual_randflip);
|
||||
init_checkbox(m_tip_randflipx, "tip-randflipx", &Brush::m_tip_randflipx);
|
||||
init_checkbox(m_tip_randflipy, "tip-randflipy", &Brush::m_tip_randflipy);
|
||||
init_checkbox(m_pattern_eachsample, "pattern-eachsample", &Brush::m_pattern_eachsample);
|
||||
init_checkbox(m_tip_invert, "tip-invert", pp::app::BrushStrokeBoolSetting::tip_invert, &Brush::m_tip_invert);
|
||||
init_checkbox(m_tip_flipx, "tip-flipx", pp::app::BrushStrokeBoolSetting::tip_flip_x, &Brush::m_tip_flipx);
|
||||
init_checkbox(m_tip_flipy, "tip-flipy", pp::app::BrushStrokeBoolSetting::tip_flip_y, &Brush::m_tip_flipy);
|
||||
init_checkbox(m_pattern_enabled, "pattern-enabled", pp::app::BrushStrokeBoolSetting::pattern_enabled, &Brush::m_pattern_enabled);
|
||||
init_checkbox(m_dual_enabled, "dual-enabled", pp::app::BrushStrokeBoolSetting::dual_enabled, &Brush::m_dual_enabled);
|
||||
init_checkbox(m_dual_scatter_bothaxis, "dual-scatter-bothaxis", pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis, &Brush::m_dual_scatter_bothaxis);
|
||||
init_checkbox(m_dual_invert, "dual-invert", pp::app::BrushStrokeBoolSetting::dual_invert, &Brush::m_dual_invert);
|
||||
init_checkbox(m_dual_flipx, "dual-flipx", pp::app::BrushStrokeBoolSetting::dual_flip_x, &Brush::m_dual_flipx);
|
||||
init_checkbox(m_dual_flipy, "dual-flipy", pp::app::BrushStrokeBoolSetting::dual_flip_y, &Brush::m_dual_flipy);
|
||||
init_checkbox(m_dual_randflip, "dual-randflip", pp::app::BrushStrokeBoolSetting::dual_random_flip, &Brush::m_dual_randflip);
|
||||
init_checkbox(m_tip_randflipx, "tip-randflipx", pp::app::BrushStrokeBoolSetting::tip_random_flip_x, &Brush::m_tip_randflipx);
|
||||
init_checkbox(m_tip_randflipy, "tip-randflipy", pp::app::BrushStrokeBoolSetting::tip_random_flip_y, &Brush::m_tip_randflipy);
|
||||
init_checkbox(m_pattern_eachsample, "pattern-eachsample", pp::app::BrushStrokeBoolSetting::pattern_each_sample, &Brush::m_pattern_eachsample);
|
||||
|
||||
init_checkbox(m_pattern_invert, "pattern-invert", &Brush::m_pattern_invert);
|
||||
init_checkbox(m_pattern_flipx, "pattern-flipx", &Brush::m_pattern_flipx);
|
||||
init_checkbox(m_pattern_flipy, "pattern-flipy", &Brush::m_pattern_flipy);
|
||||
init_checkbox(m_pattern_rand_offset, "pattern-rand-offset", &Brush::m_pattern_rand_offset);
|
||||
init_checkbox(m_pattern_invert, "pattern-invert", pp::app::BrushStrokeBoolSetting::pattern_invert, &Brush::m_pattern_invert);
|
||||
init_checkbox(m_pattern_flipx, "pattern-flipx", pp::app::BrushStrokeBoolSetting::pattern_flip_x, &Brush::m_pattern_flipx);
|
||||
init_checkbox(m_pattern_flipy, "pattern-flipy", pp::app::BrushStrokeBoolSetting::pattern_flip_y, &Brush::m_pattern_flipy);
|
||||
init_checkbox(m_pattern_rand_offset, "pattern-rand-offset", pp::app::BrushStrokeBoolSetting::pattern_random_offset, &Brush::m_pattern_rand_offset);
|
||||
|
||||
init_slider(m_dual_size, "dual-size", &Brush::m_dual_size);
|
||||
init_slider(m_dual_spacing, "dual-spacing", &Brush::m_dual_spacing);
|
||||
init_slider(m_dual_scatter, "dual-scatter", &Brush::m_dual_scatter);
|
||||
init_slider(m_tip_aspect, "tip-aspect", &Brush::m_tip_aspect);
|
||||
init_slider(m_dual_opacity, "dual-opacity", &Brush::m_dual_opacity);
|
||||
init_slider(m_dual_flow, "dual-flow", &Brush::m_dual_flow);
|
||||
init_slider(m_dual_rotate, "dual-rotate", &Brush::m_dual_rotate);
|
||||
init_slider(m_pattern_scale, "pattern-scale", &Brush::m_pattern_scale);
|
||||
init_slider(m_pattern_brightness, "pattern-brightness", &Brush::m_pattern_brightness);
|
||||
init_slider(m_pattern_contrast, "pattern-contrast", &Brush::m_pattern_contrast);
|
||||
init_slider(m_pattern_depth, "pattern-depth", &Brush::m_pattern_depth);
|
||||
init_slider(m_dual_size, "dual-size", pp::app::BrushStrokeFloatSetting::dual_size, &Brush::m_dual_size);
|
||||
init_slider(m_dual_spacing, "dual-spacing", pp::app::BrushStrokeFloatSetting::dual_spacing, &Brush::m_dual_spacing);
|
||||
init_slider(m_dual_scatter, "dual-scatter", pp::app::BrushStrokeFloatSetting::dual_scatter, &Brush::m_dual_scatter);
|
||||
init_slider(m_tip_aspect, "tip-aspect", pp::app::BrushStrokeFloatSetting::tip_aspect, &Brush::m_tip_aspect);
|
||||
init_slider(m_dual_opacity, "dual-opacity", pp::app::BrushStrokeFloatSetting::dual_opacity, &Brush::m_dual_opacity);
|
||||
init_slider(m_dual_flow, "dual-flow", pp::app::BrushStrokeFloatSetting::dual_flow, &Brush::m_dual_flow);
|
||||
init_slider(m_dual_rotate, "dual-rotate", pp::app::BrushStrokeFloatSetting::dual_rotate, &Brush::m_dual_rotate);
|
||||
init_slider(m_pattern_scale, "pattern-scale", pp::app::BrushStrokeFloatSetting::pattern_scale, &Brush::m_pattern_scale);
|
||||
init_slider(m_pattern_brightness, "pattern-brightness", pp::app::BrushStrokeFloatSetting::pattern_brightness, &Brush::m_pattern_brightness);
|
||||
init_slider(m_pattern_contrast, "pattern-contrast", pp::app::BrushStrokeFloatSetting::pattern_contrast, &Brush::m_pattern_contrast);
|
||||
init_slider(m_pattern_depth, "pattern-depth", pp::app::BrushStrokeFloatSetting::pattern_depth, &Brush::m_pattern_depth);
|
||||
|
||||
SliderCurve curve_cubic {
|
||||
[](float v) { return glm::pow(v, 3.f); },
|
||||
@@ -469,27 +602,23 @@ void NodePanelStroke::init_controls()
|
||||
|
||||
m_tip_aspect_reset = find<NodeButtonCustom>("tip-aspect-reset");
|
||||
m_tip_aspect_reset->on_click = [this](Node*) {
|
||||
m_tip_aspect->set_value(0.5);
|
||||
Canvas::I->m_current_brush->m_tip_aspect = 0.5f;
|
||||
//m_preview->draw_stroke();
|
||||
if (on_stroke_change)
|
||||
on_stroke_change(this);
|
||||
execute_stroke_control_plan(pp::app::plan_brush_tip_aspect_reset());
|
||||
};
|
||||
|
||||
m_dual_blend_mode = find<NodeComboBox>("dual-blend-mode");
|
||||
m_dual_blend_mode->on_select = [this](Node*, int index) {
|
||||
Canvas::I->m_current_brush->m_dual_blend_mode = index;
|
||||
//m_preview->draw_stroke();
|
||||
if (on_stroke_change)
|
||||
on_stroke_change(this);
|
||||
const auto plan = pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::dual, index);
|
||||
if (plan) {
|
||||
execute_stroke_control_plan(plan.value());
|
||||
}
|
||||
};
|
||||
|
||||
m_pattern_blend_mode = find<NodeComboBox>("pattern-blend-mode");
|
||||
m_pattern_blend_mode->on_select = [this](Node*, int index) {
|
||||
Canvas::I->m_current_brush->m_pattern_blend_mode = index;
|
||||
//m_preview->draw_stroke();
|
||||
if (on_stroke_change)
|
||||
on_stroke_change(this);
|
||||
const auto plan = pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::pattern, index);
|
||||
if (plan) {
|
||||
execute_stroke_control_plan(plan.value());
|
||||
}
|
||||
};
|
||||
|
||||
m_preview->m_brush = Canvas::I->m_current_brush;
|
||||
@@ -518,54 +647,68 @@ void NodePanelStroke::init_controls()
|
||||
}
|
||||
|
||||
m_brush_settings_reset = find<NodeButton>("brush-settings-reset");
|
||||
m_brush_settings_reset->on_click = [br_idx,this](Node*) {
|
||||
auto b = std::make_shared<Brush>();
|
||||
b->load_tip(m_brush_popup->get_texture_path(br_idx), m_brush_popup->get_thumb_path(br_idx));
|
||||
//b->load_dual(m_brush_popup->get_texture_path(br_idx), m_brush_popup->get_thumb_path(br_idx));
|
||||
//b->load_pattern(m_pattern_popup->get_texture_path(0), m_pattern_popup->get_thumb_path(0));
|
||||
b->m_tip_size = 30;
|
||||
b->m_tip_flow = .9f;
|
||||
b->m_tip_spacing = .1f;
|
||||
b->m_tip_opacity = 1.f;
|
||||
Canvas::I->m_current_brush = b;
|
||||
update_controls();
|
||||
App::I->brush_update(true, true);
|
||||
m_brush_settings_reset->on_click = [this](Node*) {
|
||||
execute_stroke_control_plan(pp::app::plan_brush_default_settings_reset());
|
||||
};
|
||||
|
||||
update_controls();
|
||||
}
|
||||
|
||||
void NodePanelStroke::init_slider(NodeSliderH*& target, const char* id, float Brush::* prop)
|
||||
void NodePanelStroke::init_slider(
|
||||
NodeSliderH*& target,
|
||||
const char* id,
|
||||
pp::app::BrushStrokeFloatSetting setting,
|
||||
float Brush::* prop)
|
||||
{
|
||||
target = find<NodeSliderH>(id);
|
||||
target->on_value_changed = std::bind(&NodePanelStroke::handle_slide,
|
||||
this, prop, std::placeholders::_1, std::placeholders::_2);
|
||||
this, setting, prop, std::placeholders::_1, std::placeholders::_2);
|
||||
//m_canvas->m_brush->*prop = target->m_values;
|
||||
}
|
||||
|
||||
void NodePanelStroke::handle_slide(float Brush::* prop, Node* target, float value)
|
||||
void NodePanelStroke::handle_slide(
|
||||
pp::app::BrushStrokeFloatSetting setting,
|
||||
float Brush::* prop,
|
||||
Node* target,
|
||||
float value)
|
||||
{
|
||||
auto curve = m_curves.find((NodeSliderH*)target);
|
||||
Canvas::I->m_current_brush.get()->*prop = curve != m_curves.end() ? curve->second.to_value(value) : value;
|
||||
//m_preview->draw_stroke();
|
||||
if (on_stroke_change)
|
||||
on_stroke_change(this);
|
||||
const auto brush_value = curve != m_curves.end() ? curve->second.to_value(value) : value;
|
||||
const auto plan = pp::app::plan_brush_stroke_float_setting(setting, brush_value);
|
||||
if (plan) {
|
||||
execute_stroke_control_plan(plan.value());
|
||||
} else {
|
||||
Canvas::I->m_current_brush.get()->*prop = brush_value;
|
||||
}
|
||||
}
|
||||
|
||||
void NodePanelStroke::init_checkbox(NodeCheckBox*& target, const char* id, bool Brush::* prop)
|
||||
void NodePanelStroke::init_checkbox(
|
||||
NodeCheckBox*& target,
|
||||
const char* id,
|
||||
pp::app::BrushStrokeBoolSetting setting,
|
||||
bool Brush::* prop)
|
||||
{
|
||||
target = find<NodeCheckBox>(id);
|
||||
target->on_value_changed = std::bind(&NodePanelStroke::handle_checkbox,
|
||||
this, prop, std::placeholders::_1, std::placeholders::_2);
|
||||
this, setting, std::placeholders::_2);
|
||||
Canvas::I->m_current_brush.get()->*prop = target->checked;
|
||||
}
|
||||
|
||||
void NodePanelStroke::handle_checkbox(bool Brush::* prop, Node *target, bool value)
|
||||
void NodePanelStroke::handle_checkbox(
|
||||
pp::app::BrushStrokeBoolSetting setting,
|
||||
bool value)
|
||||
{
|
||||
Canvas::I->m_current_brush.get()->*prop = value;
|
||||
//m_preview->draw_stroke();
|
||||
if (on_stroke_change)
|
||||
on_stroke_change(this);
|
||||
const auto plan = pp::app::plan_brush_stroke_bool_setting(setting, value);
|
||||
execute_stroke_control_plan(plan);
|
||||
}
|
||||
|
||||
void NodePanelStroke::execute_stroke_control_plan(const pp::app::BrushStrokeControlPlan& plan)
|
||||
{
|
||||
LegacyBrushStrokeControlServices services(*this);
|
||||
const auto status = pp::app::execute_brush_stroke_control_plan(plan, services);
|
||||
if (!status.ok()) {
|
||||
LOG("Brush stroke control action failed: %s", status.message);
|
||||
}
|
||||
}
|
||||
|
||||
kEventResult NodePanelStroke::handle_event(Event* e)
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
#include "node_image.h"
|
||||
#include "node_panel_brush.h"
|
||||
|
||||
namespace pp::app {
|
||||
struct BrushStrokeControlPlan;
|
||||
enum class BrushStrokeBoolSetting;
|
||||
enum class BrushStrokeFloatSetting;
|
||||
enum class BrushStrokeBlendSetting;
|
||||
} // namespace pp::app
|
||||
|
||||
class NodePanelStroke : public Node
|
||||
{
|
||||
public:
|
||||
@@ -103,6 +110,7 @@ public:
|
||||
inline float to_slider(float v) { return m_inv(v); }
|
||||
};
|
||||
std::map<NodeSliderH*, SliderCurve> m_curves;
|
||||
int m_default_brush_index = 0;
|
||||
|
||||
virtual Node* clone_instantiate() const override;
|
||||
virtual void clone_finalize(Node* dest) const override;
|
||||
@@ -116,9 +124,10 @@ public:
|
||||
void set_size(float value, bool normalized, bool propagate);
|
||||
|
||||
void init_fold(const std::string& name);
|
||||
void init_slider(NodeSliderH*& slider, const char* id, float Brush::* prop);
|
||||
void handle_slide(float Brush::* prop, Node* target, float value);
|
||||
void init_slider(NodeSliderH*& slider, const char* id, pp::app::BrushStrokeFloatSetting setting, float Brush::* prop);
|
||||
void handle_slide(pp::app::BrushStrokeFloatSetting setting, float Brush::* prop, Node* target, float value);
|
||||
|
||||
void init_checkbox(NodeCheckBox*& slider, const char* id, bool Brush::* prop);
|
||||
void handle_checkbox(bool Brush::* prop, Node* target, bool value);
|
||||
void init_checkbox(NodeCheckBox*& slider, const char* id, pp::app::BrushStrokeBoolSetting setting, bool Brush::* prop);
|
||||
void handle_checkbox(pp::app::BrushStrokeBoolSetting setting, bool value);
|
||||
void execute_stroke_control_plan(const pp::app::BrushStrokeControlPlan& plan);
|
||||
};
|
||||
|
||||
@@ -1156,6 +1156,42 @@ if(TARGET pano_cli)
|
||||
LABELS "app;paint;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_brush_stroke_control_float_smoke
|
||||
COMMAND pano_cli plan-brush-stroke-control --kind float --setting tip-size --value 42.5)
|
||||
set_tests_properties(pano_cli_plan_brush_stroke_control_float_smoke PROPERTIES
|
||||
LABELS "app;paint;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-brush-stroke-control\".*\"operation\":\"set-float\".*\"floatSetting\":\"tip-size\".*\"floatValue\":42.5.*\"mutatesBrush\":true.*\"notifiesStrokeChange\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_brush_stroke_control_toggle_smoke
|
||||
COMMAND pano_cli plan-brush-stroke-control --kind bool --setting dual-enabled --enabled)
|
||||
set_tests_properties(pano_cli_plan_brush_stroke_control_toggle_smoke PROPERTIES
|
||||
LABELS "app;paint;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-brush-stroke-control\".*\"operation\":\"set-bool\".*\"boolSetting\":\"dual-enabled\".*\"boolValue\":true.*\"refreshesPreview\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_brush_stroke_control_blend_smoke
|
||||
COMMAND pano_cli plan-brush-stroke-control --kind blend --setting pattern --blend-mode 3)
|
||||
set_tests_properties(pano_cli_plan_brush_stroke_control_blend_smoke PROPERTIES
|
||||
LABELS "app;paint;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-brush-stroke-control\".*\"operation\":\"set-blend-mode\".*\"blendSetting\":\"pattern\".*\"blendMode\":3")
|
||||
|
||||
add_test(NAME pano_cli_plan_brush_stroke_control_reset_smoke
|
||||
COMMAND pano_cli plan-brush-stroke-control --kind default-reset)
|
||||
set_tests_properties(pano_cli_plan_brush_stroke_control_reset_smoke PROPERTIES
|
||||
LABELS "app;paint;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-brush-stroke-control\".*\"operation\":\"reset-default-brush\".*\"updatesControls\":true.*\"notifiesStrokeChange\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_brush_stroke_control_rejects_bad_setting
|
||||
COMMAND pano_cli plan-brush-stroke-control --kind float --setting imaginary --value 1)
|
||||
set_tests_properties(pano_cli_plan_brush_stroke_control_rejects_bad_setting PROPERTIES
|
||||
LABELS "app;paint;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_brush_stroke_control_rejects_bad_blend
|
||||
COMMAND pano_cli plan-brush-stroke-control --kind blend --setting tip --blend-mode 99)
|
||||
set_tests_properties(pano_cli_plan_brush_stroke_control_rejects_bad_blend PROPERTIES
|
||||
LABELS "app;paint;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_tool_draw_smoke
|
||||
COMMAND pano_cli plan-canvas-tool --kind draw)
|
||||
set_tests_properties(pano_cli_plan_canvas_tool_draw_smoke PROPERTIES
|
||||
|
||||
@@ -135,6 +135,80 @@ public:
|
||||
std::string call_order;
|
||||
};
|
||||
|
||||
class FakeBrushStrokeControlServices final : public pp::app::BrushStrokeControlServices {
|
||||
public:
|
||||
void set_float_setting(pp::app::BrushStrokeFloatSetting setting, float value) override
|
||||
{
|
||||
float_sets += 1;
|
||||
last_float_setting = setting;
|
||||
last_float_value = value;
|
||||
call_order += "float;";
|
||||
}
|
||||
|
||||
void set_bool_setting(pp::app::BrushStrokeBoolSetting setting, bool value) override
|
||||
{
|
||||
bool_sets += 1;
|
||||
last_bool_setting = setting;
|
||||
last_bool_value = value;
|
||||
call_order += "bool;";
|
||||
}
|
||||
|
||||
void set_blend_mode(pp::app::BrushStrokeBlendSetting setting, int blend_mode) override
|
||||
{
|
||||
blend_sets += 1;
|
||||
last_blend_setting = setting;
|
||||
last_blend_mode = blend_mode;
|
||||
call_order += "blend;";
|
||||
}
|
||||
|
||||
void reset_tip_aspect(float value) override
|
||||
{
|
||||
tip_aspect_resets += 1;
|
||||
last_float_value = value;
|
||||
call_order += "tip-aspect;";
|
||||
}
|
||||
|
||||
void reset_default_brush() override
|
||||
{
|
||||
default_resets += 1;
|
||||
call_order += "default;";
|
||||
}
|
||||
|
||||
void update_stroke_controls() override
|
||||
{
|
||||
control_updates += 1;
|
||||
call_order += "controls;";
|
||||
}
|
||||
|
||||
void refresh_stroke_preview() override
|
||||
{
|
||||
preview_refreshes += 1;
|
||||
call_order += "preview;";
|
||||
}
|
||||
|
||||
void notify_stroke_changed() override
|
||||
{
|
||||
stroke_notifications += 1;
|
||||
call_order += "notify;";
|
||||
}
|
||||
|
||||
int float_sets = 0;
|
||||
int bool_sets = 0;
|
||||
int blend_sets = 0;
|
||||
int tip_aspect_resets = 0;
|
||||
int default_resets = 0;
|
||||
int control_updates = 0;
|
||||
int preview_refreshes = 0;
|
||||
int stroke_notifications = 0;
|
||||
pp::app::BrushStrokeFloatSetting last_float_setting = pp::app::BrushStrokeFloatSetting::tip_size;
|
||||
pp::app::BrushStrokeBoolSetting last_bool_setting = pp::app::BrushStrokeBoolSetting::tip_angle_init;
|
||||
pp::app::BrushStrokeBlendSetting last_blend_setting = pp::app::BrushStrokeBlendSetting::tip;
|
||||
float last_float_value = 0.0F;
|
||||
bool last_bool_value = false;
|
||||
int last_blend_mode = 0;
|
||||
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);
|
||||
@@ -202,6 +276,58 @@ void stroke_settings_plan_updates_brush_preview(pp::tests::Harness& harness)
|
||||
PP_EXPECT(harness, !plan.loads_brush_resources);
|
||||
}
|
||||
|
||||
void stroke_control_plans_validate_values_and_reject_breaking_points(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto slider = pp::app::plan_brush_stroke_float_setting(
|
||||
pp::app::BrushStrokeFloatSetting::tip_size,
|
||||
42.5F);
|
||||
PP_EXPECT(harness, slider);
|
||||
if (slider) {
|
||||
PP_EXPECT(harness, slider.value().operation == pp::app::BrushStrokeControlOperation::set_float);
|
||||
PP_EXPECT(harness, slider.value().float_setting == pp::app::BrushStrokeFloatSetting::tip_size);
|
||||
PP_EXPECT(harness, slider.value().float_value == 42.5F);
|
||||
PP_EXPECT(harness, slider.value().mutates_brush);
|
||||
PP_EXPECT(harness, slider.value().refreshes_preview);
|
||||
PP_EXPECT(harness, slider.value().notifies_stroke_change);
|
||||
}
|
||||
|
||||
const auto checkbox = pp::app::plan_brush_stroke_bool_setting(
|
||||
pp::app::BrushStrokeBoolSetting::pattern_enabled,
|
||||
true);
|
||||
PP_EXPECT(harness, checkbox.operation == pp::app::BrushStrokeControlOperation::set_bool);
|
||||
PP_EXPECT(harness, checkbox.bool_setting == pp::app::BrushStrokeBoolSetting::pattern_enabled);
|
||||
PP_EXPECT(harness, checkbox.bool_value);
|
||||
|
||||
const auto blend = pp::app::plan_brush_stroke_blend_mode(
|
||||
pp::app::BrushStrokeBlendSetting::dual,
|
||||
7);
|
||||
PP_EXPECT(harness, blend);
|
||||
if (blend) {
|
||||
PP_EXPECT(harness, blend.value().operation == pp::app::BrushStrokeControlOperation::set_blend_mode);
|
||||
PP_EXPECT(harness, blend.value().blend_setting == pp::app::BrushStrokeBlendSetting::dual);
|
||||
PP_EXPECT(harness, blend.value().blend_mode == 7);
|
||||
}
|
||||
|
||||
const auto tip_aspect = pp::app::plan_brush_tip_aspect_reset();
|
||||
PP_EXPECT(harness, tip_aspect.operation == pp::app::BrushStrokeControlOperation::reset_tip_aspect);
|
||||
PP_EXPECT(harness, tip_aspect.float_setting == pp::app::BrushStrokeFloatSetting::tip_aspect);
|
||||
PP_EXPECT(harness, tip_aspect.float_value == 0.5F);
|
||||
|
||||
const auto defaults = pp::app::plan_brush_default_settings_reset();
|
||||
PP_EXPECT(harness, defaults.operation == pp::app::BrushStrokeControlOperation::reset_default_brush);
|
||||
PP_EXPECT(harness, defaults.updates_controls);
|
||||
PP_EXPECT(harness, defaults.refreshes_preview);
|
||||
PP_EXPECT(harness, defaults.notifies_stroke_change);
|
||||
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
!pp::app::plan_brush_stroke_float_setting(
|
||||
pp::app::BrushStrokeFloatSetting::tip_flow,
|
||||
std::nanf("")));
|
||||
PP_EXPECT(harness, !pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::tip, -1));
|
||||
PP_EXPECT(harness, !pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::pattern, 64));
|
||||
}
|
||||
|
||||
void texture_list_add_plans_target_paths_and_rejects_bad_input(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_brush_texture_list_add(
|
||||
@@ -388,6 +514,60 @@ void texture_list_executor_dispatches_and_preserves_failure(pp::tests::Harness&
|
||||
PP_EXPECT(harness, failing_services.call_order == "add-failed;");
|
||||
}
|
||||
|
||||
void stroke_control_executor_dispatches_and_rejects_bad_payloads(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeBrushStrokeControlServices services;
|
||||
|
||||
const auto slider = pp::app::plan_brush_stroke_float_setting(
|
||||
pp::app::BrushStrokeFloatSetting::pattern_depth,
|
||||
0.75F);
|
||||
PP_EXPECT(harness, slider);
|
||||
if (slider) {
|
||||
PP_EXPECT(harness, pp::app::execute_brush_stroke_control_plan(slider.value(), services).ok());
|
||||
}
|
||||
|
||||
const auto checkbox = pp::app::plan_brush_stroke_bool_setting(
|
||||
pp::app::BrushStrokeBoolSetting::dual_enabled,
|
||||
true);
|
||||
PP_EXPECT(harness, pp::app::execute_brush_stroke_control_plan(checkbox, services).ok());
|
||||
|
||||
const auto blend = pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::pattern, 3);
|
||||
PP_EXPECT(harness, blend);
|
||||
if (blend) {
|
||||
PP_EXPECT(harness, pp::app::execute_brush_stroke_control_plan(blend.value(), services).ok());
|
||||
}
|
||||
|
||||
PP_EXPECT(harness, pp::app::execute_brush_stroke_control_plan(pp::app::plan_brush_tip_aspect_reset(), services).ok());
|
||||
PP_EXPECT(harness, pp::app::execute_brush_stroke_control_plan(pp::app::plan_brush_default_settings_reset(), services).ok());
|
||||
|
||||
PP_EXPECT(harness, services.float_sets == 1);
|
||||
PP_EXPECT(harness, services.last_float_setting == pp::app::BrushStrokeFloatSetting::pattern_depth);
|
||||
PP_EXPECT(harness, services.last_bool_setting == pp::app::BrushStrokeBoolSetting::dual_enabled);
|
||||
PP_EXPECT(harness, services.last_blend_setting == pp::app::BrushStrokeBlendSetting::pattern);
|
||||
PP_EXPECT(harness, services.last_blend_mode == 3);
|
||||
PP_EXPECT(harness, services.tip_aspect_resets == 1);
|
||||
PP_EXPECT(harness, services.default_resets == 1);
|
||||
PP_EXPECT(harness, services.preview_refreshes == 5);
|
||||
PP_EXPECT(harness, services.control_updates == 1);
|
||||
PP_EXPECT(harness, services.stroke_notifications == 5);
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
services.call_order == "float;preview;notify;bool;preview;notify;blend;preview;notify;tip-aspect;"
|
||||
"preview;notify;default;controls;preview;notify;");
|
||||
|
||||
FakeBrushStrokeControlServices bad_services;
|
||||
pp::app::BrushStrokeControlPlan bad_float;
|
||||
bad_float.operation = pp::app::BrushStrokeControlOperation::set_float;
|
||||
bad_float.float_value = std::nanf("");
|
||||
PP_EXPECT(harness, !pp::app::execute_brush_stroke_control_plan(bad_float, bad_services).ok());
|
||||
|
||||
pp::app::BrushStrokeControlPlan bad_blend;
|
||||
bad_blend.operation = pp::app::BrushStrokeControlOperation::set_blend_mode;
|
||||
bad_blend.blend_mode = 99;
|
||||
PP_EXPECT(harness, !pp::app::execute_brush_stroke_control_plan(bad_blend, bad_services).ok());
|
||||
PP_EXPECT(harness, bad_services.call_order.empty());
|
||||
}
|
||||
|
||||
void executor_rejects_invalid_plan_payloads(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeBrushUiServices services;
|
||||
@@ -434,12 +614,14 @@ 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("stroke control plans validate values and reject breaking points", stroke_control_plans_validate_values_and_reject_breaking_points);
|
||||
harness.run("texture list add plans target paths and rejects bad input", texture_list_add_plans_target_paths_and_rejects_bad_input);
|
||||
harness.run("texture list remove and move plans handle edges", texture_list_remove_and_move_plans_handle_edges);
|
||||
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("texture list executor dispatches and preserves failure", texture_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();
|
||||
}
|
||||
|
||||
@@ -327,6 +327,14 @@ struct PlanBrushTextureListArgs {
|
||||
bool current_is_user_texture = false;
|
||||
};
|
||||
|
||||
struct PlanBrushStrokeControlArgs {
|
||||
std::string kind = "float";
|
||||
std::string setting = "tip-size";
|
||||
float value = 1.0F;
|
||||
bool enabled = true;
|
||||
int blend_mode = 0;
|
||||
};
|
||||
|
||||
struct PlanGridOperationArgs {
|
||||
std::string kind = "pick";
|
||||
std::string path;
|
||||
@@ -1112,6 +1120,111 @@ const char* brush_texture_list_operation_name(pp::app::BrushTextureListOperation
|
||||
return "add-texture";
|
||||
}
|
||||
|
||||
const char* brush_stroke_control_operation_name(pp::app::BrushStrokeControlOperation operation) noexcept
|
||||
{
|
||||
switch (operation) {
|
||||
case pp::app::BrushStrokeControlOperation::set_float:
|
||||
return "set-float";
|
||||
case pp::app::BrushStrokeControlOperation::set_bool:
|
||||
return "set-bool";
|
||||
case pp::app::BrushStrokeControlOperation::set_blend_mode:
|
||||
return "set-blend-mode";
|
||||
case pp::app::BrushStrokeControlOperation::reset_tip_aspect:
|
||||
return "reset-tip-aspect";
|
||||
case pp::app::BrushStrokeControlOperation::reset_default_brush:
|
||||
return "reset-default-brush";
|
||||
}
|
||||
|
||||
return "set-float";
|
||||
}
|
||||
|
||||
const char* brush_stroke_float_setting_name(pp::app::BrushStrokeFloatSetting setting) noexcept
|
||||
{
|
||||
switch (setting) {
|
||||
case pp::app::BrushStrokeFloatSetting::tip_size: return "tip-size";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_spacing: return "tip-spacing";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_flow: return "tip-flow";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_opacity: return "tip-opacity";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_angle: return "tip-angle";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_angle_smooth: return "tip-angle-smooth";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_mix: return "tip-mix";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_wet: return "tip-wet";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_noise: return "tip-noise";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_hue: return "tip-hue";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_saturation: return "tip-saturation";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_value: return "tip-value";
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_scale: return "jitter-scale";
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_angle: return "jitter-angle";
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_scatter: return "jitter-scatter";
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_flow: return "jitter-flow";
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_opacity: return "jitter-opacity";
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_hue: return "jitter-hue";
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_saturation: return "jitter-saturation";
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_value: return "jitter-value";
|
||||
case pp::app::BrushStrokeFloatSetting::jitter_aspect: return "jitter-aspect";
|
||||
case pp::app::BrushStrokeFloatSetting::dual_size: return "dual-size";
|
||||
case pp::app::BrushStrokeFloatSetting::dual_spacing: return "dual-spacing";
|
||||
case pp::app::BrushStrokeFloatSetting::dual_scatter: return "dual-scatter";
|
||||
case pp::app::BrushStrokeFloatSetting::tip_aspect: return "tip-aspect";
|
||||
case pp::app::BrushStrokeFloatSetting::dual_opacity: return "dual-opacity";
|
||||
case pp::app::BrushStrokeFloatSetting::dual_flow: return "dual-flow";
|
||||
case pp::app::BrushStrokeFloatSetting::dual_rotate: return "dual-rotate";
|
||||
case pp::app::BrushStrokeFloatSetting::pattern_scale: return "pattern-scale";
|
||||
case pp::app::BrushStrokeFloatSetting::pattern_brightness: return "pattern-brightness";
|
||||
case pp::app::BrushStrokeFloatSetting::pattern_contrast: return "pattern-contrast";
|
||||
case pp::app::BrushStrokeFloatSetting::pattern_depth: return "pattern-depth";
|
||||
}
|
||||
|
||||
return "tip-size";
|
||||
}
|
||||
|
||||
const char* brush_stroke_bool_setting_name(pp::app::BrushStrokeBoolSetting setting) noexcept
|
||||
{
|
||||
switch (setting) {
|
||||
case pp::app::BrushStrokeBoolSetting::tip_angle_init: return "tip-angle-init";
|
||||
case pp::app::BrushStrokeBoolSetting::tip_angle_follow: return "tip-angle-follow";
|
||||
case pp::app::BrushStrokeBoolSetting::tip_flow_pressure: return "tip-flow-pressure";
|
||||
case pp::app::BrushStrokeBoolSetting::tip_opacity_pressure: return "tip-opacity-pressure";
|
||||
case pp::app::BrushStrokeBoolSetting::tip_size_pressure: return "tip-size-pressure";
|
||||
case pp::app::BrushStrokeBoolSetting::jitter_scatter_both_axis: return "jitter-scatter-both-axis";
|
||||
case pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis: return "jitter-aspect-both-axis";
|
||||
case pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample: return "jitter-hsv-each-sample";
|
||||
case pp::app::BrushStrokeBoolSetting::tip_invert: return "tip-invert";
|
||||
case pp::app::BrushStrokeBoolSetting::tip_flip_x: return "tip-flip-x";
|
||||
case pp::app::BrushStrokeBoolSetting::tip_flip_y: return "tip-flip-y";
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_enabled: return "pattern-enabled";
|
||||
case pp::app::BrushStrokeBoolSetting::dual_enabled: return "dual-enabled";
|
||||
case pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis: return "dual-scatter-both-axis";
|
||||
case pp::app::BrushStrokeBoolSetting::dual_invert: return "dual-invert";
|
||||
case pp::app::BrushStrokeBoolSetting::dual_flip_x: return "dual-flip-x";
|
||||
case pp::app::BrushStrokeBoolSetting::dual_flip_y: return "dual-flip-y";
|
||||
case pp::app::BrushStrokeBoolSetting::dual_random_flip: return "dual-random-flip";
|
||||
case pp::app::BrushStrokeBoolSetting::tip_random_flip_x: return "tip-random-flip-x";
|
||||
case pp::app::BrushStrokeBoolSetting::tip_random_flip_y: return "tip-random-flip-y";
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_each_sample: return "pattern-each-sample";
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_invert: return "pattern-invert";
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_flip_x: return "pattern-flip-x";
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_flip_y: return "pattern-flip-y";
|
||||
case pp::app::BrushStrokeBoolSetting::pattern_random_offset: return "pattern-random-offset";
|
||||
}
|
||||
|
||||
return "tip-angle-init";
|
||||
}
|
||||
|
||||
const char* brush_stroke_blend_setting_name(pp::app::BrushStrokeBlendSetting setting) noexcept
|
||||
{
|
||||
switch (setting) {
|
||||
case pp::app::BrushStrokeBlendSetting::tip:
|
||||
return "tip";
|
||||
case pp::app::BrushStrokeBlendSetting::dual:
|
||||
return "dual";
|
||||
case pp::app::BrushStrokeBlendSetting::pattern:
|
||||
return "pattern";
|
||||
}
|
||||
|
||||
return "tip";
|
||||
}
|
||||
|
||||
const char* canvas_tool_operation_name(pp::app::CanvasToolOperation operation) noexcept
|
||||
{
|
||||
switch (operation) {
|
||||
@@ -1639,6 +1752,7 @@ void print_help()
|
||||
<< " plan-animation-panel-action --action goto|next|prev|playback|toggle-playback [--total-duration N] [--current-frame N] [--target-frame N] [--playing]\n"
|
||||
<< " plan-brush-operation --kind color|tip|pattern|dual|preset|settings [--path FILE] [--thumb FILE] [--r N] [--g N] [--b N] [--a N] [--no-brush]\n"
|
||||
<< " plan-brush-texture-list --kind add|remove|move [--dir NAME] [--data-path DIR] [--source FILE] [--item-count N] [--current-index N] [--offset N] [--user-texture]\n"
|
||||
<< " plan-brush-stroke-control --kind float|bool|blend|tip-aspect-reset|default-reset [--setting NAME] [--value N] [--enabled|--disabled] [--blend-mode N]\n"
|
||||
<< " plan-canvas-tool --kind draw|erase|line|camera|grid|copy|cut|fill|mask-free|mask-line|bucket|pick|touch-lock [--current-mode-draw]\n"
|
||||
<< " plan-canvas-tool-state [--mode draw|erase|line|camera|grid|copy|cut|fill|mask-free|mask-line|bucket] [--picking] [--touch-lock]\n"
|
||||
<< " plan-grid-operation --kind pick|load|reload|clear|render|commit [--path FILE] [--no-heightmap] [--no-canvas] [--float32] [--float16] [--texture-resolution N] [--samples N]\n"
|
||||
@@ -4419,6 +4533,282 @@ int plan_brush_texture_list(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Result<pp::app::BrushStrokeFloatSetting> parse_brush_stroke_float_setting(
|
||||
std::string_view setting)
|
||||
{
|
||||
static constexpr std::array<std::pair<std::string_view, pp::app::BrushStrokeFloatSetting>, 32> settings{ {
|
||||
{ "tip-size", pp::app::BrushStrokeFloatSetting::tip_size },
|
||||
{ "tip-spacing", pp::app::BrushStrokeFloatSetting::tip_spacing },
|
||||
{ "tip-flow", pp::app::BrushStrokeFloatSetting::tip_flow },
|
||||
{ "tip-opacity", pp::app::BrushStrokeFloatSetting::tip_opacity },
|
||||
{ "tip-angle", pp::app::BrushStrokeFloatSetting::tip_angle },
|
||||
{ "tip-angle-smooth", pp::app::BrushStrokeFloatSetting::tip_angle_smooth },
|
||||
{ "tip-mix", pp::app::BrushStrokeFloatSetting::tip_mix },
|
||||
{ "tip-wet", pp::app::BrushStrokeFloatSetting::tip_wet },
|
||||
{ "tip-noise", pp::app::BrushStrokeFloatSetting::tip_noise },
|
||||
{ "tip-hue", pp::app::BrushStrokeFloatSetting::tip_hue },
|
||||
{ "tip-saturation", pp::app::BrushStrokeFloatSetting::tip_saturation },
|
||||
{ "tip-sat", pp::app::BrushStrokeFloatSetting::tip_saturation },
|
||||
{ "tip-value", pp::app::BrushStrokeFloatSetting::tip_value },
|
||||
{ "tip-val", pp::app::BrushStrokeFloatSetting::tip_value },
|
||||
{ "jitter-scale", pp::app::BrushStrokeFloatSetting::jitter_scale },
|
||||
{ "jitter-angle", pp::app::BrushStrokeFloatSetting::jitter_angle },
|
||||
{ "jitter-scatter", pp::app::BrushStrokeFloatSetting::jitter_scatter },
|
||||
{ "jitter-flow", pp::app::BrushStrokeFloatSetting::jitter_flow },
|
||||
{ "jitter-opacity", pp::app::BrushStrokeFloatSetting::jitter_opacity },
|
||||
{ "jitter-hue", pp::app::BrushStrokeFloatSetting::jitter_hue },
|
||||
{ "jitter-saturation", pp::app::BrushStrokeFloatSetting::jitter_saturation },
|
||||
{ "jitter-sat", pp::app::BrushStrokeFloatSetting::jitter_saturation },
|
||||
{ "jitter-value", pp::app::BrushStrokeFloatSetting::jitter_value },
|
||||
{ "jitter-val", pp::app::BrushStrokeFloatSetting::jitter_value },
|
||||
{ "jitter-aspect", pp::app::BrushStrokeFloatSetting::jitter_aspect },
|
||||
{ "dual-size", pp::app::BrushStrokeFloatSetting::dual_size },
|
||||
{ "dual-spacing", pp::app::BrushStrokeFloatSetting::dual_spacing },
|
||||
{ "dual-scatter", pp::app::BrushStrokeFloatSetting::dual_scatter },
|
||||
{ "tip-aspect", pp::app::BrushStrokeFloatSetting::tip_aspect },
|
||||
{ "dual-opacity", pp::app::BrushStrokeFloatSetting::dual_opacity },
|
||||
{ "dual-flow", pp::app::BrushStrokeFloatSetting::dual_flow },
|
||||
{ "dual-rotate", pp::app::BrushStrokeFloatSetting::dual_rotate },
|
||||
} };
|
||||
for (const auto& entry : settings) {
|
||||
if (setting == entry.first) {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeFloatSetting>::success(entry.second);
|
||||
}
|
||||
}
|
||||
if (setting == "pattern-scale") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeFloatSetting>::success(
|
||||
pp::app::BrushStrokeFloatSetting::pattern_scale);
|
||||
}
|
||||
if (setting == "pattern-brightness") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeFloatSetting>::success(
|
||||
pp::app::BrushStrokeFloatSetting::pattern_brightness);
|
||||
}
|
||||
if (setting == "pattern-contrast") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeFloatSetting>::success(
|
||||
pp::app::BrushStrokeFloatSetting::pattern_contrast);
|
||||
}
|
||||
if (setting == "pattern-depth") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeFloatSetting>::success(
|
||||
pp::app::BrushStrokeFloatSetting::pattern_depth);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<pp::app::BrushStrokeFloatSetting>::failure(
|
||||
pp::foundation::Status::invalid_argument("unknown brush stroke float setting"));
|
||||
}
|
||||
|
||||
pp::foundation::Result<pp::app::BrushStrokeBoolSetting> parse_brush_stroke_bool_setting(
|
||||
std::string_view setting)
|
||||
{
|
||||
static constexpr std::array<std::pair<std::string_view, pp::app::BrushStrokeBoolSetting>, 25> settings{ {
|
||||
{ "tip-angle-init", pp::app::BrushStrokeBoolSetting::tip_angle_init },
|
||||
{ "tip-angle-follow", pp::app::BrushStrokeBoolSetting::tip_angle_follow },
|
||||
{ "tip-flow-pressure", pp::app::BrushStrokeBoolSetting::tip_flow_pressure },
|
||||
{ "tip-opacity-pressure", pp::app::BrushStrokeBoolSetting::tip_opacity_pressure },
|
||||
{ "tip-size-pressure", pp::app::BrushStrokeBoolSetting::tip_size_pressure },
|
||||
{ "jitter-scatter-both-axis", pp::app::BrushStrokeBoolSetting::jitter_scatter_both_axis },
|
||||
{ "jitter-scatter-bothaxis", pp::app::BrushStrokeBoolSetting::jitter_scatter_both_axis },
|
||||
{ "jitter-aspect-both-axis", pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis },
|
||||
{ "jitter-aspect-bothaxis", pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis },
|
||||
{ "jitter-hsv-each-sample", pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample },
|
||||
{ "jitter-hsv-eachsample", pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample },
|
||||
{ "tip-invert", pp::app::BrushStrokeBoolSetting::tip_invert },
|
||||
{ "tip-flip-x", pp::app::BrushStrokeBoolSetting::tip_flip_x },
|
||||
{ "tip-flipx", pp::app::BrushStrokeBoolSetting::tip_flip_x },
|
||||
{ "tip-flip-y", pp::app::BrushStrokeBoolSetting::tip_flip_y },
|
||||
{ "tip-flipy", pp::app::BrushStrokeBoolSetting::tip_flip_y },
|
||||
{ "pattern-enabled", pp::app::BrushStrokeBoolSetting::pattern_enabled },
|
||||
{ "dual-enabled", pp::app::BrushStrokeBoolSetting::dual_enabled },
|
||||
{ "dual-scatter-both-axis", pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis },
|
||||
{ "dual-scatter-bothaxis", pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis },
|
||||
{ "dual-invert", pp::app::BrushStrokeBoolSetting::dual_invert },
|
||||
{ "dual-flip-x", pp::app::BrushStrokeBoolSetting::dual_flip_x },
|
||||
{ "dual-flipx", pp::app::BrushStrokeBoolSetting::dual_flip_x },
|
||||
{ "dual-flip-y", pp::app::BrushStrokeBoolSetting::dual_flip_y },
|
||||
{ "dual-flipy", pp::app::BrushStrokeBoolSetting::dual_flip_y },
|
||||
} };
|
||||
for (const auto& entry : settings) {
|
||||
if (setting == entry.first) {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBoolSetting>::success(entry.second);
|
||||
}
|
||||
}
|
||||
if (setting == "dual-random-flip" || setting == "dual-randflip") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBoolSetting>::success(
|
||||
pp::app::BrushStrokeBoolSetting::dual_random_flip);
|
||||
}
|
||||
if (setting == "tip-random-flip-x" || setting == "tip-randflipx") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBoolSetting>::success(
|
||||
pp::app::BrushStrokeBoolSetting::tip_random_flip_x);
|
||||
}
|
||||
if (setting == "tip-random-flip-y" || setting == "tip-randflipy") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBoolSetting>::success(
|
||||
pp::app::BrushStrokeBoolSetting::tip_random_flip_y);
|
||||
}
|
||||
if (setting == "pattern-each-sample" || setting == "pattern-eachsample") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBoolSetting>::success(
|
||||
pp::app::BrushStrokeBoolSetting::pattern_each_sample);
|
||||
}
|
||||
if (setting == "pattern-invert") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBoolSetting>::success(
|
||||
pp::app::BrushStrokeBoolSetting::pattern_invert);
|
||||
}
|
||||
if (setting == "pattern-flip-x" || setting == "pattern-flipx") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBoolSetting>::success(
|
||||
pp::app::BrushStrokeBoolSetting::pattern_flip_x);
|
||||
}
|
||||
if (setting == "pattern-flip-y" || setting == "pattern-flipy") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBoolSetting>::success(
|
||||
pp::app::BrushStrokeBoolSetting::pattern_flip_y);
|
||||
}
|
||||
if (setting == "pattern-random-offset" || setting == "pattern-rand-offset") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBoolSetting>::success(
|
||||
pp::app::BrushStrokeBoolSetting::pattern_random_offset);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBoolSetting>::failure(
|
||||
pp::foundation::Status::invalid_argument("unknown brush stroke bool setting"));
|
||||
}
|
||||
|
||||
pp::foundation::Result<pp::app::BrushStrokeBlendSetting> parse_brush_stroke_blend_setting(
|
||||
std::string_view setting)
|
||||
{
|
||||
if (setting == "tip" || setting == "brush") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBlendSetting>::success(
|
||||
pp::app::BrushStrokeBlendSetting::tip);
|
||||
}
|
||||
if (setting == "dual") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBlendSetting>::success(
|
||||
pp::app::BrushStrokeBlendSetting::dual);
|
||||
}
|
||||
if (setting == "pattern") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBlendSetting>::success(
|
||||
pp::app::BrushStrokeBlendSetting::pattern);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<pp::app::BrushStrokeBlendSetting>::failure(
|
||||
pp::foundation::Status::invalid_argument("unknown brush stroke blend setting"));
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_brush_stroke_control_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
PlanBrushStrokeControlArgs& args)
|
||||
{
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const std::string_view key(argv[i]);
|
||||
if (key == "--kind" || key == "--setting") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
if (key == "--kind") {
|
||||
args.kind = argv[++i];
|
||||
} else {
|
||||
args.setting = argv[++i];
|
||||
}
|
||||
} else if (key == "--value") {
|
||||
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();
|
||||
}
|
||||
args.value = value.value();
|
||||
} else if (key == "--blend-mode") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
const auto value = parse_i32_arg(argv[++i]);
|
||||
if (!value) {
|
||||
return value.status();
|
||||
}
|
||||
args.blend_mode = value.value();
|
||||
} else if (key == "--enabled") {
|
||||
args.enabled = true;
|
||||
} else if (key == "--disabled") {
|
||||
args.enabled = false;
|
||||
} else {
|
||||
return pp::foundation::Status::invalid_argument("unknown option");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Result<pp::app::BrushStrokeControlPlan> make_brush_stroke_control_plan(
|
||||
const PlanBrushStrokeControlArgs& args)
|
||||
{
|
||||
if (args.kind == "float" || args.kind == "slider") {
|
||||
const auto setting = parse_brush_stroke_float_setting(args.setting);
|
||||
if (!setting) {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeControlPlan>::failure(setting.status());
|
||||
}
|
||||
return pp::app::plan_brush_stroke_float_setting(setting.value(), args.value);
|
||||
}
|
||||
if (args.kind == "bool" || args.kind == "toggle" || args.kind == "checkbox") {
|
||||
const auto setting = parse_brush_stroke_bool_setting(args.setting);
|
||||
if (!setting) {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeControlPlan>::failure(setting.status());
|
||||
}
|
||||
return pp::foundation::Result<pp::app::BrushStrokeControlPlan>::success(
|
||||
pp::app::plan_brush_stroke_bool_setting(setting.value(), args.enabled));
|
||||
}
|
||||
if (args.kind == "blend" || args.kind == "blend-mode") {
|
||||
const auto setting = parse_brush_stroke_blend_setting(args.setting);
|
||||
if (!setting) {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeControlPlan>::failure(setting.status());
|
||||
}
|
||||
return pp::app::plan_brush_stroke_blend_mode(setting.value(), args.blend_mode);
|
||||
}
|
||||
if (args.kind == "tip-aspect-reset") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeControlPlan>::success(
|
||||
pp::app::plan_brush_tip_aspect_reset());
|
||||
}
|
||||
if (args.kind == "default-reset" || args.kind == "reset") {
|
||||
return pp::foundation::Result<pp::app::BrushStrokeControlPlan>::success(
|
||||
pp::app::plan_brush_default_settings_reset());
|
||||
}
|
||||
|
||||
return pp::foundation::Result<pp::app::BrushStrokeControlPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("unknown brush stroke control kind"));
|
||||
}
|
||||
|
||||
int plan_brush_stroke_control(int argc, char** argv)
|
||||
{
|
||||
PlanBrushStrokeControlArgs args;
|
||||
const auto status = parse_plan_brush_stroke_control_args(argc, argv, args);
|
||||
if (!status.ok()) {
|
||||
print_error("plan-brush-stroke-control", status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto plan = make_brush_stroke_control_plan(args);
|
||||
if (!plan) {
|
||||
print_error("plan-brush-stroke-control", plan.status().message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto& value = plan.value();
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-brush-stroke-control\""
|
||||
<< ",\"state\":{\"kind\":\"" << json_escape(args.kind)
|
||||
<< "\",\"setting\":\"" << json_escape(args.setting)
|
||||
<< "\",\"value\":" << args.value
|
||||
<< ",\"enabled\":" << json_bool(args.enabled)
|
||||
<< ",\"blendMode\":" << args.blend_mode
|
||||
<< "},\"plan\":{\"operation\":\"" << brush_stroke_control_operation_name(value.operation)
|
||||
<< "\",\"floatSetting\":\"" << brush_stroke_float_setting_name(value.float_setting)
|
||||
<< "\",\"boolSetting\":\"" << brush_stroke_bool_setting_name(value.bool_setting)
|
||||
<< "\",\"blendSetting\":\"" << brush_stroke_blend_setting_name(value.blend_setting)
|
||||
<< "\",\"floatValue\":" << value.float_value
|
||||
<< ",\"boolValue\":" << json_bool(value.bool_value)
|
||||
<< ",\"blendMode\":" << value.blend_mode
|
||||
<< ",\"mutatesBrush\":" << json_bool(value.mutates_brush)
|
||||
<< ",\"updatesControls\":" << json_bool(value.updates_controls)
|
||||
<< ",\"refreshesPreview\":" << json_bool(value.refreshes_preview)
|
||||
<< ",\"notifiesStrokeChange\":" << json_bool(value.notifies_stroke_change)
|
||||
<< "}}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_canvas_tool_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
@@ -7545,6 +7935,10 @@ int main(int argc, char** argv)
|
||||
return plan_brush_texture_list(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-brush-stroke-control") {
|
||||
return plan_brush_stroke_control(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-canvas-tool") {
|
||||
return plan_canvas_tool(argc, argv);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user