316 lines
11 KiB
C++
316 lines
11 KiB
C++
#pragma once
|
|
|
|
#include "foundation/result.h"
|
|
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <string_view>
|
|
|
|
namespace pp::app {
|
|
|
|
enum class CanvasToolOperation {
|
|
select_mode,
|
|
toggle_picking,
|
|
toggle_touch_lock,
|
|
};
|
|
|
|
enum class CanvasToolMode {
|
|
draw,
|
|
erase,
|
|
line,
|
|
camera,
|
|
grid,
|
|
copy,
|
|
cut,
|
|
fill,
|
|
mask_free,
|
|
mask_line,
|
|
flood_fill,
|
|
};
|
|
|
|
enum class CanvasToolTransformAction {
|
|
none,
|
|
copy,
|
|
cut,
|
|
};
|
|
|
|
enum class CanvasToolToolbarAction {
|
|
select_mode,
|
|
toggle_picking,
|
|
toggle_touch_lock,
|
|
};
|
|
|
|
enum class CanvasCursorVisibilityMode {
|
|
never,
|
|
small_brush,
|
|
not_painting,
|
|
always,
|
|
};
|
|
|
|
struct CanvasToolPlan {
|
|
CanvasToolOperation operation = CanvasToolOperation::select_mode;
|
|
CanvasToolMode mode = CanvasToolMode::draw;
|
|
CanvasToolTransformAction transform_action = CanvasToolTransformAction::none;
|
|
bool selects_toolbar_button = false;
|
|
bool updates_canvas_mode = false;
|
|
bool toggles_picking = false;
|
|
bool toggles_touch_lock = false;
|
|
bool requires_draw_mode = false;
|
|
bool no_op = false;
|
|
};
|
|
|
|
struct CanvasToolButtonState {
|
|
CanvasToolMode mode = CanvasToolMode::draw;
|
|
bool pick_active = false;
|
|
bool touch_lock_active = false;
|
|
bool pen_active = false;
|
|
bool erase_active = false;
|
|
bool line_active = false;
|
|
bool camera_active = false;
|
|
bool grid_active = false;
|
|
bool copy_active = false;
|
|
bool cut_active = false;
|
|
bool fill_active = false;
|
|
bool mask_free_active = false;
|
|
bool mask_line_active = false;
|
|
bool flood_fill_active = false;
|
|
};
|
|
|
|
struct CanvasToolToolbarBinding {
|
|
std::string_view button_id;
|
|
CanvasToolToolbarAction action = CanvasToolToolbarAction::select_mode;
|
|
CanvasToolMode mode = CanvasToolMode::draw;
|
|
bool custom_button = true;
|
|
bool applies_default_on_init = false;
|
|
};
|
|
|
|
struct CanvasToolToolbarPlan {
|
|
std::array<CanvasToolToolbarBinding, 13> bindings {};
|
|
CanvasToolMode default_mode = CanvasToolMode::draw;
|
|
};
|
|
|
|
struct CanvasCursorVisibilityInput {
|
|
CanvasToolMode mode = CanvasToolMode::draw;
|
|
CanvasCursorVisibilityMode visibility_mode = CanvasCursorVisibilityMode::never;
|
|
bool has_current_brush = true;
|
|
float brush_tip_size = 0.0F;
|
|
bool pen_is_drawing = false;
|
|
bool alt_down = false;
|
|
bool pen_is_resizing = false;
|
|
bool pen_is_picking = false;
|
|
};
|
|
|
|
struct CanvasCursorVisibilityPlan {
|
|
bool visible = true;
|
|
bool paint_mode = false;
|
|
bool uses_brush_size = false;
|
|
bool uses_pen_state = false;
|
|
bool forced_visible_by_modifier_or_tool = false;
|
|
};
|
|
|
|
class CanvasToolServices {
|
|
public:
|
|
virtual ~CanvasToolServices() = default;
|
|
|
|
virtual void select_toolbar_button(CanvasToolMode mode) = 0;
|
|
virtual void set_transform_action(CanvasToolTransformAction action) = 0;
|
|
virtual void set_canvas_mode(CanvasToolMode mode) = 0;
|
|
virtual void toggle_picking() = 0;
|
|
virtual void toggle_touch_lock() = 0;
|
|
};
|
|
|
|
[[nodiscard]] inline constexpr CanvasToolTransformAction transform_action_for_mode(CanvasToolMode mode) noexcept
|
|
{
|
|
if (mode == CanvasToolMode::copy) {
|
|
return CanvasToolTransformAction::copy;
|
|
}
|
|
if (mode == CanvasToolMode::cut) {
|
|
return CanvasToolTransformAction::cut;
|
|
}
|
|
return CanvasToolTransformAction::none;
|
|
}
|
|
|
|
[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_select(CanvasToolMode mode) noexcept
|
|
{
|
|
CanvasToolPlan plan;
|
|
plan.operation = CanvasToolOperation::select_mode;
|
|
plan.mode = mode;
|
|
plan.transform_action = transform_action_for_mode(mode);
|
|
plan.selects_toolbar_button = true;
|
|
plan.updates_canvas_mode = true;
|
|
return plan;
|
|
}
|
|
|
|
[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_pick_toggle(bool current_mode_is_draw) noexcept
|
|
{
|
|
CanvasToolPlan plan;
|
|
plan.operation = CanvasToolOperation::toggle_picking;
|
|
plan.mode = CanvasToolMode::draw;
|
|
plan.requires_draw_mode = true;
|
|
plan.toggles_picking = current_mode_is_draw;
|
|
plan.no_op = !current_mode_is_draw;
|
|
return plan;
|
|
}
|
|
|
|
[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_touch_lock_toggle() noexcept
|
|
{
|
|
CanvasToolPlan plan;
|
|
plan.operation = CanvasToolOperation::toggle_touch_lock;
|
|
plan.toggles_touch_lock = true;
|
|
return plan;
|
|
}
|
|
|
|
[[nodiscard]] inline constexpr CanvasToolToolbarPlan plan_canvas_tool_toolbar() noexcept
|
|
{
|
|
return {
|
|
std::array<CanvasToolToolbarBinding, 13> {
|
|
CanvasToolToolbarBinding { "btn-pen", CanvasToolToolbarAction::select_mode, CanvasToolMode::draw, true, true },
|
|
CanvasToolToolbarBinding { "btn-pick", CanvasToolToolbarAction::toggle_picking, CanvasToolMode::draw, true, false },
|
|
CanvasToolToolbarBinding { "btn-touchlock", CanvasToolToolbarAction::toggle_touch_lock, CanvasToolMode::draw, true, false },
|
|
CanvasToolToolbarBinding { "btn-erase", CanvasToolToolbarAction::select_mode, CanvasToolMode::erase, true, false },
|
|
CanvasToolToolbarBinding { "btn-line", CanvasToolToolbarAction::select_mode, CanvasToolMode::line, true, false },
|
|
CanvasToolToolbarBinding { "btn-cam", CanvasToolToolbarAction::select_mode, CanvasToolMode::camera, false, false },
|
|
CanvasToolToolbarBinding { "btn-grid", CanvasToolToolbarAction::select_mode, CanvasToolMode::grid, false, false },
|
|
CanvasToolToolbarBinding { "btn-copy", CanvasToolToolbarAction::select_mode, CanvasToolMode::copy, false, false },
|
|
CanvasToolToolbarBinding { "btn-cut", CanvasToolToolbarAction::select_mode, CanvasToolMode::cut, false, false },
|
|
CanvasToolToolbarBinding { "btn-fill", CanvasToolToolbarAction::select_mode, CanvasToolMode::fill, false, false },
|
|
CanvasToolToolbarBinding { "btn-mask-free", CanvasToolToolbarAction::select_mode, CanvasToolMode::mask_free, true, false },
|
|
CanvasToolToolbarBinding { "btn-mask-line", CanvasToolToolbarAction::select_mode, CanvasToolMode::mask_line, true, false },
|
|
CanvasToolToolbarBinding { "btn-bucket", CanvasToolToolbarAction::select_mode, CanvasToolMode::flood_fill, true, false },
|
|
},
|
|
CanvasToolMode::draw,
|
|
};
|
|
}
|
|
|
|
[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_toolbar_binding_action(
|
|
const CanvasToolToolbarBinding& binding,
|
|
bool current_mode_is_draw) noexcept
|
|
{
|
|
switch (binding.action) {
|
|
case CanvasToolToolbarAction::select_mode:
|
|
return plan_canvas_tool_select(binding.mode);
|
|
case CanvasToolToolbarAction::toggle_picking:
|
|
return plan_canvas_tool_pick_toggle(current_mode_is_draw);
|
|
case CanvasToolToolbarAction::toggle_touch_lock:
|
|
return plan_canvas_tool_touch_lock_toggle();
|
|
}
|
|
|
|
return plan_canvas_tool_select(CanvasToolMode::draw);
|
|
}
|
|
|
|
[[nodiscard]] inline constexpr CanvasToolButtonState plan_canvas_tool_button_state(
|
|
CanvasToolMode mode,
|
|
bool picking,
|
|
bool touch_lock) noexcept
|
|
{
|
|
CanvasToolButtonState state;
|
|
state.mode = mode;
|
|
state.pick_active = mode == CanvasToolMode::draw && picking;
|
|
state.touch_lock_active = touch_lock;
|
|
state.pen_active = mode == CanvasToolMode::draw;
|
|
state.erase_active = mode == CanvasToolMode::erase;
|
|
state.line_active = mode == CanvasToolMode::line;
|
|
state.camera_active = mode == CanvasToolMode::camera;
|
|
state.grid_active = mode == CanvasToolMode::grid;
|
|
state.copy_active = mode == CanvasToolMode::copy;
|
|
state.cut_active = mode == CanvasToolMode::cut;
|
|
state.fill_active = mode == CanvasToolMode::fill;
|
|
state.mask_free_active = mode == CanvasToolMode::mask_free;
|
|
state.mask_line_active = mode == CanvasToolMode::mask_line;
|
|
state.flood_fill_active = mode == CanvasToolMode::flood_fill;
|
|
return state;
|
|
}
|
|
|
|
[[nodiscard]] inline constexpr bool canvas_tool_mode_is_paint(CanvasToolMode mode) noexcept
|
|
{
|
|
return mode == CanvasToolMode::draw || mode == CanvasToolMode::erase;
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Result<CanvasCursorVisibilityPlan> plan_canvas_cursor_visibility(
|
|
const CanvasCursorVisibilityInput& input)
|
|
{
|
|
CanvasCursorVisibilityPlan plan;
|
|
plan.paint_mode = canvas_tool_mode_is_paint(input.mode);
|
|
if (!plan.paint_mode) {
|
|
plan.visible = true;
|
|
return pp::foundation::Result<CanvasCursorVisibilityPlan>::success(plan);
|
|
}
|
|
|
|
switch (input.visibility_mode) {
|
|
case CanvasCursorVisibilityMode::always:
|
|
plan.visible = true;
|
|
break;
|
|
case CanvasCursorVisibilityMode::never:
|
|
plan.visible = false;
|
|
break;
|
|
case CanvasCursorVisibilityMode::small_brush:
|
|
if (!input.has_current_brush) {
|
|
return pp::foundation::Result<CanvasCursorVisibilityPlan>::failure(
|
|
pp::foundation::Status::invalid_argument("canvas cursor small-brush mode requires a current brush"));
|
|
}
|
|
if (!std::isfinite(input.brush_tip_size) || input.brush_tip_size < 0.0F) {
|
|
return pp::foundation::Result<CanvasCursorVisibilityPlan>::failure(
|
|
pp::foundation::Status::invalid_argument("canvas cursor brush size must be finite and non-negative"));
|
|
}
|
|
plan.visible = input.brush_tip_size < 10.0F;
|
|
plan.uses_brush_size = true;
|
|
break;
|
|
case CanvasCursorVisibilityMode::not_painting:
|
|
plan.visible = !input.pen_is_drawing;
|
|
plan.uses_pen_state = true;
|
|
break;
|
|
}
|
|
|
|
if (input.alt_down || input.pen_is_resizing || input.pen_is_picking) {
|
|
plan.visible = true;
|
|
plan.forced_visible_by_modifier_or_tool = true;
|
|
}
|
|
|
|
return pp::foundation::Result<CanvasCursorVisibilityPlan>::success(plan);
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_canvas_tool_plan(
|
|
const CanvasToolPlan& plan,
|
|
CanvasToolServices& services)
|
|
{
|
|
switch (plan.operation) {
|
|
case CanvasToolOperation::select_mode:
|
|
if (!plan.selects_toolbar_button || !plan.updates_canvas_mode) {
|
|
return pp::foundation::Status::invalid_argument("canvas tool select plan must select toolbar and update mode");
|
|
}
|
|
if (plan.transform_action != transform_action_for_mode(plan.mode)) {
|
|
return pp::foundation::Status::invalid_argument("canvas tool select plan has mismatched transform action");
|
|
}
|
|
services.select_toolbar_button(plan.mode);
|
|
if (plan.transform_action != CanvasToolTransformAction::none) {
|
|
services.set_transform_action(plan.transform_action);
|
|
}
|
|
services.set_canvas_mode(plan.mode);
|
|
return pp::foundation::Status::success();
|
|
|
|
case CanvasToolOperation::toggle_picking:
|
|
if (!plan.requires_draw_mode) {
|
|
return pp::foundation::Status::invalid_argument("canvas pick plan must require draw mode");
|
|
}
|
|
if (plan.no_op) {
|
|
return pp::foundation::Status::success();
|
|
}
|
|
if (!plan.toggles_picking) {
|
|
return pp::foundation::Status::invalid_argument("canvas pick plan must toggle picking or be a no-op");
|
|
}
|
|
services.toggle_picking();
|
|
return pp::foundation::Status::success();
|
|
|
|
case CanvasToolOperation::toggle_touch_lock:
|
|
if (!plan.toggles_touch_lock || plan.no_op) {
|
|
return pp::foundation::Status::invalid_argument("canvas touch-lock plan must toggle touch lock");
|
|
}
|
|
services.toggle_touch_lock();
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
return pp::foundation::Status::invalid_argument("unknown canvas tool operation");
|
|
}
|
|
|
|
} // namespace pp::app
|