Skip to main content

runmat_plot/core/
interaction.rs

1//! Event handling and user interaction for interactive plots
2//!
3//! Manages mouse, keyboard, and touch input for plot navigation
4//! and data interaction.
5
6use glam::Vec2;
7
8/// Keyboard modifier keys captured alongside pointer events.
9#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
10pub struct Modifiers {
11    pub shift: bool,
12    pub ctrl: bool,
13    pub alt: bool,
14    pub meta: bool,
15}
16
17/// Event types for plot interaction
18#[derive(Debug, Clone)]
19pub enum PlotEvent {
20    MousePress {
21        position: Vec2,
22        button: MouseButton,
23        modifiers: Modifiers,
24    },
25    MouseRelease {
26        position: Vec2,
27        button: MouseButton,
28        modifiers: Modifiers,
29    },
30    MouseMove {
31        position: Vec2,
32        delta: Vec2,
33        buttons: u32,
34        modifiers: Modifiers,
35    },
36    /// Mouse wheel / trackpad scroll.
37    ///
38    /// `position` is the pointer location in the same coordinate space as other mouse events.
39    MouseWheel {
40        position: Vec2,
41        delta: Vec2,
42        modifiers: Modifiers,
43    },
44    KeyPress {
45        key: KeyCode,
46    },
47    KeyRelease {
48        key: KeyCode,
49    },
50    Resize {
51        width: u32,
52        height: u32,
53    },
54}
55
56/// Mouse button enumeration
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum MouseButton {
59    Left,
60    Right,
61    Middle,
62}
63
64/// Key code enumeration (subset for now)
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum KeyCode {
67    Escape,
68    Space,
69    Enter,
70    Tab,
71    Backspace,
72    Delete,
73    Home,
74    End,
75    PageUp,
76    PageDown,
77    ArrowUp,
78    ArrowDown,
79    ArrowLeft,
80    ArrowRight,
81    // Add more as needed
82}
83
84/// Event handler trait for plot interaction
85pub trait EventHandler {
86    fn handle_event(&mut self, event: PlotEvent) -> bool;
87}
88
89#[cfg(feature = "egui-overlay")]
90pub fn egui_raw_input_from_plot_events(
91    screen_rect: egui::Rect,
92    pixels_per_point: f32,
93    events: Vec<PlotEvent>,
94) -> egui::RawInput {
95    let ppp = pixels_per_point.max(0.5);
96    let mut egui_events = Vec::with_capacity(events.len());
97    for event in events {
98        match event {
99            PlotEvent::MousePress {
100                position,
101                button,
102                modifiers,
103            } => {
104                egui_events.push(egui::Event::PointerMoved(egui_pos(position, ppp)));
105                egui_events.push(egui::Event::PointerButton {
106                    pos: egui_pos(position, ppp),
107                    button: egui_button(button),
108                    pressed: true,
109                    modifiers: egui_modifiers(modifiers),
110                });
111            }
112            PlotEvent::MouseRelease {
113                position,
114                button,
115                modifiers,
116            } => {
117                egui_events.push(egui::Event::PointerMoved(egui_pos(position, ppp)));
118                egui_events.push(egui::Event::PointerButton {
119                    pos: egui_pos(position, ppp),
120                    button: egui_button(button),
121                    pressed: false,
122                    modifiers: egui_modifiers(modifiers),
123                });
124            }
125            PlotEvent::MouseMove {
126                position, delta, ..
127            } => {
128                egui_events.push(egui::Event::PointerMoved(egui_pos(position, ppp)));
129                if delta.length_squared() > f32::EPSILON {
130                    egui_events.push(egui::Event::MouseMoved(egui::vec2(
131                        delta.x / ppp,
132                        delta.y / ppp,
133                    )));
134                }
135            }
136            PlotEvent::MouseWheel {
137                position,
138                delta,
139                modifiers,
140            } => {
141                egui_events.push(egui::Event::PointerMoved(egui_pos(position, ppp)));
142                #[cfg(target_arch = "wasm32")]
143                egui_events.push(egui::Event::MouseWheel {
144                    unit: egui::MouseWheelUnit::Point,
145                    delta: egui::vec2(delta.x * 80.0, delta.y * 80.0),
146                    modifiers: egui_modifiers(modifiers),
147                });
148                #[cfg(not(target_arch = "wasm32"))]
149                egui_events.push(egui::Event::Scroll(egui::vec2(
150                    delta.x * 80.0,
151                    delta.y * 80.0,
152                )));
153                if modifiers.ctrl || modifiers.meta {
154                    egui_events.push(egui::Event::Zoom((1.0 + delta.y * 0.08).clamp(0.2, 5.0)));
155                }
156            }
157            PlotEvent::Resize { .. }
158            | PlotEvent::KeyPress { .. }
159            | PlotEvent::KeyRelease { .. } => {}
160        }
161    }
162
163    egui::RawInput {
164        screen_rect: Some(screen_rect),
165        events: egui_events,
166        viewports: std::iter::once((
167            egui::ViewportId::ROOT,
168            egui::ViewportInfo {
169                native_pixels_per_point: Some(ppp),
170                inner_rect: Some(screen_rect),
171                outer_rect: Some(screen_rect),
172                focused: Some(true),
173                ..Default::default()
174            },
175        ))
176        .collect(),
177        ..Default::default()
178    }
179}
180
181#[cfg(feature = "egui-overlay")]
182fn egui_pos(position: Vec2, pixels_per_point: f32) -> egui::Pos2 {
183    egui::pos2(position.x / pixels_per_point, position.y / pixels_per_point)
184}
185
186#[cfg(feature = "egui-overlay")]
187fn egui_modifiers(value: Modifiers) -> egui::Modifiers {
188    egui::Modifiers {
189        alt: value.alt,
190        ctrl: value.ctrl,
191        shift: value.shift,
192        mac_cmd: value.meta,
193        command: value.meta || value.ctrl,
194    }
195}
196
197#[cfg(feature = "egui-overlay")]
198fn egui_button(button: MouseButton) -> egui::PointerButton {
199    match button {
200        MouseButton::Left => egui::PointerButton::Primary,
201        MouseButton::Right => egui::PointerButton::Secondary,
202        MouseButton::Middle => egui::PointerButton::Middle,
203    }
204}