use cranpose_ui_graphics::Point;
use std::cell::Cell;
use std::rc::Rc;
pub type PointerId = u64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PointerPhase {
Start,
Move,
End,
Cancel,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PointerEventKind {
Down,
Move,
Up,
Cancel,
Scroll,
Enter,
Exit,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PointerButton {
Primary = 0,
Secondary = 1,
Middle = 2,
Back = 3,
Forward = 4,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PointerButtons(u8);
impl PointerButtons {
pub const NONE: Self = Self(0);
pub fn new() -> Self {
Self::NONE
}
pub fn with(mut self, button: PointerButton) -> Self {
self.insert(button);
self
}
pub fn insert(&mut self, button: PointerButton) {
self.0 |= 1 << (button as u8);
}
pub fn remove(&mut self, button: PointerButton) {
self.0 &= !(1 << (button as u8));
}
pub fn contains(&self, button: PointerButton) -> bool {
(self.0 & (1 << (button as u8))) != 0
}
}
impl Default for PointerButtons {
fn default() -> Self {
Self::NONE
}
}
#[derive(Clone, Debug)]
pub struct PointerEvent {
pub id: PointerId,
pub kind: PointerEventKind,
pub phase: PointerPhase,
pub position: Point,
pub global_position: Point,
pub scroll_delta: Point,
pub buttons: PointerButtons,
consumed: Rc<Cell<bool>>,
}
impl PointerEvent {
pub fn new(kind: PointerEventKind, position: Point, global_position: Point) -> Self {
Self {
id: 0,
kind,
phase: match kind {
PointerEventKind::Down => PointerPhase::Start,
PointerEventKind::Move | PointerEventKind::Enter | PointerEventKind::Exit => {
PointerPhase::Move
}
PointerEventKind::Up => PointerPhase::End,
PointerEventKind::Cancel => PointerPhase::Cancel,
PointerEventKind::Scroll => PointerPhase::Move,
},
position,
global_position,
scroll_delta: Point { x: 0.0, y: 0.0 },
buttons: PointerButtons::NONE,
consumed: Rc::new(Cell::new(false)),
}
}
pub fn with_scroll_delta(mut self, scroll_delta: Point) -> Self {
self.scroll_delta = scroll_delta;
self
}
pub fn with_buttons(mut self, buttons: PointerButtons) -> Self {
self.buttons = buttons;
self
}
pub fn consume(&self) {
self.consumed.set(true);
}
pub fn is_consumed(&self) -> bool {
self.consumed.get()
}
pub fn copy_with_local_position(&self, position: Point) -> Self {
Self {
id: self.id,
kind: self.kind,
phase: self.phase,
position,
global_position: self.global_position,
scroll_delta: self.scroll_delta,
buttons: self.buttons,
consumed: self.consumed.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn point(x: f32, y: f32) -> Point {
Point { x, y }
}
#[test]
fn pointer_event_clones_share_consumed_state() {
let event = PointerEvent::new(PointerEventKind::Move, point(1.0, 2.0), point(3.0, 4.0));
let cloned = event.clone();
assert!(!event.is_consumed());
assert!(!cloned.is_consumed());
cloned.consume();
assert!(event.is_consumed());
assert!(cloned.is_consumed());
}
#[test]
fn pointer_event_copy_with_local_position_preserves_consumption_state() {
let event = PointerEvent::new(PointerEventKind::Down, point(4.0, 5.0), point(4.0, 5.0));
let local = event.copy_with_local_position(point(1.0, 1.0));
assert_eq!(local.position, point(1.0, 1.0));
assert_eq!(local.global_position, event.global_position);
assert!(!local.is_consumed());
event.consume();
assert!(local.is_consumed());
}
}