#pragma once #include "foundation/result.h" #include 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 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::failure(x_status); } const auto y_status = validate_quick_slider_float(input.slider_y); if (!y_status.ok()) { return pp::foundation::Result::failure(y_status); } const auto height_status = validate_quick_slider_float(input.slider_height); if (!height_status.ok()) { return pp::foundation::Result::failure(height_status); } if (input.slider_height < 0.0F) { return pp::foundation::Result::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::failure(zoom_status); } if (input.zoom <= 0.0F) { return pp::foundation::Result::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::success(plan); } [[nodiscard]] inline pp::foundation::Result 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::failure(current_status); } const auto clicked_status = validate_quick_slot_index(clicked_index, slot_count); if (!clicked_status.ok()) { return pp::foundation::Result::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::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::success(plan); } [[nodiscard]] inline pp::foundation::Result 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::failure(brush_status); } const auto color_status = validate_quick_slot_index(color_index, slot_count); if (!color_status.ok()) { return pp::foundation::Result::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::success(plan); } [[nodiscard]] inline pp::foundation::Result 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::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::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