midi_controller/
long_press.rs1pub const LONG_PRESS_MS: u32 = 500;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum Edge {
15 Activate,
16 Deactivate,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Gesture {
22 ShortPress,
23 LongPress,
24}
25
26#[derive(Debug, Clone)]
31pub struct LongPressDetector {
32 press_time: u32,
33 active: bool,
34 fired: bool,
35 threshold_ms: u32,
36}
37
38impl Default for LongPressDetector {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl LongPressDetector {
45 pub fn new() -> Self {
46 Self {
47 press_time: 0,
48 active: false,
49 fired: false,
50 threshold_ms: LONG_PRESS_MS,
51 }
52 }
53
54 pub fn with_threshold(threshold_ms: u32) -> Self {
56 Self {
57 press_time: 0,
58 active: false,
59 fired: false,
60 threshold_ms,
61 }
62 }
63
64 pub fn new_fired() -> Self {
67 Self {
68 press_time: 0,
69 active: false,
70 fired: true,
71 threshold_ms: LONG_PRESS_MS,
72 }
73 }
74
75 pub fn update(&mut self, edge: Option<Edge>, now_ms: u32) -> Option<Gesture> {
78 match edge {
79 Some(Edge::Activate) => {
80 self.active = true;
81 self.press_time = now_ms;
82 self.fired = false;
83 None
84 }
85 Some(Edge::Deactivate) => {
86 self.active = false;
87 if self.fired {
88 None
90 } else {
91 Some(Gesture::ShortPress)
92 }
93 }
94 None if self.active && !self.fired => {
95 let held_ms = now_ms.wrapping_sub(self.press_time);
96 if held_ms >= self.threshold_ms {
97 self.fired = true;
98 Some(Gesture::LongPress)
99 } else {
100 None
101 }
102 }
103 _ => None,
104 }
105 }
106
107 pub fn is_active(&self) -> bool {
109 self.active
110 }
111
112 pub fn has_fired(&self) -> bool {
114 self.fired
115 }
116
117 pub fn held_ms(&self, now_ms: u32) -> u32 {
119 if self.active {
120 now_ms.wrapping_sub(self.press_time)
121 } else {
122 0
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn short_press_on_release_before_threshold() {
133 let mut det = LongPressDetector::new();
134 assert_eq!(det.update(Some(Edge::Activate), 0), None);
135 assert_eq!(det.update(None, 100), None);
137 assert_eq!(
139 det.update(Some(Edge::Deactivate), 200),
140 Some(Gesture::ShortPress)
141 );
142 }
143
144 #[test]
145 fn long_press_fires_at_threshold() {
146 let mut det = LongPressDetector::new();
147 det.update(Some(Edge::Activate), 1000);
148 assert_eq!(det.update(None, 1400), None);
150 assert_eq!(det.update(None, 1499), None);
151 assert_eq!(det.update(None, 1500), Some(Gesture::LongPress));
153 }
154
155 #[test]
156 fn release_after_long_press_suppressed() {
157 let mut det = LongPressDetector::new();
158 det.update(Some(Edge::Activate), 0);
159 assert_eq!(det.update(None, 500), Some(Gesture::LongPress));
160 assert_eq!(det.update(Some(Edge::Deactivate), 600), None);
162 }
163
164 #[test]
165 fn no_double_fire_on_continued_hold() {
166 let mut det = LongPressDetector::new();
167 det.update(Some(Edge::Activate), 0);
168 assert_eq!(det.update(None, 500), Some(Gesture::LongPress));
169 assert_eq!(det.update(None, 600), None);
171 assert_eq!(det.update(None, 1000), None);
172 assert_eq!(det.update(None, 5000), None);
173 }
174
175 #[test]
176 fn new_fired_suppresses_stale_release() {
177 let mut det = LongPressDetector::new_fired();
178 assert_eq!(det.update(Some(Edge::Deactivate), 100), None);
180 assert_eq!(det.update(Some(Edge::Activate), 200), None);
182 assert_eq!(
183 det.update(Some(Edge::Deactivate), 300),
184 Some(Gesture::ShortPress)
185 );
186 }
187
188 #[test]
189 fn custom_threshold() {
190 let mut det = LongPressDetector::with_threshold(1000);
191 det.update(Some(Edge::Activate), 0);
192 assert_eq!(det.update(None, 500), None); assert_eq!(det.update(None, 999), None);
194 assert_eq!(det.update(None, 1000), Some(Gesture::LongPress));
195 }
196
197 #[test]
198 fn held_ms_reports_duration() {
199 let mut det = LongPressDetector::new();
200 assert_eq!(det.held_ms(0), 0); det.update(Some(Edge::Activate), 100);
202 assert_eq!(det.held_ms(250), 150);
203 assert_eq!(det.held_ms(600), 500);
204 }
205
206 #[test]
207 fn wrapping_timestamp() {
208 let mut det = LongPressDetector::new();
209 det.update(Some(Edge::Activate), u32::MAX - 100);
211 assert_eq!(det.update(None, u32::MAX), None); assert_eq!(det.update(None, 299), None); assert_eq!(det.update(None, 399), Some(Gesture::LongPress));
217 }
218}