190 lines
9.0 KiB
C++
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
|