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::ScrollUp => self.scroll_delta -= 1,
94 MouseEventKind::ScrollDown => self.scroll_delta += 1,
95 _ => {}
98 }
99 }
100
101 pub const fn end_frame(&mut self) {
104 let mut i = 0;
105 while i < self.buttons.len() {
106 self.buttons[i].pressed = false;
107 self.buttons[i].released = false;
108 i += 1;
109 }
110 self.scroll_delta = 0;
111 }
112
113 #[must_use]
116 pub const fn pos(&self) -> Option<Pos> {
117 self.pos
118 }
119
120 #[must_use]
125 pub const fn is_down(&self, button: MouseButton) -> bool {
126 match button_slot(button) {
127 Some(slot) => self.buttons[slot].down,
128 None => false,
129 }
130 }
131
132 #[must_use]
137 pub const fn pressed(&self, button: MouseButton) -> bool {
138 match button_slot(button) {
139 Some(slot) => self.buttons[slot].pressed,
140 None => false,
141 }
142 }
143
144 #[must_use]
149 pub const fn released(&self, button: MouseButton) -> bool {
150 match button_slot(button) {
151 Some(slot) => self.buttons[slot].released,
152 None => false,
153 }
154 }
155
156 #[must_use]
159 pub const fn scroll_delta(&self) -> i32 {
160 self.scroll_delta
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use retroglyph_core::{KeyModifiers, MouseEvent};
167
168 use super::*;
169
170 fn mouse(kind: MouseEventKind, pos: Pos) -> Event {
171 Event::Mouse(MouseEvent {
172 kind,
173 position: pos,
174 pixel_position: None,
175 modifiers: KeyModifiers::NONE,
176 })
177 }
178
179 #[test]
180 fn press_and_release_are_one_shot() {
181 let mut p = Pointer::new();
182 p.handle_event(&mouse(
183 MouseEventKind::Down(MouseButton::Left),
184 Pos::new(3, 4),
185 ));
186 assert!(p.is_down(MouseButton::Left));
187 assert!(p.pressed(MouseButton::Left));
188 assert_eq!(p.pos(), Some(Pos::new(3, 4)));
189
190 p.end_frame();
191 assert!(p.is_down(MouseButton::Left)); assert!(!p.pressed(MouseButton::Left)); p.handle_event(&mouse(
195 MouseEventKind::Up(MouseButton::Left),
196 Pos::new(3, 4),
197 ));
198 assert!(!p.is_down(MouseButton::Left));
199 assert!(p.released(MouseButton::Left));
200 }
201
202 #[test]
203 fn buttons_are_tracked_independently() {
204 let mut p = Pointer::new();
205 p.handle_event(&mouse(
206 MouseEventKind::Down(MouseButton::Right),
207 Pos::new(1, 1),
208 ));
209 assert!(p.is_down(MouseButton::Right));
210 assert!(p.pressed(MouseButton::Right));
211 assert!(!p.is_down(MouseButton::Left));
213 assert!(!p.pressed(MouseButton::Left));
214 assert!(!p.is_down(MouseButton::Middle));
215 }
216
217 #[test]
218 fn scroll_accumulates_within_a_frame_and_clears_on_end_frame() {
219 let mut p = Pointer::new();
220 p.handle_event(&mouse(MouseEventKind::ScrollDown, Pos::new(0, 0)));
221 p.handle_event(&mouse(MouseEventKind::ScrollDown, Pos::new(0, 0)));
222 p.handle_event(&mouse(MouseEventKind::ScrollUp, Pos::new(0, 0)));
223 assert_eq!(p.scroll_delta(), 1);
224
225 p.end_frame();
226 assert_eq!(p.scroll_delta(), 0);
227 }
228
229 #[test]
230 fn non_mouse_events_are_ignored() {
231 let mut p = Pointer::new();
232 p.handle_event(&Event::Resize(80, 24));
233 assert_eq!(p.pos(), None);
234 }
235}