#pragma once #include #include #include #include #include #include #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 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 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, 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 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& rect_list = edit->rects; if (!static_cast(event.modifiers & Keyboard_Modifier::Ctrl)) { rect_list.clear(); } else { int n = static_cast(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(event.position.x), static_cast(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 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& rect_list = edit->rects; if (rect_list.empty()) return; PointF screen_point{static_cast(event.position.x), static_cast(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 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 edit(this); active.store(false, std::memory_order_release); std::vector& 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 draw_rect_list; std::vector 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(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