Skip to main content

egui_charts/chart/
interaction.rs

1//! User interaction handling for the chart widget.
2//!
3//! Implements tracking mode (auto-scroll to latest bar) and keyboard shortcuts
4//! (arrow keys for pan, +/- for zoom, Home for jump-to-latest, F for fit-all).
5//! These methods are called by the main widget paint loop.
6
7use egui::{Response, Ui};
8
9use crate::widget::Chart;
10
11impl Chart {
12    /// Manage tracking mode — auto-scroll to the latest bar while active.
13    ///
14    /// Tracking mode keeps the chart viewport anchored to real-time data.
15    /// It exits automatically based on the configured
16    /// [`TrackingModeExitMode`](crate::config::TrackingModeExitMode): on mouse leave,
17    /// on next tap, or on touch end. Any manual scroll/zoom/drag also deactivates it.
18    pub fn handle_tracking_mode(&mut self, ui: &Ui, response: &Response) {
19        // Track mouse entering/leaving chart area for tracking mode
20        let was_in_chart = self.mouse_in_chart;
21        self.mouse_in_chart = response.hovered();
22
23        // Handle tracking mode exit conditions
24        use crate::config::TrackingModeExitMode;
25        if self.tracking_mode_active {
26            match self.chart_options.tracking_mode.exit_mode {
27                TrackingModeExitMode::OnMouseLeave => {
28                    // Exit when mouse leaves the chart
29                    if was_in_chart && !self.mouse_in_chart {
30                        self.tracking_mode_active = false;
31                    }
32                }
33                TrackingModeExitMode::OnNextTap => {
34                    // Exit on any click
35                    if response.clicked() {
36                        self.tracking_mode_active = false;
37                    }
38                }
39                TrackingModeExitMode::OnTouchEnd => {
40                    // Exit when drag/touch ends
41                    if !response.dragged() && self.scroll_start_pos.is_some() {
42                        self.tracking_mode_active = false;
43                    }
44                }
45            }
46
47            // Exit tracking mode on any manual interaction (scroll, zoom, drag)
48            if response.dragged()
49                || ui.input(|i| {
50                    i.raw_scroll_delta.length_sq() > 0.0 || i.smooth_scroll_delta.length_sq() > 0.0
51                })
52            {
53                self.tracking_mode_active = false;
54            }
55        }
56
57        // If tracking mode is active, keep scrolling to latest
58        if self.tracking_mode_active {
59            self.state.time_scale_mut().scroll_to_realtime();
60        }
61    }
62
63    /// Process keyboard shortcuts when the chart has focus.
64    ///
65    /// | Key | Action |
66    /// |-----|--------|
67    /// | Left/Right | Pan by configured `pan_amount` bars |
68    /// | +/- | Zoom in/out by `zoom_step` centered on chart |
69    /// | Home | Scroll to latest (real-time) data |
70    /// | F | Fit all data into the viewport |
71    /// | PageUp/PageDown | Zoom in/out by `3x zoom_step` |
72    ///
73    /// Shortcuts are only active when `chart_options.keyboard.enabled` is `true`
74    /// and the chart response has focus.
75    pub fn handle_keyboard_shortcuts(
76        &mut self,
77        ui: &Ui,
78        response: &Response,
79        chart_width: f32,
80        chart_rect_min_x: f32,
81    ) {
82        if !self.chart_options.keyboard.enabled || !response.has_focus() {
83            return;
84        }
85
86        ui.input(|i| {
87            use egui::Key;
88
89            // Pan left (Left arrow)
90            if i.key_pressed(Key::ArrowLeft) {
91                self.state
92                    .time_scale_mut()
93                    .scroll_bars(-self.chart_options.keyboard.pan_amount);
94            }
95
96            // Pan right (Right arrow)
97            if i.key_pressed(Key::ArrowRight) {
98                self.state
99                    .time_scale_mut()
100                    .scroll_bars(self.chart_options.keyboard.pan_amount);
101            }
102
103            // Zoom in (+)
104            if i.key_pressed(Key::Plus) || i.key_pressed(Key::Equals) {
105                let zoom_point_x = chart_width / 2.0;
106                self.state.time_scale_mut().zoom(
107                    self.chart_options.keyboard.zoom_step,
108                    zoom_point_x,
109                    chart_rect_min_x,
110                    chart_width,
111                );
112            }
113
114            // Zoom out (-)
115            if i.key_pressed(Key::Minus) {
116                let zoom_point_x = chart_width / 2.0;
117                self.state.time_scale_mut().zoom(
118                    -self.chart_options.keyboard.zoom_step,
119                    zoom_point_x,
120                    chart_rect_min_x,
121                    chart_width,
122                );
123            }
124
125            // Scroll to real-time / latest data (Home)
126            if i.key_pressed(Key::Home) {
127                self.state.time_scale_mut().scroll_to_realtime();
128            }
129
130            // Fit content (F) - zoom to show all data
131            if i.key_pressed(Key::F) {
132                self.state.time_scale_mut().fit_content();
133            }
134
135            // Page Up (zoom in more)
136            if i.key_pressed(Key::PageUp) {
137                let zoom_point_x = chart_width / 2.0;
138                self.state.time_scale_mut().zoom(
139                    self.chart_options.keyboard.zoom_step * 3.0,
140                    zoom_point_x,
141                    chart_rect_min_x,
142                    chart_width,
143                );
144            }
145
146            // Page Down (zoom out more)
147            if i.key_pressed(Key::PageDown) {
148                let zoom_point_x = chart_width / 2.0;
149                self.state.time_scale_mut().zoom(
150                    -self.chart_options.keyboard.zoom_step * 3.0,
151                    zoom_point_x,
152                    chart_rect_min_x,
153                    chart_width,
154                );
155            }
156        });
157    }
158
159    /// Automatically request keyboard focus when the pointer hovers over the chart,
160    /// so keyboard shortcuts work without an explicit click.
161    pub fn request_focus_if_needed(&self, response: &mut Response) {
162        if self.chart_options.keyboard.enabled && response.hovered() {
163            response.request_focus();
164        }
165    }
166
167    /// Display a grabbing cursor while the user is actively dragging (panning) the chart.
168    pub fn set_panning_cursor(&self, ui: &Ui, response: &Response) {
169        if response.dragged() {
170            ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing);
171        }
172    }
173}