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