Another project
0

Configure Feed

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

at main 5.9 kB View raw
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 #[must_use] 57 pub const fn without(self, button: PointerButton) -> Self { 58 Self(self.0 & !button.bit()) 59 } 60} 61 62#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 63#[serde(transparent)] 64pub struct FrameInstant(Duration); 65 66impl FrameInstant { 67 pub const ZERO: Self = Self(Duration::ZERO); 68 69 #[must_use] 70 pub const fn from_duration(d: Duration) -> Self { 71 Self(d) 72 } 73 74 #[must_use] 75 pub const fn duration(self) -> Duration { 76 self.0 77 } 78 79 #[must_use] 80 pub fn since(self, earlier: Self) -> Duration { 81 self.0.saturating_sub(earlier.0) 82 } 83 84 #[must_use] 85 pub fn after(self, delay: Duration) -> Self { 86 Self(self.0.saturating_add(delay)) 87 } 88} 89 90#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)] 91#[serde(transparent)] 92pub struct DragThreshold(LayoutPx); 93 94impl DragThreshold { 95 pub const DEFAULT: Self = Self(LayoutPx::new(4.0)); 96 97 #[must_use] 98 pub const fn new(px: LayoutPx) -> Self { 99 assert!(px.value() >= 0.0, "DragThreshold must be non-negative"); 100 Self(px) 101 } 102 103 #[must_use] 104 pub const fn px(self) -> LayoutPx { 105 self.0 106 } 107 108 #[must_use] 109 pub fn exceeded_by(self, offset: LayoutOffset) -> bool { 110 let dx = offset.dx.value(); 111 let dy = offset.dy.value(); 112 let t = self.0.value(); 113 dx * dx + dy * dy > t * t 114 } 115} 116 117#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 118#[serde(transparent)] 119pub struct DoubleClickWindow(Duration); 120 121impl DoubleClickWindow { 122 pub const DEFAULT: Self = Self(Duration::from_millis(400)); 123 124 #[must_use] 125 pub const fn new(window: Duration) -> Self { 126 Self(window) 127 } 128 129 #[must_use] 130 pub const fn duration(self) -> Duration { 131 self.0 132 } 133 134 #[must_use] 135 pub fn contains(self, gap: Duration) -> bool { 136 gap <= self.0 137 } 138} 139 140#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] 141#[serde(transparent)] 142pub struct ClickCount(u8); 143 144impl ClickCount { 145 pub const SINGLE: Self = Self(1); 146 pub const DOUBLE: Self = Self(2); 147 148 #[must_use] 149 pub const fn new(count: u8) -> Self { 150 Self(count) 151 } 152 153 #[must_use] 154 pub const fn get(self) -> u8 { 155 self.0 156 } 157 158 #[must_use] 159 pub const fn next(self) -> Self { 160 Self(self.0.saturating_add(1)) 161 } 162 163 #[must_use] 164 pub const fn is_double(self) -> bool { 165 self.0 >= 2 166 } 167} 168 169#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 170pub struct PointerSample { 171 pub position: LayoutPos, 172} 173 174impl PointerSample { 175 #[must_use] 176 pub const fn new(position: LayoutPos) -> Self { 177 Self { position } 178 } 179} 180 181#[cfg(test)] 182mod tests { 183 use core::time::Duration; 184 185 use super::{ 186 ClickCount, DoubleClickWindow, DragThreshold, FrameInstant, PointerButton, 187 PointerButtonMask, 188 }; 189 use crate::layout::{LayoutOffset, LayoutPx}; 190 191 #[test] 192 fn button_mask_with_just() { 193 let mask = PointerButtonMask::just(PointerButton::Primary).with(PointerButton::Middle); 194 assert!(mask.contains(PointerButton::Primary)); 195 assert!(mask.contains(PointerButton::Middle)); 196 assert!(!mask.contains(PointerButton::Secondary)); 197 } 198 199 #[test] 200 fn drag_threshold_uses_squared_distance() { 201 let t = DragThreshold::new(LayoutPx::new(5.0)); 202 let inside = LayoutOffset::new(LayoutPx::new(3.0), LayoutPx::new(3.0)); 203 let outside = LayoutOffset::new(LayoutPx::new(4.0), LayoutPx::new(4.0)); 204 assert!(!t.exceeded_by(inside)); 205 assert!(t.exceeded_by(outside)); 206 } 207 208 #[test] 209 fn double_click_window_contains_short_gap() { 210 let w = DoubleClickWindow::DEFAULT; 211 assert!(w.contains(Duration::from_millis(200))); 212 assert!(!w.contains(Duration::from_millis(800))); 213 } 214 215 #[test] 216 fn click_count_progresses() { 217 let one = ClickCount::SINGLE; 218 let two = one.next(); 219 assert!(!one.is_double()); 220 assert!(two.is_double()); 221 } 222 223 #[test] 224 fn click_count_triple_remains_double_flag() { 225 let three = ClickCount::SINGLE.next().next(); 226 assert_eq!(three.get(), 3); 227 assert!(three.is_double()); 228 } 229 230 #[test] 231 fn frame_instant_since_clamps() { 232 let later = FrameInstant::from_duration(Duration::from_millis(100)); 233 let earlier = FrameInstant::from_duration(Duration::from_millis(40)); 234 assert_eq!(later.since(earlier), Duration::from_millis(60)); 235 assert_eq!(earlier.since(later), Duration::ZERO); 236 } 237}