#include "Selection_Rectangle_Overlay.h" #include "Plottable_Real_Time_Data.h" #include "Heatmap_Utils.h" #include "../render/Blend2D_Cache.h" #include #include #include namespace renderive { namespace detail { namespace { struct Selection_Interaction_Base {}; struct Selection_Interaction { bool selecting{}; PointF selection_start{}; PointF selection_current{}; }; using Selection_Interaction_State = Double_State_Strategy; RectF axis_content_rect(const Axis_Transform& first, const Axis_Transform& second) { return mapped_rect(first, second, first.coordinate_range, second.coordinate_range); } } struct Selection_Rectangle_Overlay_Control::Impl { struct Prepared_Region { RectF pixels; std::string label; }; struct Prepare_Buffer { std::vector regions; RectF active_selection; bool selecting{}; }; Impl(Selection_Rectangle_Overlay_Control& owner, std::shared_ptr horizontal, std::shared_ptr vertical) : horizontal_axis(std::move(horizontal)), vertical_axis(std::move(vertical)), regions(owner) {} std::shared_ptr horizontal_axis; std::shared_ptr vertical_axis; Plottable_History_Real_Time_Data> regions; Selection_Interaction_State interaction; Prepare_Buffer prepare_buffer; }; Selection_Rectangle_Overlay_Control::Selection_Rectangle_Overlay_Control(::Scene_Base& scene, const Selection_Rectangle_Overlay_Properties& properties, std::shared_ptr horizontal_axis, std::shared_ptr vertical_axis) : Plottable_State(scene, properties), impl_(std::make_unique(*this, std::move(horizontal_axis), std::move(vertical_axis))) {} Selection_Rectangle_Overlay_Control::~Selection_Rectangle_Overlay_Control() = default; std::vector Selection_Rectangle_Overlay_Control::selected_regions() const { return impl_->regions.snapshot(); } void Selection_Rectangle_Overlay_Control::clear_selected_regions() { impl_->regions.clear(); impl_->interaction.update([](Selection_Interaction& interaction) { interaction.selecting = false; }); changed(); } void Selection_Rectangle_Overlay_Control::handle_event(const Event& event) { const RectF content = axis_content_rect(impl_->horizontal_axis->transform(), impl_->vertical_axis->transform()); if(event.type == Event_Type::Pointer_Press) { const auto& pointer = static_cast(event); if(pointer.button != Mouse_Button::Left || !content.contains(pointer.position)) return; impl_->interaction.update([&](Selection_Interaction& interaction) { interaction.selecting = true; interaction.selection_start = interaction.selection_current = pointer.position; }); event.accept(); changed(); return; } if(event.type == Event_Type::Pointer_Move) { const auto& pointer = static_cast(event); bool selecting{}; impl_->interaction.update([&](Selection_Interaction& interaction) { selecting = interaction.selecting; if(selecting) interaction.selection_current = pointer.position; }); if(!selecting) return; event.accept(); changed(); return; } if(event.type != Event_Type::Pointer_Release) return; const auto& pointer = static_cast(event); PointF start; bool selecting{}; impl_->interaction.update([&](Selection_Interaction& interaction) { selecting = interaction.selecting; if(!selecting) return; interaction.selecting = false; start = interaction.selection_start; interaction.selection_current = pointer.position; }); if(!selecting) return; const Axis_Transform horizontal = impl_->horizontal_axis->transform(); const Axis_Transform vertical = impl_->vertical_axis->transform(); const double first_start = horizontal.point_to_coord(start); const double second_start = vertical.point_to_coord(start); const RectF region{first_start, second_start, horizontal.point_to_coord(pointer.position) - first_start, vertical.point_to_coord(pointer.position) - second_start}; if(std::abs(region.width) > 1e-9 && std::abs(region.height) > 1e-9) impl_->regions.update(region.normalized()); event.accept(); changed(); } void Selection_Rectangle_Overlay_Control::publish() { publish_properties(); impl_->interaction.publish(); } void Selection_Rectangle_Overlay_Control::prepare_frame( const Render_State_View& view, const Scene_Render_Context&) { const auto& state = render_properties(view); const auto& interaction = view.get(impl_->interaction); const auto horizontal = impl_->horizontal_axis->transform(view); const auto vertical = impl_->vertical_axis->transform(view); auto& output = impl_->prepare_buffer; output = {}; for (const RectF& region : view.get(impl_->regions)) { const RectF pixels = mapped_rect(horizontal, vertical, {region.x, region.right()}, {region.y, region.bottom()}); std::ostringstream text; text << region.width << " x " << region.height; output.regions.push_back({pixels, text.str()}); } output.selecting = interaction.selecting; output.active_selection = { interaction.selection_start.x, interaction.selection_start.y, interaction.selection_current.x - interaction.selection_start.x, interaction.selection_current.y - interaction.selection_start.y}; } void Selection_Rectangle_Overlay_Control::paint( Painter& painter, const Scene_Render_Context&) { const auto& output = impl_->prepare_buffer; const auto view = render_state_view(); const auto& state = render_properties(view); for (const auto& region : output.regions) { painter.rect(region.pixels, state.selection_border_pen, state.selection_brush); const RectF normalized = region.pixels.normalized(); painter.text({normalized.x + 3.0, normalized.y + 3.0}, region.label, state.label_font, state.label_pen); } if (output.selecting) painter.rect(output.active_selection, state.selection_border_pen, state.selection_brush); } } }