Files
panopainter/src/app_core/brush_ui.h

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