extern crate alloc;
use alloc::vec::Vec;
use ui_events::pointer::{
PointerButton, PointerButtonEvent, PointerButtons, PointerEvent, PointerState, PointerUpdate,
};
use dpi::{LogicalPosition, PhysicalPosition};
#[derive(Clone, Debug, Default)]
pub struct PrimaryPointerState {
just_pressed: PointerButtons,
just_released: PointerButtons,
current: PointerState,
coalesced: Vec<PointerState>,
predicted: Vec<PointerState>,
}
impl PrimaryPointerState {
pub fn is_just_pressed(&self, button: PointerButton) -> bool {
self.just_pressed.contains(button)
}
pub fn is_just_released(&self, button: PointerButton) -> bool {
self.just_released.contains(button)
}
pub fn is_auxiliary_just_pressed(&self) -> bool {
self.is_just_pressed(PointerButton::Auxiliary)
}
pub fn is_auxiliary_just_released(&self) -> bool {
self.is_just_released(PointerButton::Auxiliary)
}
pub fn is_primary_just_pressed(&self) -> bool {
self.is_just_pressed(PointerButton::Primary)
}
pub fn is_primary_just_released(&self) -> bool {
self.is_just_released(PointerButton::Primary)
}
pub fn is_secondary_just_pressed(&self) -> bool {
self.is_just_pressed(PointerButton::Secondary)
}
pub fn is_secondary_just_released(&self) -> bool {
self.is_just_released(PointerButton::Secondary)
}
pub fn is_any_down(&self) -> bool {
!self.current.buttons.is_empty()
}
pub fn is_down(&self, button: PointerButton) -> bool {
self.current.buttons.contains(button)
}
pub fn clear_frame(&mut self) {
self.just_pressed.clear();
self.just_released.clear();
self.coalesced.clear();
self.predicted.clear();
}
pub fn current_position(&self) -> PhysicalPosition<f64> {
self.current.position
}
pub fn current_logical_position(&self) -> LogicalPosition<f64> {
self.current.logical_position()
}
pub fn motion(&self) -> PhysicalPosition<f64> {
let current = self.current.position;
let first = self
.coalesced
.first()
.map(|s| s.position)
.unwrap_or(current);
PhysicalPosition {
x: current.x - first.x,
y: current.y - first.y,
}
}
pub fn logical_motion(&self) -> LogicalPosition<f64> {
let current = self.current.logical_position();
let first = self
.coalesced
.first()
.map(|s| s.logical_position())
.unwrap_or(current);
LogicalPosition {
x: current.x - first.x,
y: current.y - first.y,
}
}
fn push_state(&mut self, state: PointerState) {
if state.time != 0 {
self.coalesced.push(state);
}
}
pub fn process_pointer_event(&mut self, event: PointerEvent) {
if !event.is_primary_pointer() {
return;
}
match event {
PointerEvent::Down(PointerButtonEvent {
button: Some(b),
state,
..
}) => {
self.just_pressed.insert(b);
let mut state = state.clone();
core::mem::swap(&mut self.current, &mut state);
self.push_state(state);
self.predicted.clear();
}
PointerEvent::Up(PointerButtonEvent {
button: Some(b),
state,
..
}) => {
self.just_released.insert(b);
let mut state = state.clone();
core::mem::swap(&mut self.current, &mut state);
self.push_state(state);
self.predicted.clear();
}
PointerEvent::Move(PointerUpdate {
current,
coalesced,
predicted,
..
}) => {
self.coalesced.push(self.current.clone());
self.current = current.clone();
self.coalesced.extend(coalesced);
self.predicted.clear();
self.predicted.extend(predicted);
}
PointerEvent::Cancel(_) | PointerEvent::Leave(_) => {
self.predicted.clear();
self.coalesced.clear();
self.current.buttons.clear();
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use ui_events::pointer::{
PointerButtonEvent, PointerEvent, PointerId, PointerInfo, PointerState, PointerType,
};
fn phony_time() -> u64 {
use core::sync::atomic::AtomicU64;
use core::sync::atomic::Ordering;
static TIME: AtomicU64 = AtomicU64::new(0);
TIME.fetch_add(1, Ordering::SeqCst)
}
fn make_down_event(button: PointerButton) -> PointerEvent {
PointerEvent::Down(PointerButtonEvent {
button: Some(button),
pointer: PointerInfo {
pointer_id: Some(PointerId::PRIMARY),
persistent_device_id: None,
pointer_type: PointerType::Mouse,
},
state: PointerState {
time: phony_time(),
buttons: button.into(),
..Default::default()
},
})
}
fn make_up_event(button: PointerButton) -> PointerEvent {
PointerEvent::Up(PointerButtonEvent {
button: Some(button),
pointer: PointerInfo {
pointer_id: Some(PointerId::PRIMARY),
persistent_device_id: None,
pointer_type: PointerType::Mouse,
},
state: PointerState {
time: phony_time(),
..Default::default()
},
})
}
#[test]
fn press_and_hold_primary() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_down_event(PointerButton::Primary));
assert!(state.is_primary_just_pressed());
assert!(state.is_down(PointerButton::Primary));
assert!(!state.is_primary_just_released());
state.clear_frame();
assert!(!state.is_primary_just_pressed());
assert!(state.is_down(PointerButton::Primary));
}
#[test]
fn press_and_release_primary_same_frame() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_down_event(PointerButton::Primary));
state.process_pointer_event(make_up_event(PointerButton::Primary));
assert!(state.is_primary_just_pressed());
assert!(state.is_primary_just_released());
assert!(!state.is_down(PointerButton::Primary));
}
#[test]
fn release_after_hold() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_down_event(PointerButton::Primary));
state.clear_frame();
state.process_pointer_event(make_up_event(PointerButton::Primary));
assert!(!state.is_primary_just_pressed());
assert!(state.is_primary_just_released());
assert!(!state.is_down(PointerButton::Primary));
}
fn make_move_event(
position: PhysicalPosition<f64>,
coalesced: Vec<PhysicalPosition<f64>>,
predicted: Vec<PhysicalPosition<f64>>,
) -> PointerEvent {
let coalesced = coalesced
.iter()
.copied()
.map(|position| PointerState {
time: phony_time(),
position,
..Default::default()
})
.collect();
let current = PointerState {
time: phony_time(),
position,
..Default::default()
};
let predicted = predicted
.iter()
.copied()
.map(|position| PointerState {
time: phony_time(),
position,
..Default::default()
})
.collect();
PointerEvent::Move(PointerUpdate {
pointer: PointerInfo {
pointer_id: Some(PointerId::PRIMARY),
persistent_device_id: None,
pointer_type: PointerType::Mouse,
},
current,
coalesced,
predicted,
})
}
fn make_cancel_event() -> PointerEvent {
PointerEvent::Cancel(PointerInfo {
pointer_id: Some(PointerId::PRIMARY),
persistent_device_id: None,
pointer_type: PointerType::Mouse,
})
}
fn make_leave_event() -> PointerEvent {
PointerEvent::Leave(PointerInfo {
pointer_id: Some(PointerId::PRIMARY),
persistent_device_id: None,
pointer_type: PointerType::Mouse,
})
}
#[test]
fn down_updates_current_buttons() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_down_event(PointerButton::Primary));
assert!(state.current.buttons.contains(PointerButton::Primary));
assert!(state.is_down(PointerButton::Primary));
assert!(state.is_any_down());
}
#[test]
fn up_updates_current_buttons() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_down_event(PointerButton::Primary));
state.process_pointer_event(make_up_event(PointerButton::Primary));
assert!(!state.current.buttons.contains(PointerButton::Primary));
assert!(!state.is_down(PointerButton::Primary));
assert!(!state.is_any_down());
}
#[test]
fn move_appends_to_coalesced_if_down() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_down_event(PointerButton::Primary));
state.process_pointer_event(make_move_event(
PhysicalPosition { x: 10.0, y: 10.0 },
vec![PhysicalPosition { x: 5.0, y: 5.0 }],
vec![],
));
assert_eq!(state.coalesced.len(), 2);
assert_eq!(
state.coalesced[1].position,
PhysicalPosition { x: 5.0, y: 5.0 }
);
assert_eq!(
state.current.position,
PhysicalPosition { x: 10.0, y: 10.0 }
);
}
#[test]
fn move_appends_to_coalesced_even_if_not_down() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_move_event(
PhysicalPosition { x: 10.0, y: 10.0 },
vec![PhysicalPosition { x: 5.0, y: 5.0 }],
vec![],
));
assert_eq!(state.coalesced.len(), 2);
assert_eq!(state.coalesced[0].position, PhysicalPosition::default());
assert_eq!(
state.coalesced[1].position,
PhysicalPosition { x: 5.0, y: 5.0 }
);
assert_eq!(
state.current.position,
PhysicalPosition { x: 10.0, y: 10.0 }
);
}
#[test]
fn move_sets_predicted() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_move_event(
PhysicalPosition { x: 10.0, y: 10.0 },
vec![],
vec![PhysicalPosition { x: 15.0, y: 15.0 }],
));
assert_eq!(state.predicted.len(), 1);
assert_eq!(
state.predicted[0].position,
PhysicalPosition { x: 15.0, y: 15.0 }
);
}
#[test]
fn down_clears_predicted() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_move_event(
PhysicalPosition { x: 10.0, y: 10.0 },
vec![],
vec![PhysicalPosition { x: 15.0, y: 15.0 }],
));
assert!(!state.predicted.is_empty());
state.process_pointer_event(make_down_event(PointerButton::Primary));
assert!(state.predicted.is_empty());
}
#[test]
fn up_clears_predicted() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_down_event(PointerButton::Primary));
state.process_pointer_event(make_move_event(
PhysicalPosition { x: 10.0, y: 10.0 },
vec![],
vec![PhysicalPosition { x: 15.0, y: 15.0 }],
));
assert!(!state.predicted.is_empty());
state.process_pointer_event(make_up_event(PointerButton::Primary));
assert!(state.predicted.is_empty());
}
#[test]
fn cancel_clears_states() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_down_event(PointerButton::Primary));
assert!(state.predicted.is_empty());
assert!(!state.current.buttons.is_empty());
state.process_pointer_event(make_cancel_event());
assert!(state.coalesced.is_empty());
assert!(state.predicted.is_empty());
assert!(state.current.buttons.is_empty());
}
#[test]
fn leave_clears_states() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_down_event(PointerButton::Primary));
assert!(state.predicted.is_empty());
assert!(!state.current.buttons.is_empty());
state.process_pointer_event(make_leave_event());
assert!(state.coalesced.is_empty());
assert!(state.predicted.is_empty());
assert!(state.current.buttons.is_empty());
}
#[test]
fn clear_frame_clears_coalesced_and_predicted() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_move_event(
PhysicalPosition { x: 10.0, y: 10.0 },
vec![PhysicalPosition { x: 5.0, y: 5.0 }],
vec![PhysicalPosition { x: 15.0, y: 15.0 }],
));
assert!(!state.coalesced.is_empty());
assert!(!state.predicted.is_empty());
state.clear_frame();
assert!(state.coalesced.is_empty());
assert!(state.predicted.is_empty());
}
#[test]
fn current_position_and_logical() {
let mut state = PrimaryPointerState::default();
let position = PhysicalPosition { x: 100.0, y: 200.0 };
let scale_factor = 2.0;
let current = PointerState {
time: phony_time(),
position,
scale_factor,
..Default::default()
};
state.process_pointer_event(PointerEvent::Move(PointerUpdate {
pointer: PointerInfo {
pointer_id: Some(PointerId::PRIMARY),
persistent_device_id: None,
pointer_type: PointerType::Mouse,
},
current,
coalesced: vec![],
predicted: vec![],
}));
assert_eq!(state.current_position(), position);
assert_eq!(
state.current_logical_position(),
position.to_logical(scale_factor)
);
}
#[test]
fn motion_with_coalesced() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_move_event(
PhysicalPosition { x: 10.0, y: 20.0 },
vec![],
vec![],
));
state.process_pointer_event(make_move_event(
PhysicalPosition { x: 30.0, y: 40.0 },
vec![PhysicalPosition { x: 15.0, y: 25.0 }],
vec![],
));
assert_eq!(state.motion(), PhysicalPosition { x: 30.0, y: 40.0 });
assert_eq!(state.logical_motion(), LogicalPosition { x: 30.0, y: 40.0 });
}
#[test]
fn motion_without_coalesced() {
let mut state = PrimaryPointerState::default();
state.process_pointer_event(make_move_event(
PhysicalPosition { x: 30.0, y: 40.0 },
vec![],
vec![],
));
assert_eq!(state.motion(), PhysicalPosition { x: 30.0, y: 40.0 });
assert_eq!(state.logical_motion(), LogicalPosition { x: 30.0, y: 40.0 });
}
}