Files
Renderive/render_2D/plottable/Selection_Rectangle_Overlay.cpp
T

154 lines
6.5 KiB
C++

#include "Selection_Rectangle_Overlay.h"
#include "Plottable_Real_Time_Data.h"
#include "Heatmap_Utils.h"
#include "../render/Blend2D_Cache.h"
#include <algorithm>
#include <cmath>
#include <sstream>
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<Selection_Interaction_Base, Selection_Interaction>;
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<Prepared_Region> regions;
RectF active_selection;
bool selecting{};
};
Impl(Selection_Rectangle_Overlay_Control& owner, renderive_Owner<Abs_Axis> horizontal, renderive_Owner<Abs_Axis> vertical)
: horizontal_axis(std::move(horizontal)), vertical_axis(std::move(vertical)), regions(owner) {}
renderive_Owner<Abs_Axis> horizontal_axis;
renderive_Owner<Abs_Axis> vertical_axis;
Plottable_History_Real_Time_Data<RectF, std::vector<RectF>> regions;
Selection_Interaction_State interaction;
Prepare_Buffer prepare_buffer;
};
Selection_Rectangle_Overlay_Control::Selection_Rectangle_Overlay_Control(const Selection_Rectangle_Overlay_Properties& properties, renderive_Owner<Abs_Axis> horizontal_axis, renderive_Owner<Abs_Axis> vertical_axis)
: Plottable_State(properties), impl_(std::make_unique<Impl>(*this, std::move(horizontal_axis), std::move(vertical_axis))) {}
Selection_Rectangle_Overlay_Control::~Selection_Rectangle_Overlay_Control() = default;
std::vector<RectF> 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<const Pointer_Event&>(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<const Pointer_Event&>(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<const Pointer_Event&>(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 Prepare_Render_Context& context) {
const auto& view = context.frame.render_state;
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 Paint_Render_Context& context) {
const auto& output = impl_->prepare_buffer;
const auto& view = context.frame.render_state;
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);
}
}
}