umsh_ux_display_tracker/
gate.rs1use umsh_ux_tracker::button::ButtonEvent;
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum GateReason {
35 ScreenOff,
37 AlertActive,
39 Refreshing,
41}
42
43impl GateReason {
44 const fn bit(self) -> u8 {
45 1 << self as u8
46 }
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum Disposition {
52 Deliver,
54 ConsumedByWake,
57 CancelAlert,
59 Discard,
61}
62
63#[derive(Clone, Copy, Debug, Default)]
68pub struct Gate {
69 current: u8,
70 latched: Option<u8>,
71}
72
73impl Gate {
74 pub const fn new() -> Self {
75 Self {
76 current: 0,
77 latched: None,
78 }
79 }
80
81 pub fn set(&mut self, reason: GateReason, active: bool) {
83 if active {
84 self.current |= reason.bit();
85 } else {
86 self.current &= !reason.bit();
87 }
88 }
89
90 pub fn is_set(&self, reason: GateReason) -> bool {
91 self.current & reason.bit() != 0
92 }
93
94 pub fn is_gating(&self) -> bool {
96 self.current != 0
97 }
98
99 pub fn on_press(&mut self) {
104 if self.latched.is_none() {
105 self.latched = Some(self.current);
106 }
107 }
108
109 pub fn settle(&mut self, resting: bool) {
116 if resting {
117 self.latched = None;
118 }
119 }
120
121 pub fn disposition(&self, event: ButtonEvent) -> Disposition {
126 if matches!(event, ButtonEvent::VeryLong) {
127 return Disposition::Deliver;
128 }
129 let reasons = self.latched.unwrap_or(self.current);
130 if reasons & GateReason::AlertActive.bit() != 0 {
131 Disposition::CancelAlert
132 } else if reasons & GateReason::ScreenOff.bit() != 0 {
133 Disposition::ConsumedByWake
134 } else if reasons & GateReason::Refreshing.bit() != 0 {
135 Disposition::Discard
136 } else {
137 Disposition::Deliver
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn an_ungated_gesture_is_delivered() {
148 let mut g = Gate::new();
149 g.on_press();
150 assert_eq!(g.disposition(ButtonEvent::Single), Disposition::Deliver);
151 assert!(!g.is_gating());
152 }
153
154 #[test]
155 fn a_gesture_begun_against_a_dark_panel_only_wakes_it() {
156 let mut g = Gate::new();
157 g.set(GateReason::ScreenOff, true);
158 g.on_press();
159 assert_eq!(
160 g.disposition(ButtonEvent::Single),
161 Disposition::ConsumedByWake
162 );
163 }
164
165 #[test]
166 fn the_whole_chord_is_judged_by_its_first_press() {
167 let mut g = Gate::new();
168 g.set(GateReason::ScreenOff, true);
169 g.on_press();
170 g.set(GateReason::ScreenOff, false);
172 g.on_press(); assert_eq!(
174 g.disposition(ButtonEvent::Double),
175 Disposition::ConsumedByWake
176 );
177 }
178
179 #[test]
180 fn the_next_gesture_is_judged_afresh() {
181 let mut g = Gate::new();
182 g.set(GateReason::ScreenOff, true);
183 g.on_press();
184 assert_eq!(
185 g.disposition(ButtonEvent::Single),
186 Disposition::ConsumedByWake
187 );
188 g.set(GateReason::ScreenOff, false);
189 g.settle(true);
190
191 g.on_press();
192 assert_eq!(g.disposition(ButtonEvent::Single), Disposition::Deliver);
193 }
194
195 #[test]
196 fn a_gesture_that_resolves_to_nothing_does_not_strand_the_latch() {
197 let mut g = Gate::new();
198 g.set(GateReason::ScreenOff, true);
199 g.on_press();
200 g.set(GateReason::ScreenOff, false);
203 g.settle(true);
204
205 g.on_press();
206 assert_eq!(g.disposition(ButtonEvent::Single), Disposition::Deliver);
207 }
208
209 #[test]
210 fn settle_while_a_chord_is_pending_keeps_the_latch() {
211 let mut g = Gate::new();
212 g.set(GateReason::ScreenOff, true);
213 g.on_press();
214 g.set(GateReason::ScreenOff, false);
215 g.settle(false);
217 assert_eq!(
218 g.disposition(ButtonEvent::Double),
219 Disposition::ConsumedByWake
220 );
221 }
222
223 #[test]
224 fn an_alert_turns_every_gesture_into_a_cancel() {
225 let mut g = Gate::new();
226 g.set(GateReason::AlertActive, true);
227 g.on_press();
228 for event in [
229 ButtonEvent::Single,
230 ButtonEvent::Double,
231 ButtonEvent::Triple,
232 ButtonEvent::Quad,
233 ButtonEvent::Long,
234 ] {
235 assert_eq!(g.disposition(event), Disposition::CancelAlert);
236 }
237 }
238
239 #[test]
240 fn power_off_always_passes_through() {
241 let mut g = Gate::new();
242 for reason in [
243 GateReason::ScreenOff,
244 GateReason::AlertActive,
245 GateReason::Refreshing,
246 ] {
247 g.set(reason, true);
248 }
249 g.on_press();
250 assert_eq!(g.disposition(ButtonEvent::VeryLong), Disposition::Deliver);
251 }
252
253 #[test]
254 fn a_refreshing_panel_discards_input() {
255 let mut g = Gate::new();
256 g.set(GateReason::Refreshing, true);
257 g.on_press();
258 assert_eq!(g.disposition(ButtonEvent::Single), Disposition::Discard);
259 }
260
261 #[test]
262 fn cancelling_an_alert_outranks_a_dark_panel() {
263 let mut g = Gate::new();
264 g.set(GateReason::ScreenOff, true);
265 g.set(GateReason::AlertActive, true);
266 g.on_press();
267 assert_eq!(g.disposition(ButtonEvent::Single), Disposition::CancelAlert);
268 }
269
270 #[test]
271 fn an_unlatched_gesture_falls_back_to_live_conditions() {
272 let mut g = Gate::new();
273 g.set(GateReason::AlertActive, true);
274 assert_eq!(g.disposition(ButtonEvent::Single), Disposition::CancelAlert);
275 }
276}