101 lines
9.0 KiB
C++
101 lines
9.0 KiB
C++
#pragma once
|
|
#include "common/Curve_Plot.hpp"
|
|
#include <cmath>
|
|
#include <sstream>
|
|
namespace aethera::render_2d {
|
|
struct Selection_Rectangle_Overlay::Private : Prev_Private {
|
|
using No_Prepare = void;
|
|
using Regions_Get = std::vector<Rect_F> (*)(const Root*);
|
|
using Clear_Run = void (*)(Root*);
|
|
struct Dispatch {
|
|
Regions_Get selected_regions; /* 查询最终对象已发布选择区域。 */
|
|
Clear_Run clear_selected_regions; /* 清空最终对象选择区域。 */
|
|
};
|
|
Scene_Object* scene{}; /* 不拥有的所属 Scene。 */
|
|
Axis_Object* horizontal_axis{}; /* 不拥有的水平坐标轴。 */
|
|
Axis_Object* vertical_axis{}; /* 不拥有的垂直坐标轴。 */
|
|
Point_F drag_origin{}; /* 当前拖动起点,单位为画布像素。 */
|
|
Point_F drag_current{}; /* 当前拖动终点,单位为画布像素。 */
|
|
bool dragging{}; /* 是否正在构造尚未提交的选择矩形。 */
|
|
const Dispatch* dispatch{}; /* 最终类型公开薄壳分派表。 */
|
|
/* CRTP 覆盖:绑定 Paint-only、事件能力和最终 Overlay 分派表。 */
|
|
template <Attached Object> void bind_private_crtp(Object* object);
|
|
void bind_sources(Axis_Object* horizontal_axis_value, Axis_Object* vertical_axis_value);
|
|
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
|
/* CRTP 覆盖:无需 Prepare 子图,直接绘制选择矩形。 */
|
|
template <Attached Object> void paint(Object* object);
|
|
/* CRTP 覆盖:处理拖拽并更新权威选择区域状态。 */
|
|
template <Attached Object> void handle_event(Object* object, const Event& event);
|
|
/* CRTP 覆盖:本类状态写入后只标记 Paint 数据失效。 */
|
|
template <typename Object, typename Owner, typename Member, typename Prop_Type> void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> pending_states);
|
|
template <typename Object, typename Prop_Type, typename State_Type> void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
|
|
};
|
|
template <typename Object>
|
|
Selection_Rectangle_Overlay::Builder<Object>::Builder(Axis_Object* horizontal_axis_value, Axis_Object* vertical_axis_value) : Base(), horizontal_axis(horizontal_axis_value), vertical_axis(vertical_axis_value) {}
|
|
template <typename Object>
|
|
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Selection_Rectangle_Overlay::Builder<Object>::build() {
|
|
auto result = Base::build();
|
|
if (!result) return std::unexpected(result.error());
|
|
auto overlay = std::move(result).value();
|
|
auto& private_data = static_cast<typename Object::Private&>(*overlay->d); private_data.bind_sources(horizontal_axis, vertical_axis);
|
|
private_data.scene_attach = [object = overlay.get()](Root* root) -> std::expected<void, Dependency_Graph_Error> {
|
|
auto* scene = static_cast<Scene_Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); data.scene = scene; auto* horizontal_axis = data.horizontal_axis; auto* vertical_axis = data.vertical_axis;
|
|
return scene->template edit_dependency_graph<Prepare_Data_Tag, Paint_Tag, Paint_Cache_Tag>([&](auto& prepare, auto& paint, auto& cache) { prepare.add_dependency(object, scene); prepare.add_dependency(object, horizontal_axis); prepare.add_dependency(object, vertical_axis); paint.add_dependency(horizontal_axis, object); paint.add_dependency(vertical_axis, object); cache.template add_prop_dependency<&Render_Scene_2D::Prop::viewport>(object, scene); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, horizontal_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, horizontal_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, horizontal_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, horizontal_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::position>(object, vertical_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::pixel_length>(object, vertical_axis); cache.template add_prop_dependency<&Abs_Axis::Prop::orientation>(object, vertical_axis); cache.template add_prop_dependency<&Numeric_Axis::Prop::coordinate_range>(object, vertical_axis); });
|
|
}; return overlay;
|
|
}
|
|
template <Attached Object>
|
|
void Selection_Rectangle_Overlay::Private::paint(Object* object) {
|
|
auto& data = static_cast<typename Object::Private&>(*this);
|
|
const auto& state = static_cast<const Prop&>(*data.current);
|
|
const Size canvas = scene->template read_prop<Render_Scene_2D::Base_Tag>().viewport;
|
|
auto& cache = data.paint_surface();
|
|
if (canvas.empty()) return;
|
|
detail::Painter painter(cache, canvas);
|
|
const auto paint_region = [&](Rect_F region) {
|
|
const auto rect = detail::map_plot_rect(horizontal_axis, {region.x, region.x + region.width}, vertical_axis, {region.y, region.y + region.height}, Axis_Orientation::horizontal);
|
|
painter.rect(rect, state.selection_border_pen, state.selection_brush);
|
|
};
|
|
for (const auto& region : state.selected_regions) paint_region(region);
|
|
if (dragging) painter.rect(Rect_F{drag_origin.x, drag_origin.y, drag_current.x - drag_origin.x, drag_current.y - drag_origin.y}.normalized(), state.selection_border_pen, state.selection_brush);
|
|
}
|
|
template <Attached Object>
|
|
void Selection_Rectangle_Overlay::Private::handle_event(Object* object, const Event& event) {
|
|
const auto* pointer = dynamic_cast<const Pointer_Event_Capability*>(&event);
|
|
if (!pointer) return;
|
|
const Point_F point{pointer->position_x(), pointer->position_y()};
|
|
if (event.type == Event_Type::pointer_press && pointer->pointer_button() == Mouse_Button::left) {
|
|
const auto modifiers = static_cast<std::underlying_type_t<Keyboard_Modifier>>(pointer->keyboard_modifiers());
|
|
const auto control = static_cast<std::underlying_type_t<Keyboard_Modifier>>(Keyboard_Modifier::control);
|
|
if ((modifiers & control) == 0) object->template set<&Prop::selected_regions>(std::vector<Rect_F>{});
|
|
dragging = true; drag_origin = point; drag_current = point; object->template mark_dirty<Paint_Tag>(); event.accept(); return;
|
|
}
|
|
if (event.type == Event_Type::pointer_move && dragging) { drag_current = point; object->template mark_dirty<Paint_Tag>(); event.accept(); return; }
|
|
if (event.type != Event_Type::pointer_release || !dragging) return;
|
|
dragging = false; drag_current = point;
|
|
if (std::hypot(drag_current.x - drag_origin.x, drag_current.y - drag_origin.y) < 3.0) {
|
|
object->template mark_dirty<Paint_Tag>(); event.accept(); return;
|
|
}
|
|
const Axis_Coordinate first_x = horizontal_axis->point_to_coordinate(drag_origin);
|
|
const Axis_Coordinate second_x = horizontal_axis->point_to_coordinate(drag_current);
|
|
const Axis_Coordinate first_y = vertical_axis->point_to_coordinate(drag_origin);
|
|
const Axis_Coordinate second_y = vertical_axis->point_to_coordinate(drag_current);
|
|
object->template update_prop<&Prop::selected_regions>([=](Prop_Access<typename Object::Prop> props) { props.template get<Selection_Rectangle_Overlay::Base_Tag>().selected_regions.push_back(Rect_F{first_x, first_y, second_x - first_x, second_y - first_y}.normalized()); });
|
|
event.accept();
|
|
}
|
|
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
|
void Selection_Rectangle_Overlay::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Paint_Tag>(); }
|
|
template <typename Object, typename Prop_Type, typename State_Type>
|
|
void Selection_Rectangle_Overlay::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { pending_states.template get<Selection_Rectangle_Overlay::Base_Tag>().selected_region_count = static_cast<const Prop&>(*current_prop).selected_regions.size(); }
|
|
template <Attached Object>
|
|
const Selection_Rectangle_Overlay::Private::Dispatch& Selection_Rectangle_Overlay::Private::dispatch_for() {
|
|
static const Dispatch value{
|
|
[](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Selection_Rectangle_Overlay::Base_Tag>().selected_regions; },
|
|
[](Root* root) { static_cast<Object*>(root)->template set<&Prop::selected_regions>(std::vector<Rect_F>{}); }
|
|
};
|
|
return value;
|
|
}
|
|
template <Attached Object>
|
|
void Selection_Rectangle_Overlay::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
|
|
inline void Selection_Rectangle_Overlay::Private::bind_sources(Axis_Object* horizontal_axis_value, Axis_Object* vertical_axis_value) { horizontal_axis = horizontal_axis_value; vertical_axis = vertical_axis_value; }
|
|
}
|