egui_charts/chart/tool_interaction/mod.rs
1//! Tool Interaction Module
2//!
3//! Handles drawing tool interactions on the chart pane:
4//! - Coordinate transformations between screen and chart space
5//! - Active drawing tool interactions (click, drag, multi-point)
6//! - Drawing selection and manipulation
7//! - Cursor mode effects (Eraser)
8//!
9//! ## Module Structure
10//! - `interaction` - Active drawing tool interaction handling
11//! - `selection` - Drawing selection, manipulation, eraser
12
13mod effects;
14mod interaction;
15mod selection;
16
17use crate::chart::cursor_modes::CursorModeState;
18use crate::drawings::DrawingInteractionMode;
19use crate::drawings::DrawingManager;
20use crate::widget::Chart;
21use egui::{Painter, Rect, Response, Ui};
22
23/// Actions emitted by cursor mode toolbar buttons.
24///
25/// These are returned to the caller so the UI layer can toggle the
26/// corresponding cursor mode states.
27#[derive(Clone, Debug, PartialEq)]
28pub enum CursorModeAction {
29 /// Toggle eraser mode active state
30 ToggleEraser,
31}
32
33impl Chart {
34 /// Handles drawing tool interaction and rendering
35 ///
36 /// This is the main entry point for drawing handling, coordinating:
37 /// - Coordinate transformations between screen and chart space
38 /// - Active drawing tool interactions (click, drag, multi-point)
39 /// - Drawing selection and manipulation
40 /// - Cursor mode effects (Eraser)
41 ///
42 /// # Arguments
43 /// - `cursor_modes` - Mutable reference to cursor mode state (passed separately to avoid borrow conflicts)
44 /// - `last_close_price` - The most recent close price (for position drawings P&L)
45 pub fn handle_drawings(
46 &self,
47 ui: &mut Ui,
48 drawing_manager: &mut DrawingManager,
49 cursor_modes: &mut CursorModeState,
50 response: &Response,
51 price_rect: Rect,
52 adjusted_min: f64,
53 adjusted_max: f64,
54 painter: &Painter,
55 last_close_price: Option<f64>,
56 timescale: &crate::model::TimeScale,
57 ) {
58 let adjusted_range = (adjusted_max - adjusted_min).max(1e-12);
59 let rect_min_x = price_rect.min.x;
60 let rect_max_y = price_rect.max.y;
61 let rect_height = price_rect.height();
62
63 let rect_width = price_rect.width();
64
65 // Coordinate conversion closures
66 // Use idx_to_coord_precise to preserve fractional bar indices for accurate drawing positions
67 // CRITICAL: Must pass the actual rect width, not timescale.width (which may differ)
68 let bar_to_x = |bar_idx: f32| -> f32 {
69 timescale.idx_to_coord_precise(bar_idx, rect_min_x, rect_width)
70 };
71 // x_to_bar_snapped: ROUNDED to nearest candle center (for line tools)
72 let x_to_bar_snapped =
73 |x: f32| -> f32 { timescale.coord_to_idx(x, rect_min_x, rect_width).round() };
74 // x_to_bar_precise: Full precision for freeform tools (Brush, Highlighter)
75 // Prevents pixelation when zooming - preserves sub-pixel positioning
76 let x_to_bar_precise =
77 |x: f32| -> f32 { timescale.coord_to_idx(x, rect_min_x, rect_width) };
78 let price_to_y = |price: f64| -> f32 {
79 let ratio = (price - adjusted_min) / adjusted_range;
80 rect_max_y - (ratio as f32 * rect_height)
81 };
82 let y_to_price = |y: f32| -> f64 {
83 let ratio = ((rect_max_y - y) / rect_height).clamp(0.0, 1.0) as f64;
84 adjusted_min + ratio * adjusted_range
85 };
86
87 // Update all drawings' screen coords before rendering (handles pan/zoom)
88 drawing_manager.update_all_screen_coords(bar_to_x, price_to_y);
89
90 // Update real-time prices for position drawings
91 if let Some(price) = last_close_price {
92 drawing_manager.update_pos_prices(price);
93 }
94
95 // Handle drawing interaction
96 // Select converter based on tool type: freeform tools (ContinuousDraw) use precise,
97 // all other tools snap to candle centers
98 if let Some(active_tool) = drawing_manager.active_tool {
99 let uses_precise =
100 active_tool.interaction_mode() == DrawingInteractionMode::ContinuousDraw;
101 if uses_precise {
102 interaction::handle_active_tool(
103 ui,
104 drawing_manager,
105 response,
106 price_rect,
107 &x_to_bar_precise,
108 &y_to_price,
109 active_tool,
110 );
111 } else {
112 interaction::handle_active_tool(
113 ui,
114 drawing_manager,
115 response,
116 price_rect,
117 &x_to_bar_snapped,
118 &y_to_price,
119 active_tool,
120 );
121 }
122 } else {
123 selection::handle_selection(
124 ui,
125 drawing_manager,
126 cursor_modes,
127 response,
128 price_rect,
129 &x_to_bar_snapped,
130 &y_to_price,
131 );
132 }
133
134 // Handle keyboard shortcuts for drawing manipulation
135 selection::handle_keyboard_shortcuts(ui, drawing_manager);
136
137 // Render existing drawings
138 drawing_manager.render_all(painter, price_rect);
139 }
140
141 /// Render eraser highlight for hovered drawing
142 pub fn render_eraser_highlight(
143 &self,
144 painter: &Painter,
145 drawing_manager: &DrawingManager,
146 cursor_modes: &CursorModeState,
147 ) {
148 selection::render_eraser_highlight(painter, drawing_manager, cursor_modes);
149 }
150}