172 lines
5.4 KiB
C++
172 lines
5.4 KiB
C++
#pragma once
|
|
|
|
#include "foundation/result.h"
|
|
|
|
#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,
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
class BrushUiServices {
|
|
public:
|
|
virtual ~BrushUiServices() = default;
|
|
|
|
virtual void set_tip_color(float r, float g, float b, float a) = 0;
|
|
virtual void set_texture(BrushUiTextureSlot slot, std::string_view path, std::string_view thumbnail_path) = 0;
|
|
virtual void replace_brush_from_preset(bool preserve_existing_color, bool load_resources) = 0;
|
|
virtual void refresh_brush_ui(bool update_color_ui, bool update_brush_ui) = 0;
|
|
};
|
|
|
|
[[nodiscard]] inline pp::foundation::Status validate_brush_ui_color_channel(float value) noexcept
|
|
{
|
|
if (!std::isfinite(value) || value < 0.0F || value > 1.0F) {
|
|
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::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::Status execute_brush_ui_plan(
|
|
const BrushUiPlan& plan,
|
|
BrushUiServices& services)
|
|
{
|
|
switch (plan.operation) {
|
|
case BrushUiOperation::set_tip_color:
|
|
{
|
|
for (const auto value : { plan.r, plan.g, plan.b, plan.a }) {
|
|
const auto channel_status = validate_brush_ui_color_channel(value);
|
|
if (!channel_status.ok()) {
|
|
return channel_status;
|
|
}
|
|
}
|
|
services.set_tip_color(plan.r, plan.g, plan.b, plan.a);
|
|
services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
case BrushUiOperation::set_texture:
|
|
if (plan.path.empty()) {
|
|
return pp::foundation::Status::invalid_argument("brush texture path must not be empty");
|
|
}
|
|
services.set_texture(plan.texture_slot, plan.path, plan.thumbnail_path);
|
|
services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
|
|
return pp::foundation::Status::success();
|
|
|
|
case BrushUiOperation::replace_brush_from_preset:
|
|
services.replace_brush_from_preset(plan.preserves_existing_color, plan.loads_brush_resources);
|
|
services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
|
|
return pp::foundation::Status::success();
|
|
|
|
case BrushUiOperation::stroke_settings_changed:
|
|
services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
return pp::foundation::Status::invalid_argument("unknown brush UI operation");
|
|
}
|
|
|
|
} // namespace pp::app
|