Files
Renderive/Core/plottable/Selection_Rectangle_Overlay_p.h
T
2026-08-02 20:33:53 +08:00

190 lines
9.0 KiB
C++

#pragma once
#include <algorithm>
#include <atomic>
#include <cmath>
#include <mutex>
#include <string>
#include <vector>
#include "../Axis/Abs_Axis_p.h"
#include "../event/Event.h"
#include "../event/Key_Event.h"
#include "../event/Pointer_Event.h"
#include "../base/String_Format.h"
#include "../render/Canvas.h"
#include "Selection_Rectangle_Overlay.h"
namespace renderive {
struct Selection_Rectangle_Overlay_Render_State : Render_State {
std::vector<RectF> rects;
Font label_font;
Pen selection_border_pen, label_pen = Pen{Color::white()};
Brush selection_brush = Brush{Color{0, 0, 255, 50}, Brush_Style::Solid};
std::weak_ptr<Abs_Axis> horizontal_axis{}, vertical_axis{};
Selection_Rectangle_Overlay_Render_State() {
selection_border_pen.style = Line_Style::Dash;
selection_border_pen.color = Color::white();
}
};
struct Selection_Rectangle_Overlay_Input_Data : Input_Data {};
struct Selection_Rectangle_Overlay_Private : Typed_Render_Data<Selection_Rectangle_Overlay, Selection_Rectangle_Overlay_Render_State, Selection_Rectangle_Overlay_Input_Data>, Hit_Testable, Pointer_Interactive, Key_Press_Interactive {
std::atomic_bool active{false};
std::mutex interaction_mapping_mutex;
Axis_Mapping_2D interaction_mapping_value{};
bool interaction_mapping_valid{};
bool interaction_mapping(Axis_Mapping_2D& mapping) {
std::lock_guard lock(interaction_mapping_mutex);
if (!interaction_mapping_valid)
return false;
mapping = interaction_mapping_value;
return true;
}
void publish_interaction_mapping(const Axis_Mapping_2D& mapping) {
std::lock_guard lock(interaction_mapping_mutex);
interaction_mapping_value = mapping;
interaction_mapping_valid = true;
}
void invalidate_interaction_mapping() {
std::lock_guard lock(interaction_mapping_mutex);
interaction_mapping_valid = false;
}
bool select_test(const PointF& pos) override {
Axis_Mapping_2D mapping;
if (!interaction_mapping(mapping))
return false;
PointF first = mapping.map(mapping.domain.coord_range.origin, mapping.value.coord_range.origin);
PointF second = mapping.map(mapping.domain.coord_range.target, mapping.value.coord_range.target);
double rx = std::min(first.x, second.x);
double ry = std::min(first.y, second.y);
double rw = std::max(first.x, second.x) - rx;
double rh = std::max(first.y, second.y) - ry;
return RectF{rx, ry, rw, rh}.contains(pos);
}
void mouse_press_event(const Pointer_Event& event) override {
if (event.button != Mouse_Button::Left)
return;
Render_Edit_Lease<Selection_Rectangle_Overlay_Private, Selection_Rectangle_Overlay_Render_State> edit(this);
auto horizontal_axis = edit->horizontal_axis.lock();
auto vertical_axis = edit->vertical_axis.lock();
if (!horizontal_axis || !vertical_axis)
return;
Axis_Mapping_2D mapping;
if (!interaction_mapping(mapping))
return;
std::vector<RectF>& rect_list = edit->rects;
if (!static_cast<bool>(event.modifiers & Keyboard_Modifier::Ctrl)) {
rect_list.clear();
}
else {
int n = static_cast<int>(rect_list.size());
for (int i = n - 1; i >= 0; i--) {
auto& rect = rect_list[i];
if (std::abs(rect.width) < 1e-12 || std::abs(rect.height) < 1e-12)
rect_list.erase(rect_list.begin() + i);
}
}
PointF screen_point{static_cast<double>(event.position.x), static_cast<double>(event.position.y)};
double x = mapping.domain.pixel_to_coord(mapping.domain_pixel(screen_point));
double y = mapping.value.pixel_to_coord(mapping.value_pixel(screen_point));
rect_list.push_back(RectF{x, y, 0.0, 0.0});
active.store(true, std::memory_order_release);
event.accept();
}
void mouse_move_event(const Pointer_Event& event) override {
if (!active.load(std::memory_order_acquire))
return;
if (!(event.buttons & 1))
return;
Render_Edit_Lease<Selection_Rectangle_Overlay_Private, Selection_Rectangle_Overlay_Render_State> edit(this);
auto horizontal_axis = edit->horizontal_axis.lock();
auto vertical_axis = edit->vertical_axis.lock();
if (!horizontal_axis || !vertical_axis)
return;
Axis_Mapping_2D mapping;
if (!interaction_mapping(mapping))
return;
std::vector<RectF>& rect_list = edit->rects;
if (rect_list.empty())
return;
PointF screen_point{static_cast<double>(event.position.x), static_cast<double>(event.position.y)};
double x = mapping.domain.pixel_to_coord(mapping.domain_pixel(screen_point));
double y = mapping.value.pixel_to_coord(mapping.value_pixel(screen_point));
rect_list.back().width = x - rect_list.back().x;
rect_list.back().height = y - rect_list.back().y;
event.accept();
}
void mouse_release_event(const Pointer_Event& event) override {
if (event.button != Mouse_Button::Left || !active.load(std::memory_order_acquire))
return;
active.store(false, std::memory_order_release);
Render_Edit_Lease<Selection_Rectangle_Overlay_Private, Selection_Rectangle_Overlay_Render_State> edit(this);
if (!edit->rects.empty() &&
(std::abs(edit->rects.back().width) < 1e-12 || std::abs(edit->rects.back().height) < 1e-12))
edit->rects.pop_back();
event.accept();
}
void key_press_event(const Key_Event& event) override {
if (event.key != Key::Escape || !active.load(std::memory_order_acquire))
return;
Render_Edit_Lease<Selection_Rectangle_Overlay_Private, Selection_Rectangle_Overlay_Render_State> edit(this);
active.store(false, std::memory_order_release);
std::vector<RectF>& rect_list = edit->rects;
if (rect_list.empty())
return;
rect_list.pop_back();
event.accept();
}
void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override {
Selection_Rectangle_Overlay_Render_State* s = render_state(frame_view);
if (!s)
return;
auto horizontal_axis = s->horizontal_axis.lock();
auto vertical_axis = s->vertical_axis.lock();
if (!horizontal_axis || !vertical_axis)
return;
Axis_Mapping_2D mapping = Axis_Render_Access::mapping(horizontal_axis.get(), vertical_axis.get(), snapshot);
publish_interaction_mapping(mapping);
std::vector<RectF> draw_rect_list;
std::vector<RectF> coord_rect_list;
for (RectF r : s->rects) {
if (std::abs(r.width) < 1e-12 || std::abs(r.height) < 1e-12)
continue;
coord_rect_list.push_back(r.normalized());
PointF first = mapping.map(r.x, r.y);
PointF second = mapping.map(r.right(), r.bottom());
RectF dr{first.x, first.y, second.x - first.x, second.y - first.y};
draw_rect_list.push_back(dr.normalized());
}
canvas.save();
canvas.set_font(Axis_Render_Access::unit_text_font(horizontal_axis.get(), snapshot));
Text_Metrics xfm = canvas.measure_text("M");
canvas.set_font(Axis_Render_Access::unit_text_font(vertical_axis.get(), snapshot));
Text_Metrics yfm = canvas.measure_text("M");
canvas.set_pen(s->selection_border_pen);
canvas.set_brush(s->selection_brush);
for (auto& rect : draw_rect_list)
canvas.draw_rect(rect);
canvas.set_pen(s->label_pen);
canvas.set_font(s->label_font);
int n = static_cast<int>(draw_rect_list.size());
for (int i = 0; i < n; ++i) {
RectF& rect = draw_rect_list[i];
RectF& coord_rect = coord_rect_list[i];
double offset_x = coord_rect.right() - coord_rect.x;
double offset_y = coord_rect.bottom() - coord_rect.y;
std::string text1 = "offset_x == " + format_number(offset_x, Number_Format{Number_Notation::General, 6})
+ Axis_Render_Access::unit_text(horizontal_axis.get(), snapshot);
std::string text2 = "offset_y == " + format_number(offset_y, Number_Format{Number_Notation::General, 6})
+ Axis_Render_Access::unit_text(vertical_axis.get(), snapshot);
double width1 = canvas.measure_text(text1).width + 32.0;
double width2 = canvas.measure_text(text2).width + 32.0;
double rect1_x = rect.x + rect.width / 2.0 - width1 / 2.0;
double rect2_x = rect.x + rect.width / 2.0 - width2 / 2.0;
RectF rect1{rect1_x, rect.y, width1, xfm.line_height()};
RectF rect2{rect2_x, rect1.bottom(), width2, yfm.line_height()};
canvas.draw_text(rect1, Text_Align::Center, text1);
canvas.draw_text(rect2, Text_Align::Center, text2);
}
canvas.restore();
}
};
} // namespace renderive