azul_layout/managers/hover.rs
1//! Hover state management for tracking mouse and touch hover history
2//!
3//! The HoverManager records hit test results for multiple input points
4//! (mouse, touch, pen) over multiple frames to enable gesture detection
5//! (like DragStart) that requires analyzing hover patterns over time
6//! rather than just the current frame.
7
8use std::collections::{BTreeMap, VecDeque};
9
10use crate::hit_test::FullHitTest;
11
12/// Maximum number of frames to keep in hover history
13const MAX_HOVER_HISTORY: usize = 5;
14
15/// Identifier for an input point (mouse, touch, pen, etc.)
16#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub enum InputPointId {
18 /// Mouse cursor
19 Mouse,
20 /// Touch point with unique ID (from TouchEvent.id)
21 Touch(u64),
22}
23
24/// Manages hover state history for all input points
25///
26/// Records hit test results for mouse and touch inputs over multiple frames:
27/// - DragStart detection (requires movement threshold over multiple frames)
28/// - Hover-over event detection
29/// - Multi-touch gesture detection
30/// - Input path analysis
31///
32/// The manager maintains a separate history for each active input point.
33#[derive(Debug, Clone, PartialEq)]
34pub struct HoverManager {
35 /// Hit test history for each input point
36 /// Each point has its own ring buffer of the last N frames
37 hover_histories: BTreeMap<InputPointId, VecDeque<FullHitTest>>,
38}
39
40impl HoverManager {
41 /// Create a new empty HoverManager
42 pub fn new() -> Self {
43 Self {
44 hover_histories: BTreeMap::new(),
45 }
46 }
47
48 /// Push a new hit test result for a specific input point
49 ///
50 /// The most recent result is always at index 0 for that input point.
51 /// If the history is full, the oldest frame is dropped.
52 pub fn push_hit_test(&mut self, input_id: InputPointId, hit_test: FullHitTest) {
53 let history = self
54 .hover_histories
55 .entry(input_id)
56 .or_insert_with(|| VecDeque::with_capacity(MAX_HOVER_HISTORY));
57
58 // Add to front (most recent)
59 history.push_front(hit_test);
60
61 // Remove oldest if we exceed the limit
62 if history.len() > MAX_HOVER_HISTORY {
63 history.pop_back();
64 }
65 }
66
67 /// Remove an input point's history (e.g., when touch ends)
68 pub fn remove_input_point(&mut self, input_id: &InputPointId) {
69 self.hover_histories.remove(input_id);
70 }
71
72 /// Get the most recent hit test result for an input point
73 ///
74 /// Returns None if no hit tests have been recorded for this input point.
75 pub fn get_current(&self, input_id: &InputPointId) -> Option<&FullHitTest> {
76 self.hover_histories
77 .get(input_id)
78 .and_then(|history| history.front())
79 }
80
81 /// Get the most recent mouse cursor hit test (convenience method)
82 pub fn get_current_mouse(&self) -> Option<&FullHitTest> {
83 self.get_current(&InputPointId::Mouse)
84 }
85
86 /// Get the hit test result from N frames ago for an input point
87 // (0 = current frame)
88 ///
89 /// Returns None if the requested frame is not in history.
90 pub fn get_frame(&self, input_id: &InputPointId, frames_ago: usize) -> Option<&FullHitTest> {
91 self.hover_histories
92 .get(input_id)
93 .and_then(|history| history.get(frames_ago))
94 }
95
96 /// Get the entire hover history for an input point (most recent first)
97 pub fn get_history(&self, input_id: &InputPointId) -> Option<&VecDeque<FullHitTest>> {
98 self.hover_histories.get(input_id)
99 }
100
101 /// Get all currently tracked input points
102 pub fn get_active_input_points(&self) -> Vec<InputPointId> {
103 self.hover_histories.keys().copied().collect()
104 }
105
106 /// Get the number of frames in history for an input point
107 pub fn frame_count(&self, input_id: &InputPointId) -> usize {
108 self.hover_histories
109 .get(input_id)
110 .map(|h| h.len())
111 .unwrap_or(0)
112 }
113
114 /// Clear all hover history for all input points
115 pub fn clear(&mut self) {
116 self.hover_histories.clear();
117 }
118
119 /// Clear history for a specific input point
120 pub fn clear_input_point(&mut self, input_id: &InputPointId) {
121 if let Some(history) = self.hover_histories.get_mut(input_id) {
122 history.clear();
123 }
124 }
125
126 /// Check if we have enough frames for gesture detection on an input point
127 ///
128 /// DragStart detection requires analyzing movement over multiple frames.
129 /// This returns true if we have at least 2 frames of history.
130 pub fn has_sufficient_history_for_gestures(&self, input_id: &InputPointId) -> bool {
131 self.frame_count(input_id) >= 2
132 }
133
134 /// Check if any input point has enough history for gesture detection
135 pub fn any_has_sufficient_history_for_gestures(&self) -> bool {
136 self.hover_histories
137 .iter()
138 .any(|(_, history)| history.len() >= 2)
139 }
140}
141
142impl Default for HoverManager {
143 fn default() -> Self {
144 Self::new()
145 }
146}