Skip to main content

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}