Files
panopainter/src/app_core/brush_ui.h

863 lines
30 KiB
C++

#pragma once
#include "foundation/result.h"
#include <algorithm>
#include <cmath>
#include <string>
#include <string_view>
namespace pp::app {
enum class BrushUiTextureSlot {
tip,
pattern,
dual,
};
enum class BrushUiOperation {
set_tip_color,
set_texture,
replace_brush_from_preset,
stroke_settings_changed,
};
enum class BrushTextureListOperation {
add_texture,
remove_texture,
move_texture,
};
enum class BrushPresetListOperation {
add_current_brush,
remove_preset,
move_preset,
select_preset,
clear_presets,
};
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;
std::string path;
std::string thumbnail_path;
float r = 0.0F;
float g = 0.0F;
float b = 0.0F;
float a = 1.0F;
bool mutates_brush = false;
bool preserves_existing_color = false;
bool loads_brush_resources = false;
bool update_color_ui = false;
bool update_brush_ui = false;
};
struct BrushTextureListPlan {
BrushTextureListOperation operation = BrushTextureListOperation::add_texture;
int item_count = 0;
int current_index = -1;
int target_index = -1;
int move_offset = 0;
std::string source_path;
std::string high_path;
std::string thumbnail_path;
std::string brush_name;
bool user_texture = false;
bool deletes_texture_files = false;
bool saves_list = false;
bool notifies_selection = false;
bool converts_brush_alpha = false;
bool no_op = false;
};
struct BrushPresetListPlan {
BrushPresetListOperation operation = BrushPresetListOperation::select_preset;
int item_count = 0;
int current_index = -1;
int target_index = -1;
int move_offset = 0;
bool saves_list = false;
bool updates_empty_notification = false;
bool selects_target = false;
bool clears_selection = false;
bool notifies_brush_changed = false;
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;
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;
};
class BrushTextureListServices {
public:
virtual ~BrushTextureListServices() = default;
virtual pp::foundation::Status add_texture_from_source(
std::string_view source_path,
std::string_view high_path,
std::string_view thumbnail_path,
std::string_view brush_name,
bool converts_brush_alpha) = 0;
virtual void remove_texture(int index, bool delete_texture_files) = 0;
virtual void move_texture(int from_index, int to_index) = 0;
virtual void select_texture(int index) = 0;
virtual void save_texture_list() = 0;
};
class BrushPresetListServices {
public:
virtual ~BrushPresetListServices() = default;
virtual pp::foundation::Status add_current_brush_preset(int target_index) = 0;
virtual void remove_brush_preset(
int current_index,
int target_index,
bool selects_target,
bool clears_selection) = 0;
virtual void move_brush_preset(int from_index, int to_index) = 0;
virtual void select_brush_preset(int index, bool notify_brush_changed) = 0;
virtual void clear_brush_presets(bool clears_selection) = 0;
virtual void update_preset_empty_notification() = 0;
virtual void save_preset_list() = 0;
};
class BrushStrokeControlServices {
public:
virtual ~BrushStrokeControlServices() = default;
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
{
const auto slash = source_path.find_last_of("/\\");
const auto name_begin = slash == std::string_view::npos ? 0U : slash + 1U;
if (name_begin >= source_path.size()) {
return pp::foundation::Result<std::string_view>::failure(
pp::foundation::Status::invalid_argument("brush texture source path must contain a file name"));
}
const auto dot = source_path.find_last_of('.');
if (dot == std::string_view::npos || dot <= name_begin || dot + 1U >= source_path.size()) {
return pp::foundation::Result<std::string_view>::failure(
pp::foundation::Status::invalid_argument("brush texture source path must include a file extension"));
}
return pp::foundation::Result<std::string_view>::success(source_path.substr(name_begin, dot - name_begin));
}
[[nodiscard]] inline pp::foundation::Status validate_brush_ui_color_channel(float value) noexcept
{
if (!std::isfinite(value) || value < 0.0F || value > 1.0F) {
return pp::foundation::Status::out_of_range("brush color channels must be finite and within 0..1");
}
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,
float b,
float a)
{
for (const auto value : { r, g, b, a }) {
const auto channel_status = validate_brush_ui_color_channel(value);
if (!channel_status.ok()) {
return pp::foundation::Result<BrushUiPlan>::failure(channel_status);
}
}
BrushUiPlan plan;
plan.operation = BrushUiOperation::set_tip_color;
plan.r = r;
plan.g = g;
plan.b = b;
plan.a = a;
plan.mutates_brush = true;
plan.update_color_ui = true;
return pp::foundation::Result<BrushUiPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushUiPlan> plan_brush_ui_texture(
BrushUiTextureSlot slot,
std::string_view path,
std::string_view thumbnail_path)
{
if (path.empty()) {
return pp::foundation::Result<BrushUiPlan>::failure(
pp::foundation::Status::invalid_argument("brush texture path must not be empty"));
}
BrushUiPlan plan;
plan.operation = BrushUiOperation::set_texture;
plan.texture_slot = slot;
plan.path = std::string(path);
plan.thumbnail_path = std::string(thumbnail_path);
plan.mutates_brush = true;
plan.loads_brush_resources = true;
plan.update_color_ui = true;
plan.update_brush_ui = true;
return pp::foundation::Result<BrushUiPlan>::success(std::move(plan));
}
[[nodiscard]] inline pp::foundation::Result<BrushUiPlan> plan_brush_ui_preset_replace(bool has_preset_brush)
{
if (!has_preset_brush) {
return pp::foundation::Result<BrushUiPlan>::failure(
pp::foundation::Status::invalid_argument("preset brush must be available"));
}
BrushUiPlan plan;
plan.operation = BrushUiOperation::replace_brush_from_preset;
plan.mutates_brush = true;
plan.preserves_existing_color = true;
plan.loads_brush_resources = true;
plan.update_color_ui = true;
plan.update_brush_ui = true;
return pp::foundation::Result<BrushUiPlan>::success(plan);
}
[[nodiscard]] inline constexpr BrushUiPlan plan_brush_ui_stroke_settings_changed() noexcept
{
BrushUiPlan plan;
plan.operation = BrushUiOperation::stroke_settings_changed;
plan.mutates_brush = true;
plan.update_color_ui = true;
plan.update_brush_ui = true;
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,
std::string_view source_path)
{
if (directory_name.empty()) {
return pp::foundation::Result<BrushTextureListPlan>::failure(
pp::foundation::Status::invalid_argument("brush texture directory must not be empty"));
}
if (data_path.empty()) {
return pp::foundation::Result<BrushTextureListPlan>::failure(
pp::foundation::Status::invalid_argument("brush texture data path must not be empty"));
}
const auto stem = brush_texture_source_stem(source_path);
if (!stem) {
return pp::foundation::Result<BrushTextureListPlan>::failure(stem.status());
}
BrushTextureListPlan plan;
plan.operation = BrushTextureListOperation::add_texture;
plan.source_path = std::string(source_path);
plan.brush_name = std::string(stem.value());
plan.high_path = std::string(data_path) + "/" + std::string(directory_name) + "/" + plan.brush_name + ".png";
plan.thumbnail_path = std::string(data_path) + "/" + std::string(directory_name) + "/thumbs/"
+ plan.brush_name + ".png";
plan.user_texture = true;
plan.saves_list = true;
plan.converts_brush_alpha = directory_name == "brushes";
return pp::foundation::Result<BrushTextureListPlan>::success(std::move(plan));
}
[[nodiscard]] inline pp::foundation::Result<BrushTextureListPlan> plan_brush_texture_list_remove(
int item_count,
int current_index,
bool current_is_user_texture)
{
if (item_count <= 0) {
return pp::foundation::Result<BrushTextureListPlan>::failure(
pp::foundation::Status::invalid_argument("brush texture list must contain an item to remove"));
}
if (current_index < 0 || current_index >= item_count) {
return pp::foundation::Result<BrushTextureListPlan>::failure(
pp::foundation::Status::out_of_range("selected brush texture index is outside the list"));
}
BrushTextureListPlan plan;
plan.operation = BrushTextureListOperation::remove_texture;
plan.item_count = item_count;
plan.current_index = current_index;
plan.target_index = item_count > 1 ? std::min(current_index, item_count - 2) : -1;
plan.user_texture = current_is_user_texture;
plan.deletes_texture_files = current_is_user_texture;
plan.saves_list = true;
plan.notifies_selection = plan.target_index >= 0;
return pp::foundation::Result<BrushTextureListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushTextureListPlan> plan_brush_texture_list_move(
int item_count,
int current_index,
int offset)
{
if (item_count <= 0) {
return pp::foundation::Result<BrushTextureListPlan>::failure(
pp::foundation::Status::invalid_argument("brush texture list must contain an item to move"));
}
if (current_index < 0 || current_index >= item_count) {
return pp::foundation::Result<BrushTextureListPlan>::failure(
pp::foundation::Status::out_of_range("selected brush texture index is outside the list"));
}
if (offset == 0) {
return pp::foundation::Result<BrushTextureListPlan>::failure(
pp::foundation::Status::invalid_argument("brush texture move offset must not be zero"));
}
BrushTextureListPlan plan;
plan.operation = BrushTextureListOperation::move_texture;
plan.item_count = item_count;
plan.current_index = current_index;
plan.target_index = std::clamp(current_index + offset, 0, item_count - 1);
plan.move_offset = offset;
plan.saves_list = true;
plan.no_op = plan.target_index == current_index;
return pp::foundation::Result<BrushTextureListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushPresetListPlan> plan_brush_preset_list_add(
int item_count,
bool has_current_brush)
{
if (item_count < 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::out_of_range("brush preset item count must not be negative"));
}
if (!has_current_brush) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::invalid_argument("current brush must be available to add a preset"));
}
BrushPresetListPlan plan;
plan.operation = BrushPresetListOperation::add_current_brush;
plan.item_count = item_count;
plan.target_index = item_count;
plan.saves_list = true;
plan.updates_empty_notification = true;
return pp::foundation::Result<BrushPresetListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushPresetListPlan> plan_brush_preset_list_remove(
int item_count,
int current_index)
{
if (item_count <= 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::invalid_argument("brush preset list must contain an item to remove"));
}
if (current_index < 0 || current_index >= item_count) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::out_of_range("selected brush preset index is outside the list"));
}
BrushPresetListPlan plan;
plan.operation = BrushPresetListOperation::remove_preset;
plan.item_count = item_count;
plan.current_index = current_index;
plan.target_index = item_count > 1 ? std::min(current_index, item_count - 2) : -1;
plan.saves_list = true;
plan.updates_empty_notification = true;
plan.selects_target = plan.target_index >= 0;
plan.clears_selection = plan.target_index < 0;
return pp::foundation::Result<BrushPresetListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushPresetListPlan> plan_brush_preset_list_move(
int item_count,
int current_index,
int offset)
{
if (item_count <= 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::invalid_argument("brush preset list must contain an item to move"));
}
if (current_index < 0 || current_index >= item_count) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::out_of_range("selected brush preset index is outside the list"));
}
if (offset == 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::invalid_argument("brush preset move offset must not be zero"));
}
BrushPresetListPlan plan;
plan.operation = BrushPresetListOperation::move_preset;
plan.item_count = item_count;
plan.current_index = current_index;
plan.target_index = std::clamp(current_index + offset, 0, item_count - 1);
plan.move_offset = offset;
plan.saves_list = true;
plan.no_op = plan.target_index == current_index;
return pp::foundation::Result<BrushPresetListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushPresetListPlan> plan_brush_preset_list_select(
int item_count,
int index)
{
if (item_count <= 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::invalid_argument("brush preset list must contain an item to select"));
}
if (index < 0 || index >= item_count) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::out_of_range("selected brush preset index is outside the list"));
}
BrushPresetListPlan plan;
plan.operation = BrushPresetListOperation::select_preset;
plan.item_count = item_count;
plan.current_index = index;
plan.target_index = index;
plan.selects_target = true;
plan.notifies_brush_changed = true;
return pp::foundation::Result<BrushPresetListPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<BrushPresetListPlan> plan_brush_preset_list_clear(int item_count)
{
if (item_count < 0) {
return pp::foundation::Result<BrushPresetListPlan>::failure(
pp::foundation::Status::out_of_range("brush preset item count must not be negative"));
}
BrushPresetListPlan plan;
plan.operation = BrushPresetListOperation::clear_presets;
plan.item_count = item_count;
plan.saves_list = true;
plan.updates_empty_notification = true;
plan.clears_selection = true;
plan.no_op = item_count == 0;
return pp::foundation::Result<BrushPresetListPlan>::success(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");
}
[[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)
{
switch (plan.operation) {
case BrushTextureListOperation::add_texture:
{
if (plan.source_path.empty() || plan.high_path.empty() || plan.thumbnail_path.empty()
|| plan.brush_name.empty()) {
return pp::foundation::Status::invalid_argument("brush texture add plan has incomplete paths");
}
const auto add_status = services.add_texture_from_source(
plan.source_path,
plan.high_path,
plan.thumbnail_path,
plan.brush_name,
plan.converts_brush_alpha);
if (!add_status.ok()) {
return add_status;
}
if (plan.saves_list) {
services.save_texture_list();
}
return pp::foundation::Status::success();
}
case BrushTextureListOperation::remove_texture:
if (plan.item_count <= 0 || plan.current_index < 0 || plan.current_index >= plan.item_count) {
return pp::foundation::Status::out_of_range("brush texture remove plan has invalid selection");
}
services.remove_texture(plan.current_index, plan.deletes_texture_files);
if (plan.notifies_selection && plan.target_index >= 0) {
services.select_texture(plan.target_index);
}
if (plan.saves_list) {
services.save_texture_list();
}
return pp::foundation::Status::success();
case BrushTextureListOperation::move_texture:
if (plan.item_count <= 0 || plan.current_index < 0 || plan.current_index >= plan.item_count
|| plan.target_index < 0 || plan.target_index >= plan.item_count) {
return pp::foundation::Status::out_of_range("brush texture move plan has invalid indices");
}
services.move_texture(plan.current_index, plan.target_index);
if (plan.saves_list) {
services.save_texture_list();
}
return pp::foundation::Status::success();
}
return pp::foundation::Status::invalid_argument("unknown brush texture list operation");
}
[[nodiscard]] inline pp::foundation::Status execute_brush_preset_list_plan(
const BrushPresetListPlan& plan,
BrushPresetListServices& services)
{
switch (plan.operation) {
case BrushPresetListOperation::add_current_brush:
{
if (plan.item_count < 0 || plan.target_index != plan.item_count) {
return pp::foundation::Status::out_of_range("brush preset add plan has invalid target");
}
const auto add_status = services.add_current_brush_preset(plan.target_index);
if (!add_status.ok()) {
return add_status;
}
if (plan.updates_empty_notification) {
services.update_preset_empty_notification();
}
if (plan.saves_list) {
services.save_preset_list();
}
return pp::foundation::Status::success();
}
case BrushPresetListOperation::remove_preset:
if (plan.item_count <= 0 || plan.current_index < 0 || plan.current_index >= plan.item_count) {
return pp::foundation::Status::out_of_range("brush preset remove plan has invalid selection");
}
if (plan.selects_target && (plan.target_index < 0 || plan.target_index >= plan.item_count - 1)) {
return pp::foundation::Status::out_of_range("brush preset remove plan has invalid target");
}
services.remove_brush_preset(
plan.current_index,
plan.target_index,
plan.selects_target,
plan.clears_selection);
if (plan.updates_empty_notification) {
services.update_preset_empty_notification();
}
if (plan.saves_list) {
services.save_preset_list();
}
return pp::foundation::Status::success();
case BrushPresetListOperation::move_preset:
if (plan.item_count <= 0 || plan.current_index < 0 || plan.current_index >= plan.item_count
|| plan.target_index < 0 || plan.target_index >= plan.item_count) {
return pp::foundation::Status::out_of_range("brush preset move plan has invalid indices");
}
services.move_brush_preset(plan.current_index, plan.target_index);
if (plan.saves_list) {
services.save_preset_list();
}
return pp::foundation::Status::success();
case BrushPresetListOperation::select_preset:
if (plan.item_count <= 0 || plan.target_index < 0 || plan.target_index >= plan.item_count) {
return pp::foundation::Status::out_of_range("brush preset select plan has invalid target");
}
services.select_brush_preset(plan.target_index, plan.notifies_brush_changed);
return pp::foundation::Status::success();
case BrushPresetListOperation::clear_presets:
if (plan.item_count < 0) {
return pp::foundation::Status::out_of_range("brush preset clear plan has invalid item count");
}
services.clear_brush_presets(plan.clears_selection);
if (plan.updates_empty_notification) {
services.update_preset_empty_notification();
}
if (plan.saves_list) {
services.save_preset_list();
}
return pp::foundation::Status::success();
}
return pp::foundation::Status::invalid_argument("unknown brush preset list operation");
}
} // namespace pp::app