use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use cranpose_ui_graphics::Point;
use super::rotary::RotaryScrollEvent;
pub type PointerId = u64;
type PostDispatchAction = Box<dyn FnOnce() -> bool>;
#[derive(Clone)]
struct DeferredPostDispatch {
action: Rc<RefCell<Option<PostDispatchAction>>>,
}
impl std::fmt::Debug for DeferredPostDispatch {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("DeferredPostDispatch")
.field("is_pending", &self.action.borrow().is_some())
.finish()
}
}
#[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,
Zoom,
RotaryScrollPre,
RotaryScroll,
Enter,
Exit,
}
impl PointerEventKind {
pub fn is_rotary(self) -> bool {
matches!(self, Self::RotaryScrollPre | Self::RotaryScroll)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum PointerSource {
Mouse,
Touch,
Stylus,
#[default]
Unknown,
}
impl PointerSource {
pub fn is_touch_like(self) -> bool {
matches!(self, PointerSource::Touch | PointerSource::Stylus)
}
}
#[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(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Modifiers {
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub meta: bool,
}
impl Modifiers {
pub const NONE: Modifiers = Modifiers {
shift: false,
ctrl: false,
alt: false,
meta: false,
};
pub fn any(&self) -> bool {
self.shift || self.ctrl || self.alt || self.meta
}
pub fn command_or_ctrl(&self) -> bool {
#[cfg(target_os = "macos")]
{
self.meta
}
#[cfg(not(target_os = "macos"))]
{
self.ctrl
}
}
}
#[derive(Clone, Debug)]
pub struct PointerEvent {
pub id: PointerId,
pub kind: PointerEventKind,
pub phase: PointerPhase,
pub position: Point,
pub global_position: Point,
pub screen_position: Option<Point>,
pub scroll_delta: Point,
pub buttons: PointerButtons,
pub time_ms: Option<i64>,
pub animation_time_nanos: Option<u64>,
pub zoom_delta: f32,
pub source: PointerSource,
pub modifiers: Option<Modifiers>,
consumed: Rc<Cell<bool>>,
deferred_post_dispatch: DeferredPostDispatch,
}
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
| PointerEventKind::Zoom
| PointerEventKind::RotaryScrollPre
| PointerEventKind::RotaryScroll => PointerPhase::Move,
},
position,
global_position,
screen_position: None,
scroll_delta: Point { x: 0.0, y: 0.0 },
buttons: PointerButtons::NONE,
time_ms: None,
animation_time_nanos: None,
zoom_delta: 1.0,
source: PointerSource::Unknown,
modifiers: None,
consumed: Rc::new(Cell::new(false)),
deferred_post_dispatch: DeferredPostDispatch {
action: Rc::new(RefCell::new(None)),
},
}
}
pub fn with_id(mut self, id: PointerId) -> Self {
self.id = id;
self
}
pub fn with_zoom_delta(mut self, zoom_delta: f32) -> Self {
self.zoom_delta = zoom_delta;
self
}
pub fn with_scroll_delta(mut self, scroll_delta: Point) -> Self {
self.scroll_delta = scroll_delta;
self
}
pub fn with_time_ms(mut self, time_ms: Option<i64>) -> Self {
self.time_ms = time_ms;
self
}
pub fn with_animation_time_nanos(mut self, time_nanos: u64) -> Self {
self.animation_time_nanos = Some(time_nanos);
self
}
pub fn with_screen_position(mut self, screen_position: Option<Point>) -> Self {
self.screen_position = screen_position;
self
}
pub fn travelled_to(&self) -> Point {
self.screen_position.unwrap_or(self.global_position)
}
pub fn with_buttons(mut self, buttons: PointerButtons) -> Self {
self.buttons = buttons;
self
}
pub fn with_source(mut self, source: PointerSource) -> Self {
self.source = source;
self
}
pub fn with_modifiers(mut self, modifiers: Modifiers) -> Self {
self.modifiers = Some(modifiers);
self
}
pub fn rotary(kind: PointerEventKind, rotary: RotaryScrollEvent, position: Point) -> Self {
debug_assert!(
kind.is_rotary(),
"PointerEvent::rotary requires a rotary event kind"
);
Self::new(kind, position, position)
.with_scroll_delta(Point {
x: rotary.horizontal_scroll_pixels,
y: rotary.vertical_scroll_pixels,
})
.with_time_ms(Some(rotary.uptime_millis as i64))
}
pub fn rotary_scroll_event(&self) -> Option<RotaryScrollEvent> {
if !self.kind.is_rotary() {
return None;
}
Some(RotaryScrollEvent {
vertical_scroll_pixels: self.scroll_delta.y,
horizontal_scroll_pixels: self.scroll_delta.x,
uptime_millis: self.time_ms.unwrap_or(0).max(0) as u64,
})
}
pub fn consume(&self) {
self.consumed.set(true);
}
pub fn is_consumed(&self) -> bool {
self.consumed.get()
}
pub fn defer_post_dispatch_action<F>(&self, action: F)
where
F: FnOnce() -> bool + 'static,
{
*self.deferred_post_dispatch.action.borrow_mut() = Some(Box::new(action));
}
pub fn finish_post_dispatch(&self) {
if self.is_consumed() {
self.deferred_post_dispatch.action.borrow_mut().take();
return;
}
let Some(action) = self.deferred_post_dispatch.action.borrow_mut().take() else {
return;
};
if action() {
self.consume();
}
}
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,
screen_position: self.screen_position,
scroll_delta: self.scroll_delta,
buttons: self.buttons,
time_ms: self.time_ms,
animation_time_nanos: self.animation_time_nanos,
zoom_delta: self.zoom_delta,
source: self.source,
modifiers: self.modifiers,
consumed: self.consumed.clone(),
deferred_post_dispatch: self.deferred_post_dispatch.clone(),
}
}
}
#[cfg(test)]
#[path = "tests/types_tests.rs"]
mod tests;