Files
panopainter/src/app_core/quick_ui.h

303 lines
10 KiB
C++

#pragma once
#include "foundation/result.h"
#include <cmath>
namespace pp::app {
enum class QuickUiSlotKind {
brush,
color,
};
enum class QuickUiOperation {
select_slot,
open_slot_popup,
restore_state,
reset_state,
};
struct QuickUiPlan {
QuickUiOperation operation = QuickUiOperation::select_slot;
QuickUiSlotKind slot_kind = QuickUiSlotKind::brush;
int slot_index = 0;
int previous_index = 0;
int brush_index = 0;
int color_index = 0;
int slot_count = 0;
bool fire_event = false;
bool updates_selection = false;
bool opens_brush_popup = false;
bool opens_color_picker = false;
bool invokes_change_callback = false;
bool restores_slots = false;
bool resets_slots = false;
bool redraws_brush_previews = false;
bool mutates_quick_state = false;
};
struct QuickSliderPreviewInput {
bool ui_rtl = false;
float slider_x = 0.0F;
float slider_y = 0.0F;
float slider_height = 0.0F;
float zoom = 1.0F;
bool has_pen_mode = false;
bool has_line_mode = false;
};
struct QuickSliderPreviewPlan {
float cursor_x = 0.0F;
float cursor_y = 0.0F;
bool updates_pen_mode = false;
bool updates_line_mode = false;
bool draws_tip = false;
bool disables_pen_outline = false;
bool redraws_brush_preview = false;
bool invokes_change_callback = false;
};
class QuickUiServices {
public:
virtual ~QuickUiServices() = default;
virtual void select_slot(QuickUiSlotKind slot_kind, int slot_index, bool fire_event) = 0;
virtual void open_slot_popup(QuickUiSlotKind slot_kind, int slot_index) = 0;
virtual void restore_state(int brush_index, int color_index, bool fire_event) = 0;
virtual void reset_state(bool fire_event) = 0;
};
[[nodiscard]] inline pp::foundation::Status validate_quick_slider_float(float value) noexcept
{
if (!std::isfinite(value)) {
return pp::foundation::Status::invalid_argument("quick slider preview value must be finite");
}
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status validate_quick_slot_count(int slot_count) noexcept
{
if (slot_count <= 0) {
return pp::foundation::Status::out_of_range("quick slot count must be greater than zero");
}
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status validate_quick_slot_index(int slot_index, int slot_count) noexcept
{
const auto count_status = validate_quick_slot_count(slot_count);
if (!count_status.ok()) {
return count_status;
}
if (slot_index < 0 || slot_index >= slot_count) {
return pp::foundation::Status::out_of_range("quick slot index is out of range");
}
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Result<QuickSliderPreviewPlan> plan_quick_slider_preview(
const QuickSliderPreviewInput& input)
{
const auto x_status = validate_quick_slider_float(input.slider_x);
if (!x_status.ok()) {
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(x_status);
}
const auto y_status = validate_quick_slider_float(input.slider_y);
if (!y_status.ok()) {
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(y_status);
}
const auto height_status = validate_quick_slider_float(input.slider_height);
if (!height_status.ok()) {
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(height_status);
}
if (input.slider_height < 0.0F) {
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(
pp::foundation::Status::invalid_argument("quick slider preview height must not be negative"));
}
const auto zoom_status = validate_quick_slider_float(input.zoom);
if (!zoom_status.ok()) {
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(zoom_status);
}
if (input.zoom <= 0.0F) {
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(
pp::foundation::Status::invalid_argument("quick slider preview zoom must be positive"));
}
const float offset = input.ui_rtl ? -100.0F : 100.0F;
QuickSliderPreviewPlan plan;
plan.cursor_x = (input.slider_x + offset) * input.zoom;
plan.cursor_y = (input.slider_y + input.slider_height * 0.5F) * input.zoom;
plan.updates_pen_mode = input.has_pen_mode;
plan.updates_line_mode = input.has_line_mode;
plan.draws_tip = input.has_pen_mode || input.has_line_mode;
plan.disables_pen_outline = input.has_pen_mode;
plan.redraws_brush_preview = true;
plan.invokes_change_callback = true;
return pp::foundation::Result<QuickSliderPreviewPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<QuickUiPlan> plan_quick_slot_click(
QuickUiSlotKind slot_kind,
int current_index,
int clicked_index,
int slot_count)
{
const auto current_status = validate_quick_slot_index(current_index, slot_count);
if (!current_status.ok()) {
return pp::foundation::Result<QuickUiPlan>::failure(current_status);
}
const auto clicked_status = validate_quick_slot_index(clicked_index, slot_count);
if (!clicked_status.ok()) {
return pp::foundation::Result<QuickUiPlan>::failure(clicked_status);
}
QuickUiPlan plan;
plan.slot_kind = slot_kind;
plan.slot_index = clicked_index;
plan.previous_index = current_index;
plan.brush_index = slot_kind == QuickUiSlotKind::brush ? clicked_index : 0;
plan.color_index = slot_kind == QuickUiSlotKind::color ? clicked_index : 0;
plan.slot_count = slot_count;
if (clicked_index != current_index) {
plan.operation = QuickUiOperation::select_slot;
plan.updates_selection = true;
plan.invokes_change_callback = true;
plan.mutates_quick_state = true;
return pp::foundation::Result<QuickUiPlan>::success(plan);
}
plan.operation = QuickUiOperation::open_slot_popup;
plan.opens_brush_popup = slot_kind == QuickUiSlotKind::brush;
plan.opens_color_picker = slot_kind == QuickUiSlotKind::color;
return pp::foundation::Result<QuickUiPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<QuickUiPlan> plan_quick_state_restore(
int brush_index,
int color_index,
int slot_count,
bool fire_event)
{
const auto brush_status = validate_quick_slot_index(brush_index, slot_count);
if (!brush_status.ok()) {
return pp::foundation::Result<QuickUiPlan>::failure(brush_status);
}
const auto color_status = validate_quick_slot_index(color_index, slot_count);
if (!color_status.ok()) {
return pp::foundation::Result<QuickUiPlan>::failure(color_status);
}
QuickUiPlan plan;
plan.operation = QuickUiOperation::restore_state;
plan.brush_index = brush_index;
plan.color_index = color_index;
plan.slot_count = slot_count;
plan.fire_event = fire_event;
plan.updates_selection = true;
plan.invokes_change_callback = fire_event;
plan.restores_slots = true;
plan.redraws_brush_previews = true;
plan.mutates_quick_state = true;
return pp::foundation::Result<QuickUiPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<QuickUiPlan> plan_quick_state_reset(
int slot_count,
bool fire_event)
{
const auto count_status = validate_quick_slot_count(slot_count);
if (!count_status.ok()) {
return pp::foundation::Result<QuickUiPlan>::failure(count_status);
}
QuickUiPlan plan;
plan.operation = QuickUiOperation::reset_state;
plan.brush_index = 0;
plan.color_index = 0;
plan.slot_count = slot_count;
plan.fire_event = fire_event;
plan.updates_selection = true;
plan.invokes_change_callback = fire_event;
plan.resets_slots = true;
plan.redraws_brush_previews = true;
plan.mutates_quick_state = true;
return pp::foundation::Result<QuickUiPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Status execute_quick_ui_plan(
const QuickUiPlan& plan,
QuickUiServices& services)
{
switch (plan.operation) {
case QuickUiOperation::select_slot:
if (!plan.updates_selection) {
return pp::foundation::Status::invalid_argument("quick select plan must update selection");
}
{
const auto status = validate_quick_slot_index(plan.slot_index, plan.slot_count);
if (!status.ok()) {
return status;
}
}
services.select_slot(plan.slot_kind, plan.slot_index, plan.invokes_change_callback);
return pp::foundation::Status::success();
case QuickUiOperation::open_slot_popup:
if (plan.slot_kind == QuickUiSlotKind::brush && !plan.opens_brush_popup) {
return pp::foundation::Status::invalid_argument("quick brush popup plan must open brush popup");
}
if (plan.slot_kind == QuickUiSlotKind::color && !plan.opens_color_picker) {
return pp::foundation::Status::invalid_argument("quick color popup plan must open color picker");
}
{
const auto status = validate_quick_slot_index(plan.slot_index, plan.slot_count);
if (!status.ok()) {
return status;
}
}
services.open_slot_popup(plan.slot_kind, plan.slot_index);
return pp::foundation::Status::success();
case QuickUiOperation::restore_state:
if (!plan.restores_slots) {
return pp::foundation::Status::invalid_argument("quick restore plan must restore slots");
}
{
const auto brush_status = validate_quick_slot_index(plan.brush_index, plan.slot_count);
if (!brush_status.ok()) {
return brush_status;
}
const auto color_status = validate_quick_slot_index(plan.color_index, plan.slot_count);
if (!color_status.ok()) {
return color_status;
}
}
services.restore_state(plan.brush_index, plan.color_index, plan.fire_event);
return pp::foundation::Status::success();
case QuickUiOperation::reset_state:
if (!plan.resets_slots) {
return pp::foundation::Status::invalid_argument("quick reset plan must reset slots");
}
{
const auto status = validate_quick_slot_count(plan.slot_count);
if (!status.ok()) {
return status;
}
}
services.reset_state(plan.fire_event);
return pp::foundation::Status::success();
}
return pp::foundation::Status::invalid_argument("unknown quick UI operation");
}
} // namespace pp::app