Skip to main content

uzor_core/input/
sense.rs

1//! Sense flags for widget interaction detection
2//!
3//! The [`Sense`] type determines what kinds of user interactions a widget
4//! will detect and respond to.
5
6/// What interactions a widget is sensitive to
7///
8/// Using `CLICK_AND_DRAG` introduces latency to distinguish click vs drag intent.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
10pub struct Sense {
11    /// Widget responds to clicks
12    pub click: bool,
13    /// Widget responds to drags
14    pub drag: bool,
15    /// Widget tracks hover state
16    pub hover: bool,
17    /// Widget can receive keyboard focus
18    pub focus: bool,
19}
20
21// Predefined constants
22impl Sense {
23    /// No interactions at all
24    pub const NONE: Sense = Sense {
25        click: false,
26        drag: false,
27        hover: false,
28        focus: false,
29    };
30
31    /// Only hover detection
32    pub const HOVER: Sense = Sense {
33        click: false,
34        drag: false,
35        hover: true,
36        focus: false,
37    };
38
39    /// Click and hover (for buttons, checkboxes)
40    pub const CLICK: Sense = Sense {
41        click: true,
42        drag: false,
43        hover: true,
44        focus: false,
45    };
46
47    /// Drag and hover (for sliders, scrollbars)
48    pub const DRAG: Sense = Sense {
49        click: false,
50        drag: true,
51        hover: true,
52        focus: false,
53    };
54
55    /// Both click and drag (introduces latency)
56    pub const CLICK_AND_DRAG: Sense = Sense {
57        click: true,
58        drag: true,
59        hover: true,
60        focus: false,
61    };
62
63    /// Can receive keyboard focus but no mouse interaction
64    pub const FOCUSABLE: Sense = Sense {
65        click: false,
66        drag: false,
67        hover: true,
68        focus: true,
69    };
70
71    /// Full interaction - click, drag, hover, focus
72    pub const ALL: Sense = Sense {
73        click: true,
74        drag: true,
75        hover: true,
76        focus: true,
77    };
78}
79
80// Constructor methods
81impl Sense {
82    /// Create empty sense (no interactions)
83    #[inline]
84    pub fn none() -> Self {
85        Self::NONE
86    }
87
88    /// Create hover-only sense
89    #[inline]
90    pub fn hover() -> Self {
91        Self::HOVER
92    }
93
94    /// Create click sense (includes hover)
95    #[inline]
96    pub fn click() -> Self {
97        Self::CLICK
98    }
99
100    /// Create drag sense (includes hover)
101    #[inline]
102    pub fn drag() -> Self {
103        Self::DRAG
104    }
105
106    /// Create click and drag sense (includes hover, introduces latency)
107    #[inline]
108    pub fn click_and_drag() -> Self {
109        Self::CLICK_AND_DRAG
110    }
111
112    /// Create focusable sense (for keyboard navigation)
113    #[inline]
114    pub fn focusable() -> Self {
115        Self::FOCUSABLE
116    }
117
118    /// Create full interaction sense
119    #[inline]
120    pub fn all() -> Self {
121        Self::ALL
122    }
123}
124
125// Combination methods
126impl Sense {
127    /// Union of two senses (OR)
128    #[inline]
129    pub fn union(self, other: Sense) -> Sense {
130        Sense {
131            click: self.click || other.click,
132            drag: self.drag || other.drag,
133            hover: self.hover || other.hover,
134            focus: self.focus || other.focus,
135        }
136    }
137
138    /// Intersection of two senses (AND)
139    #[inline]
140    pub fn intersection(self, other: Sense) -> Sense {
141        Sense {
142            click: self.click && other.click,
143            drag: self.drag && other.drag,
144            hover: self.hover && other.hover,
145            focus: self.focus && other.focus,
146        }
147    }
148
149    /// Add click sensing (also adds hover)
150    #[inline]
151    pub fn with_click(mut self) -> Self {
152        self.click = true;
153        self.hover = true;
154        self
155    }
156
157    /// Add drag sensing (also adds hover)
158    #[inline]
159    pub fn with_drag(mut self) -> Self {
160        self.drag = true;
161        self.hover = true;
162        self
163    }
164
165    /// Add focus capability
166    #[inline]
167    pub fn with_focus(mut self) -> Self {
168        self.focus = true;
169        self
170    }
171}
172
173// Query methods
174impl Sense {
175    /// Check if any interaction is sensed (click, drag, or focus)
176    #[inline]
177    pub fn interactive(&self) -> bool {
178        self.click || self.drag || self.focus
179    }
180
181    /// Check if both click and drag are sensed (has latency)
182    #[inline]
183    pub fn has_click_and_drag(&self) -> bool {
184        self.click && self.drag
185    }
186
187    /// Check if widget is purely visual (no interactions)
188    #[inline]
189    pub fn is_passive(&self) -> bool {
190        !self.click && !self.drag && !self.focus
191    }
192}
193
194impl std::ops::BitOr for Sense {
195    type Output = Sense;
196
197    /// Combine two senses using the `|` operator (equivalent to union)
198    #[inline]
199    fn bitor(self, rhs: Self) -> Self::Output {
200        self.union(rhs)
201    }
202}
203
204impl std::ops::BitOrAssign for Sense {
205    /// Combine this sense with another using `|=`
206    #[inline]
207    fn bitor_assign(&mut self, rhs: Self) {
208        *self = self.union(rhs);
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_predefined_constants() {
218        assert!(!Sense::NONE.click);
219        assert!(!Sense::NONE.drag);
220        assert!(!Sense::NONE.hover);
221        assert!(!Sense::NONE.focus);
222
223        assert!(!Sense::HOVER.click);
224        assert!(Sense::HOVER.hover);
225
226        assert!(Sense::CLICK.click);
227        assert!(!Sense::CLICK.drag);
228        assert!(Sense::CLICK.hover);
229
230        assert!(!Sense::DRAG.click);
231        assert!(Sense::DRAG.drag);
232        assert!(Sense::DRAG.hover);
233
234        assert!(Sense::CLICK_AND_DRAG.click);
235        assert!(Sense::CLICK_AND_DRAG.drag);
236        assert!(Sense::CLICK_AND_DRAG.hover);
237
238        assert!(!Sense::FOCUSABLE.click);
239        assert!(Sense::FOCUSABLE.hover);
240        assert!(Sense::FOCUSABLE.focus);
241
242        assert!(Sense::ALL.click);
243        assert!(Sense::ALL.drag);
244        assert!(Sense::ALL.hover);
245        assert!(Sense::ALL.focus);
246    }
247
248    #[test]
249    fn test_constructor_methods() {
250        assert_eq!(Sense::none(), Sense::NONE);
251        assert_eq!(Sense::hover(), Sense::HOVER);
252        assert_eq!(Sense::click(), Sense::CLICK);
253        assert_eq!(Sense::drag(), Sense::DRAG);
254        assert_eq!(Sense::click_and_drag(), Sense::CLICK_AND_DRAG);
255        assert_eq!(Sense::focusable(), Sense::FOCUSABLE);
256        assert_eq!(Sense::all(), Sense::ALL);
257    }
258
259    #[test]
260    fn test_union() {
261        let click = Sense::click();
262        let drag = Sense::drag();
263        let combined = click.union(drag);
264
265        assert!(combined.click);
266        assert!(combined.drag);
267        assert!(combined.hover);
268        assert!(!combined.focus);
269        assert_eq!(combined, Sense::CLICK_AND_DRAG);
270    }
271
272    #[test]
273    fn test_intersection() {
274        let click_and_drag = Sense::CLICK_AND_DRAG;
275        let click = Sense::click();
276        let common = click_and_drag.intersection(click);
277
278        assert!(common.click);
279        assert!(!common.drag);
280        assert!(common.hover);
281        assert!(!common.focus);
282    }
283
284    #[test]
285    fn test_with_methods() {
286        let sense = Sense::none().with_click();
287        assert!(sense.click);
288        assert!(sense.hover);
289        assert!(!sense.drag);
290
291        let sense = Sense::none().with_drag();
292        assert!(sense.drag);
293        assert!(sense.hover);
294        assert!(!sense.click);
295
296        let sense = Sense::click().with_focus();
297        assert!(sense.click);
298        assert!(sense.focus);
299        assert!(sense.hover);
300
301        let sense = Sense::none().with_click().with_drag().with_focus();
302        assert_eq!(sense, Sense::ALL);
303    }
304
305    #[test]
306    fn test_query_methods() {
307        assert!(Sense::click().interactive());
308        assert!(Sense::drag().interactive());
309        assert!(Sense::focusable().interactive());
310        assert!(!Sense::hover().interactive());
311        assert!(!Sense::none().interactive());
312
313        assert!(Sense::CLICK_AND_DRAG.has_click_and_drag());
314        assert!(Sense::ALL.has_click_and_drag());
315        assert!(!Sense::click().has_click_and_drag());
316        assert!(!Sense::drag().has_click_and_drag());
317
318        assert!(Sense::none().is_passive());
319        assert!(Sense::hover().is_passive());
320        assert!(!Sense::click().is_passive());
321        assert!(!Sense::drag().is_passive());
322        assert!(!Sense::focusable().is_passive());
323    }
324
325    #[test]
326    fn test_bitor_operator() {
327        let combined = Sense::click() | Sense::drag();
328        assert_eq!(combined, Sense::CLICK_AND_DRAG);
329
330        let mut sense = Sense::click();
331        sense |= Sense::drag();
332        assert_eq!(sense, Sense::CLICK_AND_DRAG);
333
334        let complex = Sense::click() | Sense::drag() | Sense::focusable();
335        assert!(complex.click);
336        assert!(complex.drag);
337        assert!(complex.focus);
338        assert!(complex.hover);
339    }
340
341    #[test]
342    fn test_default() {
343        let default_sense = Sense::default();
344        assert_eq!(default_sense, Sense::NONE);
345    }
346
347    #[test]
348    fn test_eq_and_hash() {
349        use std::collections::HashSet;
350
351        let mut set = HashSet::new();
352        set.insert(Sense::click());
353        set.insert(Sense::click());
354        set.insert(Sense::drag());
355
356        assert_eq!(set.len(), 2);
357        assert!(set.contains(&Sense::click()));
358        assert!(set.contains(&Sense::drag()));
359    }
360
361    #[test]
362    fn test_clone_and_copy() {
363        let original = Sense::click();
364        let cloned = original.clone();
365        let copied = original;
366
367        assert_eq!(original, cloned);
368        assert_eq!(original, copied);
369    }
370}