Skip to main content

a7n_core/
cursor.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3//! Local cursor visibility while driving or after remote ownership ends.
4
5#[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    /// Focus returning home or becoming driven clears the previous hiding reasons.
49    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    /// Motion clears idle hiding, but must not reveal a cursor while driving.
59    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        // Arrange
83        let mut cursor = IdleCursor::new(true);
84
85        // Act
86        let changes = [cursor.ended_driven(), cursor.ended_driven()];
87
88        // Assert
89        assert_eq!(changes, [Some(true), None]);
90    }
91
92    #[test]
93    fn show_motion_or_new_driver_restores_cursor() {
94        // Arrange
95        let mut cursor = IdleCursor::new(true);
96        cursor.ended_driven();
97
98        // Act
99        let changes = [cursor.show(), cursor.show()];
100
101        // Assert
102        assert_eq!(changes, [Some(false), None]);
103    }
104
105    #[test]
106    fn configure_disabled_restores_and_never_hides() {
107        // Arrange
108        let mut cursor = IdleCursor::new(true);
109        cursor.ended_driven();
110
111        // Act
112        let changes = [cursor.configure(false), cursor.ended_driven()];
113
114        // Assert
115        assert_eq!(changes, [Some(false), None]);
116    }
117
118    #[test]
119    fn driving_motion_and_peer_handover_stays_hidden_until_home() {
120        // Arrange
121        let mut cursor = IdleCursor::new(true);
122
123        // Act
124        let changes = [
125            cursor.driving(true),
126            cursor.show(),
127            cursor.driving(true),
128            cursor.driving(false),
129        ];
130
131        // Assert
132        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        // Arrange
138        let mut cursor = IdleCursor::new(true);
139        cursor.driving(true);
140
141        // Act
142        let changes = [cursor.driving(false), cursor.ended_driven(), cursor.show()];
143
144        // Assert
145        assert_eq!(changes, [Some(false), Some(true), Some(false)]);
146    }
147
148    #[test]
149    fn driving_idle_reason_cleared_keeps_driving_hidden() {
150        // Arrange
151        let mut cursor = IdleCursor::new(true);
152        cursor.driving(true);
153
154        // Act
155        let changes = [cursor.ended_driven(), cursor.show(), cursor.driving(false)];
156
157        // Assert
158        assert_eq!(changes, [None, None, Some(false)]);
159    }
160
161    #[test]
162    fn configure_while_driving_restores_then_hides_when_enabled() {
163        // Arrange
164        let mut cursor = IdleCursor::new(true);
165        cursor.driving(true);
166
167        // Act
168        let changes = [
169            cursor.configure(false),
170            cursor.show(),
171            cursor.configure(true),
172        ];
173
174        // Assert
175        assert_eq!(changes, [Some(false), None, Some(true)]);
176    }
177}