Files
panopainter/src/app_core/canvas_tool_ui.h

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