Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

1use core::time::Duration; 2 3use serde::{Deserialize, Serialize}; 4 5use crate::layout::{LayoutOffset, LayoutPos, LayoutPx}; 6 7#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] 8pub enum PointerButton { 9 Primary, 10 Secondary, 11 Middle, 12} 13 14impl PointerButton { 15 pub const ALL: [Self; 3] = [Self::Primary, Self::Secondary, Self::Middle]; 16 17 #[must_use] 18 pub const fn bit(self) -> u8 { 19 match self { 20 Self::Primary => 1 << 0, 21 Self::Secondary => 1 << 1, 22 Self::Middle => 1 << 2, 23 } 24 } 25} 26 27#[derive( 28 Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, 29)] 30#[serde(transparent)] 31pub struct PointerButtonMask(u8); 32 33impl PointerButtonMask { 34 pub const EMPTY: Self = Self(0); 35 36 #[must_use] 37 pub const fn just(button: PointerButton) -> Self { 38 Self(button.bit()) 39 } 40 41 #[must_use] 42 pub const fn contains(self, button: PointerButton) -> bool { 43 (self.0 & button.bit()) != 0 44 } 45 46 #[must_use] 47 pub const fn is_empty(self) -> bool { 48 self.0 == 0 49 } 50 51 #[must_use] 52 pub const fn with(self, button: PointerButton) -> Self { 53 Self(self.0 | button.bit()) 54 } 55} 56 57#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 58#[serde(transparent)] 59pub struct FrameInstant(Duration); 60 61impl FrameInstant { 62 pub const ZERO: Self = Self(Duration::ZERO); 63 64 #[must_use] 65 pub const fn from_duration(d: Duration) -> Self { 66 Self(d) 67 } 68 69 #[must_use] 70 pub const fn duration(self) -> Duration { 71 self.0 72 } 73 74 #[must_use] 75 pub fn since(self, earlier: Self) -> Duration { 76 self.0.saturating_sub(earlier.0) 77 } 78} 79 80#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)] 81#[serde(transparent)] 82pub struct DragThreshold(LayoutPx); 83 84impl DragThreshold { 85 pub const DEFAULT: Self = Self(LayoutPx::new(4.0)); 86 87 #[must_use] 88 pub const fn new(px: LayoutPx) -> Self { 89 assert!(px.value() >= 0.0, "DragThreshold must be non-negative"); 90 Self(px) 91 } 92 93 #[must_use] 94 pub const fn px(self) -> LayoutPx { 95 self.0 96 } 97 98 #[must_use] 99 pub fn exceeded_by(self, offset: LayoutOffset) -> bool { 100 let dx = offset.dx.value(); 101 let dy = offset.dy.value(); 102 let t = self.0.value(); 103 dx * dx + dy * dy > t * t 104 } 105} 106 107#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 108#[serde(transparent)] 109pub struct DoubleClickWindow(Duration); 110 111impl DoubleClickWindow { 112 pub const DEFAULT: Self = Self(Duration::from_millis(400)); 113 114 #[must_use] 115 pub const fn new(window: Duration) -> Self { 116 Self(window) 117 } 118 119 #[must_use] 120 pub const fn duration(self) -> Duration { 121 self.0 122 } 123 124 #[must_use] 125 pub fn contains(self, gap: Duration) -> bool { 126 gap <= self.0 127 } 128} 129 130#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] 131#[serde(transparent)] 132pub struct ClickCount(u8); 133 134impl ClickCount { 135 pub const SINGLE: Self = Self(1); 136 pub const DOUBLE: Self = Self(2); 137 138 #[must_use] 139 pub const fn new(count: u8) -> Self { 140 Self(count) 141 } 142 143 #[must_use] 144 pub const fn get(self) -> u8 { 145 self.0 146 } 147 148 #[must_use] 149 pub const fn next(self) -> Self { 150 Self(self.0.saturating_add(1)) 151 } 152 153 #[must_use] 154 pub const fn is_double(self) -> bool { 155 self.0 >= 2 156 } 157} 158 159#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 160pub struct PointerSample { 161 pub position: LayoutPos, 162} 163 164impl PointerSample { 165 #[must_use] 166 pub const fn new(position: LayoutPos) -> Self { 167 Self { position } 168 } 169} 170 171#[cfg(test)] 172mod tests { 173 use core::time::Duration; 174 175 use super::{ 176 ClickCount, DoubleClickWindow, DragThreshold, FrameInstant, PointerButton, 177 PointerButtonMask, 178 }; 179 use crate::layout::{LayoutOffset, LayoutPx}; 180 181 #[test] 182 fn button_mask_with_just() { 183 let mask = PointerButtonMask::just(PointerButton::Primary).with(PointerButton::Middle); 184 assert!(mask.contains(PointerButton::Primary)); 185 assert!(mask.contains(PointerButton::Middle)); 186 assert!(!mask.contains(PointerButton::Secondary)); 187 } 188 189 #[test] 190 fn drag_threshold_uses_squared_distance() { 191 let t = DragThreshold::new(LayoutPx::new(5.0)); 192 let inside = LayoutOffset::new(LayoutPx::new(3.0), LayoutPx::new(3.0)); 193 let outside = LayoutOffset::new(LayoutPx::new(4.0), LayoutPx::new(4.0)); 194 assert!(!t.exceeded_by(inside)); 195 assert!(t.exceeded_by(outside)); 196 } 197 198 #[test] 199 fn double_click_window_contains_short_gap() { 200 let w = DoubleClickWindow::DEFAULT; 201 assert!(w.contains(Duration::from_millis(200))); 202 assert!(!w.contains(Duration::from_millis(800))); 203 } 204 205 #[test] 206 fn click_count_progresses() { 207 let one = ClickCount::SINGLE; 208 let two = one.next(); 209 assert!(!one.is_double()); 210 assert!(two.is_double()); 211 } 212 213 #[test] 214 fn click_count_triple_remains_double_flag() { 215 let three = ClickCount::SINGLE.next().next(); 216 assert_eq!(three.get(), 3); 217 assert!(three.is_double()); 218 } 219 220 #[test] 221 fn frame_instant_since_clamps() { 222 let later = FrameInstant::from_duration(Duration::from_millis(100)); 223 let earlier = FrameInstant::from_duration(Duration::from_millis(40)); 224 assert_eq!(later.since(earlier), Duration::from_millis(60)); 225 assert_eq!(earlier.since(later), Duration::ZERO); 226 } 227}