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