use retroglyph_core::{Event, MouseButton, MouseEventKind, Pos};
#[derive(Debug, Clone, Copy, Default)]
struct ButtonState {
down: bool,
pressed: bool,
released: bool,
}
const fn button_slot(button: MouseButton) -> Option<usize> {
match button {
MouseButton::Left => Some(0),
MouseButton::Right => Some(1),
MouseButton::Middle => Some(2),
_ => None,
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Pointer {
pos: Option<Pos>,
buttons: [ButtonState; 3],
scroll_delta: i32,
}
impl Pointer {
#[must_use]
pub const fn new() -> Self {
Self {
pos: None,
buttons: [ButtonState {
down: false,
pressed: false,
released: false,
}; 3],
scroll_delta: 0,
}
}
pub const fn handle_event(&mut self, event: &Event) {
let Event::Mouse(mouse) = event else {
return;
};
self.pos = Some(mouse.position);
match mouse.kind {
MouseEventKind::Down(button) => {
if let Some(slot) = button_slot(button) {
let slot = &mut self.buttons[slot];
slot.down = true;
slot.pressed = true;
}
}
MouseEventKind::Up(button) => {
if let Some(slot) = button_slot(button) {
let slot = &mut self.buttons[slot];
slot.down = false;
slot.released = true;
}
}
MouseEventKind::ScrollUp => self.scroll_delta -= 1,
MouseEventKind::ScrollDown => self.scroll_delta += 1,
_ => {}
}
}
pub const fn end_frame(&mut self) {
let mut i = 0;
while i < self.buttons.len() {
self.buttons[i].pressed = false;
self.buttons[i].released = false;
i += 1;
}
self.scroll_delta = 0;
}
#[must_use]
pub const fn pos(&self) -> Option<Pos> {
self.pos
}
#[must_use]
pub const fn is_down(&self, button: MouseButton) -> bool {
match button_slot(button) {
Some(slot) => self.buttons[slot].down,
None => false,
}
}
#[must_use]
pub const fn pressed(&self, button: MouseButton) -> bool {
match button_slot(button) {
Some(slot) => self.buttons[slot].pressed,
None => false,
}
}
#[must_use]
pub const fn released(&self, button: MouseButton) -> bool {
match button_slot(button) {
Some(slot) => self.buttons[slot].released,
None => false,
}
}
#[must_use]
pub const fn scroll_delta(&self) -> i32 {
self.scroll_delta
}
}
#[cfg(test)]
mod tests {
use retroglyph_core::{KeyModifiers, MouseEvent};
use super::*;
fn mouse(kind: MouseEventKind, pos: Pos) -> Event {
Event::Mouse(MouseEvent {
kind,
position: pos,
pixel_position: None,
modifiers: KeyModifiers::NONE,
})
}
#[test]
fn press_and_release_are_one_shot() {
let mut p = Pointer::new();
p.handle_event(&mouse(
MouseEventKind::Down(MouseButton::Left),
Pos::new(3, 4),
));
assert!(p.is_down(MouseButton::Left));
assert!(p.pressed(MouseButton::Left));
assert_eq!(p.pos(), Some(Pos::new(3, 4)));
p.end_frame();
assert!(p.is_down(MouseButton::Left)); assert!(!p.pressed(MouseButton::Left));
p.handle_event(&mouse(
MouseEventKind::Up(MouseButton::Left),
Pos::new(3, 4),
));
assert!(!p.is_down(MouseButton::Left));
assert!(p.released(MouseButton::Left));
}
#[test]
fn buttons_are_tracked_independently() {
let mut p = Pointer::new();
p.handle_event(&mouse(
MouseEventKind::Down(MouseButton::Right),
Pos::new(1, 1),
));
assert!(p.is_down(MouseButton::Right));
assert!(p.pressed(MouseButton::Right));
assert!(!p.is_down(MouseButton::Left));
assert!(!p.pressed(MouseButton::Left));
assert!(!p.is_down(MouseButton::Middle));
}
#[test]
fn scroll_accumulates_within_a_frame_and_clears_on_end_frame() {
let mut p = Pointer::new();
p.handle_event(&mouse(MouseEventKind::ScrollDown, Pos::new(0, 0)));
p.handle_event(&mouse(MouseEventKind::ScrollDown, Pos::new(0, 0)));
p.handle_event(&mouse(MouseEventKind::ScrollUp, Pos::new(0, 0)));
assert_eq!(p.scroll_delta(), 1);
p.end_frame();
assert_eq!(p.scroll_delta(), 0);
}
#[test]
fn non_mouse_events_are_ignored() {
let mut p = Pointer::new();
p.handle_event(&Event::Resize(80, 24));
assert_eq!(p.pos(), None);
}
}