1#[derive(Debug)]
6pub struct IdleCursor {
7 enabled: bool,
8 hidden: bool,
9 reason: Reason,
10}
11
12#[derive(Debug, PartialEq, Eq)]
13enum Reason {
14 Home,
15 Idle,
16 Driving,
17}
18
19impl IdleCursor {
20 #[must_use]
21 pub fn new(enabled: bool) -> Self {
22 Self {
23 enabled,
24 hidden: false,
25 reason: Reason::Home,
26 }
27 }
28
29 pub fn configure(&mut self, enabled: bool) -> Option<bool> {
30 self.enabled = enabled;
31 if !enabled && self.reason == Reason::Idle {
32 self.reason = Reason::Home;
33 }
34 self.update()
35 }
36
37 pub fn ended_driven(&mut self) -> Option<bool> {
38 if self.reason != Reason::Driving {
39 self.reason = if self.enabled {
40 Reason::Idle
41 } else {
42 Reason::Home
43 };
44 }
45 self.update()
46 }
47
48 pub fn driving(&mut self, driving: bool) -> Option<bool> {
50 self.reason = if driving {
51 Reason::Driving
52 } else {
53 Reason::Home
54 };
55 self.update()
56 }
57
58 pub fn show(&mut self) -> Option<bool> {
60 if self.reason == Reason::Idle {
61 self.reason = Reason::Home;
62 }
63 self.update()
64 }
65
66 fn update(&mut self) -> Option<bool> {
67 let hidden = self.enabled && self.reason != Reason::Home;
68 if self.hidden == hidden {
69 return None;
70 }
71 self.hidden = hidden;
72 Some(hidden)
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn ended_driven_enabled_hides_once() {
82 let mut cursor = IdleCursor::new(true);
84
85 let changes = [cursor.ended_driven(), cursor.ended_driven()];
87
88 assert_eq!(changes, [Some(true), None]);
90 }
91
92 #[test]
93 fn show_motion_or_new_driver_restores_cursor() {
94 let mut cursor = IdleCursor::new(true);
96 cursor.ended_driven();
97
98 let changes = [cursor.show(), cursor.show()];
100
101 assert_eq!(changes, [Some(false), None]);
103 }
104
105 #[test]
106 fn configure_disabled_restores_and_never_hides() {
107 let mut cursor = IdleCursor::new(true);
109 cursor.ended_driven();
110
111 let changes = [cursor.configure(false), cursor.ended_driven()];
113
114 assert_eq!(changes, [Some(false), None]);
116 }
117
118 #[test]
119 fn driving_motion_and_peer_handover_stays_hidden_until_home() {
120 let mut cursor = IdleCursor::new(true);
122
123 let changes = [
125 cursor.driving(true),
126 cursor.show(),
127 cursor.driving(true),
128 cursor.driving(false),
129 ];
130
131 assert_eq!(changes, [Some(true), None, None, Some(false)]);
133 }
134
135 #[test]
136 fn driving_becomes_driven_then_released_hides_only_without_input() {
137 let mut cursor = IdleCursor::new(true);
139 cursor.driving(true);
140
141 let changes = [cursor.driving(false), cursor.ended_driven(), cursor.show()];
143
144 assert_eq!(changes, [Some(false), Some(true), Some(false)]);
146 }
147
148 #[test]
149 fn driving_idle_reason_cleared_keeps_driving_hidden() {
150 let mut cursor = IdleCursor::new(true);
152 cursor.driving(true);
153
154 let changes = [cursor.ended_driven(), cursor.show(), cursor.driving(false)];
156
157 assert_eq!(changes, [None, None, Some(false)]);
159 }
160
161 #[test]
162 fn configure_while_driving_restores_then_hides_when_enabled() {
163 let mut cursor = IdleCursor::new(true);
165 cursor.driving(true);
166
167 let changes = [
169 cursor.configure(false),
170 cursor.show(),
171 cursor.configure(true),
172 ];
173
174 assert_eq!(changes, [Some(false), None, Some(true)]);
176 }
177}