113 lines
5.5 KiB
C++
113 lines
5.5 KiB
C++
#include "Selection_Rectangle_Overlay.h"
|
|
#include "../render/Blend2D_Cache.h"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <sstream>
|
|
namespace renderive {
|
|
namespace detail {
|
|
namespace {
|
|
struct Selection_Runtime_Base {};
|
|
struct Selection_Runtime {
|
|
std::vector<RectF> regions;
|
|
bool selecting{};
|
|
PointF selection_start{};
|
|
PointF selection_current{};
|
|
};
|
|
using Selection_Runtime_State = Double_State_Strategy<Selection_Runtime_Base, Selection_Runtime>;
|
|
RectF axis_content_rect(const Axis_Transform& horizontal, const Axis_Transform& vertical) {
|
|
const double x1 = horizontal.coord_to_pixel(horizontal.coordinate_range.origin);
|
|
const double x2 = horizontal.coord_to_pixel(horizontal.coordinate_range.target);
|
|
const double y1 = vertical.coord_to_pixel(vertical.coordinate_range.origin);
|
|
const double y2 = vertical.coord_to_pixel(vertical.coordinate_range.target);
|
|
return {std::min(x1, x2), std::min(y1, y2), std::abs(x2 - x1), std::abs(y2 - y1)};
|
|
}
|
|
}
|
|
struct Selection_Rectangle_Overlay_Control::Impl {
|
|
Impl(std::shared_ptr<Abs_Axis> horizontal, std::shared_ptr<Abs_Axis> vertical) : horizontal_axis(std::move(horizontal)), vertical_axis(std::move(vertical)) {}
|
|
std::shared_ptr<Abs_Axis> horizontal_axis;
|
|
std::shared_ptr<Abs_Axis> vertical_axis;
|
|
Selection_Runtime_State runtime;
|
|
};
|
|
Selection_Rectangle_Overlay_Control::Selection_Rectangle_Overlay_Control(Plot_Core& plot, const Selection_Rectangle_Overlay_Properties& properties, std::shared_ptr<Abs_Axis> horizontal_axis, std::shared_ptr<Abs_Axis> vertical_axis)
|
|
: Plottable_State(plot, properties), impl_(std::make_unique<Impl>(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_->runtime.get<&Selection_Runtime::regions>();
|
|
}
|
|
void Selection_Rectangle_Overlay_Control::clear_selected_regions() {
|
|
impl_->runtime.update([](Selection_Runtime& runtime) {
|
|
runtime.regions.clear();
|
|
runtime.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_->runtime.update([&](Selection_Runtime& runtime) {
|
|
runtime.selecting = true;
|
|
runtime.selection_start = runtime.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_->runtime.update([&](Selection_Runtime& runtime) {
|
|
selecting = runtime.selecting;
|
|
if(selecting)
|
|
runtime.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_->runtime.update([&](Selection_Runtime& runtime) {
|
|
selecting = runtime.selecting;
|
|
if(!selecting)
|
|
return;
|
|
runtime.selecting = false;
|
|
start = runtime.selection_start;
|
|
runtime.selection_current = pointer.position;
|
|
});
|
|
if(!selecting)
|
|
return;
|
|
const RectF region{impl_->horizontal_axis->pixel_to_coord(start.x), impl_->vertical_axis->pixel_to_coord(start.y), impl_->horizontal_axis->pixel_to_coord(pointer.position.x) - impl_->horizontal_axis->pixel_to_coord(start.x), impl_->vertical_axis->pixel_to_coord(pointer.position.y) - impl_->vertical_axis->pixel_to_coord(start.y)};
|
|
if(std::abs(region.width) > 1e-9 && std::abs(region.height) > 1e-9)
|
|
impl_->runtime.update([region](Selection_Runtime& runtime) { runtime.regions.push_back(region.normalized()); });
|
|
event.accept();
|
|
changed();
|
|
}
|
|
void Selection_Rectangle_Overlay_Control::publish() {
|
|
publish_properties();
|
|
impl_->runtime.publish();
|
|
}
|
|
void Selection_Rectangle_Overlay_Control::paint(Painter& painter) {
|
|
const auto state = render_properties();
|
|
const auto runtime = impl_->runtime.render_use_state();
|
|
for(const RectF& region : runtime.regions) {
|
|
RectF pixels{impl_->horizontal_axis->coord_to_pixel(region.x), impl_->vertical_axis->coord_to_pixel(region.y), impl_->horizontal_axis->coord_to_pixel(region.right()) - impl_->horizontal_axis->coord_to_pixel(region.x), impl_->vertical_axis->coord_to_pixel(region.bottom()) - impl_->vertical_axis->coord_to_pixel(region.y)};
|
|
painter.rect(pixels, state.selection_border_pen, state.selection_brush);
|
|
std::ostringstream text;
|
|
text << region.width << " x " << region.height;
|
|
const RectF normalized = pixels.normalized();
|
|
painter.text({normalized.x + 3.0, normalized.y + 3.0}, text.str(), state.label_font, state.label_pen);
|
|
}
|
|
if(runtime.selecting)
|
|
painter.rect({runtime.selection_start.x, runtime.selection_start.y, runtime.selection_current.x - runtime.selection_start.x, runtime.selection_current.y - runtime.selection_start.y}, state.selection_border_pen, state.selection_brush);
|
|
}
|
|
}
|
|
}
|