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}