303 lines
10 KiB
C++
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
|