retroglyph_ui/interact/
pointer.rs1use retroglyph_core::event::{Event, MouseButton, MouseEventKind};
5use retroglyph_core::grid::Pos;
6
7#[derive(Debug, Clone, Copy, Default)]
10struct ButtonState {
11 down: bool,
12 pressed: bool,
13 released: bool,
14}
15
16const fn button_slot(button: MouseButton) -> Option<usize> {
27 match button {
28 MouseButton::Left => Some(0),
29 MouseButton::Right => Some(1),
30 MouseButton::Middle => Some(2),
31 _ => None,
32 }
33}
34
35#[derive(Debug, Clone, Copy, Default)]
51pub struct Pointer {
52 pos: Option<Pos>,
53 buttons: [ButtonState; 3],
54 scroll_delta: i32,
55}
56
57impl Pointer {
58 #[must_use]
60 pub const fn new() -> Self {
61 Self {
62 pos: None,
63 buttons: [ButtonState {
64 down: false,
65 pressed: false,
66 released: false,
67 }; 3],
68 scroll_delta: 0,
69 }
70 }
71
72 pub const fn handle_event(&mut self, event: &Event) {
75 let Event::Mouse(mouse) = event else {
76 return;
77 };
78 self.pos = Some(mouse.position);
79 match mouse.kind {
80 MouseEventKind::Down(button) => {
81 if let Some(slot) = button_slot(button) {
82 let slot = &mut self.buttons[slot];
83 slot.down = true;
84 slot.pressed = true;
85 }
86 }
87 MouseEventKind::Up(button) => {
88 if let Some(slot) = button_slot(button) {
89 let slot = &mut self.buttons[slot];
90 slot.down = false;
91 slot.released = true;
92 }
93 }
94 MouseEventKind::Scroll { dy, .. } if dy > 0.0 => self.scroll_delta -= 1,
98 MouseEventKind::Scroll { dy, .. } if dy < 0.0 => self.scroll_delta += 1,
99 _ => {}
102 }
103 }
104
105 pub const fn end_frame(&mut self) {
108 let mut i = 0;
109 while i < self.buttons.len() {
110 self.buttons[i].pressed = false;
111 self.buttons[i].released = false;
112 i += 1;
113 }
114 self.scroll_delta = 0;
115 }
116
117 #[must_use]
120 pub const fn pos(&self) -> Option<Pos> {
121 self.pos
122 }
123
124 #[must_use]
129 pub const fn is_down(&self, button: MouseButton) -> bool {
130 match button_slot(button) {
131 Some(slot) => self.buttons[slot].down,
132 None => false,
133 }
134 }
135
136 #[must_use]
141 pub const fn pressed(&self, button: MouseButton) -> bool {
142 match button_slot(button) {
143 Some(slot) => self.buttons[slot].pressed,
144 None => false,
145 }
146 }
147
148 #[must_use]
153 pub const fn released(&self, button: MouseButton) -> bool {
154 match button_slot(button) {
155 Some(slot) => self.buttons[slot].released,
156 None => false,
157 }
158 }
159
160 #[must_use]
163 pub const fn scroll_delta(&self) -> i32 {
164 self.scroll_delta
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use retroglyph_core::event::{KeyModifiers, MouseEvent};
171
172 use super::*;
173
174 fn mouse(kind: MouseEventKind, pos: Pos) -> Event {
175 Event::Mouse(MouseEvent::new(kind, pos, KeyModifiers::NONE))
176 }
177
178 #[test]
179 fn press_and_release_are_one_shot() {
180 let mut p = Pointer::new();
181 p.handle_event(&mouse(
182 MouseEventKind::Down(MouseButton::Left),
183 Pos::new(3, 4),
184 ));
185 assert!(p.is_down(MouseButton::Left));
186 assert!(p.pressed(MouseButton::Left));
187 assert_eq!(p.pos(), Some(Pos::new(3, 4)));
188
189 p.end_frame();
190 assert!(p.is_down(MouseButton::Left)); assert!(!p.pressed(MouseButton::Left)); p.handle_event(&mouse(
194 MouseEventKind::Up(MouseButton::Left),
195 Pos::new(3, 4),
196 ));
197 assert!(!p.is_down(MouseButton::Left));
198 assert!(p.released(MouseButton::Left));
199 }
200
201 #[test]
202 fn buttons_are_tracked_independently() {
203 let mut p = Pointer::new();
204 p.handle_event(&mouse(
205 MouseEventKind::Down(MouseButton::Right),
206 Pos::new(1, 1),
207 ));
208 assert!(p.is_down(MouseButton::Right));
209 assert!(p.pressed(MouseButton::Right));
210 assert!(!p.is_down(MouseButton::Left));
212 assert!(!p.pressed(MouseButton::Left));
213 assert!(!p.is_down(MouseButton::Middle));
214 }
215
216 #[test]
217 fn scroll_accumulates_within_a_frame_and_clears_on_end_frame() {
218 let mut p = Pointer::new();
219 let scroll_down = MouseEventKind::Scroll { dx: 0.0, dy: -1.0 };
220 let scroll_up = MouseEventKind::Scroll { dx: 0.0, dy: 1.0 };
221 p.handle_event(&mouse(scroll_down, Pos::new(0, 0)));
222 p.handle_event(&mouse(scroll_down, Pos::new(0, 0)));
223 p.handle_event(&mouse(scroll_up, Pos::new(0, 0)));
224 assert_eq!(p.scroll_delta(), 1);
225
226 p.end_frame();
227 assert_eq!(p.scroll_delta(), 0);
228 }
229
230 #[test]
231 fn non_mouse_events_are_ignored() {
232 let mut p = Pointer::new();
233 p.handle_event(&Event::Resize(80, 24));
234 assert_eq!(p.pos(), None);
235 }
236}