Skip to main content

loam_app/session/
cursor.rs

1#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2pub enum CursorPolicy {
3    Hold,
4    #[default]
5    Toggle,
6}
7
8#[derive(Default)]
9pub(crate) struct CursorCapture {
10    enabled: bool,
11    policy: CursorPolicy,
12    wanted: bool,
13    locked: bool,
14    alt: [bool; 2],
15    focused: bool,
16    request: Option<bool>,
17}
18
19impl CursorCapture {
20    pub(crate) fn new() -> Self {
21        Self {
22            focused: true,
23            ..Self::default()
24        }
25    }
26
27    pub(crate) fn capture(&mut self, enabled: bool, policy: CursorPolicy) {
28        let policy_changed = self.policy != policy;
29        self.policy = policy;
30        if self.enabled != enabled {
31            self.enabled = enabled;
32            self.set_wanted(enabled && (policy != CursorPolicy::Hold || !self.alt_held()));
33        } else if enabled && policy_changed && policy == CursorPolicy::Hold {
34            self.set_wanted(!self.alt_held());
35        }
36    }
37
38    pub(crate) fn alt(&mut self, index: usize, pressed: bool) {
39        let was_held = self.alt_held();
40        self.alt[index] = pressed;
41        let held = self.alt_held();
42        if !self.enabled || was_held == held {
43            return;
44        }
45        match self.policy {
46            CursorPolicy::Hold => self.set_wanted(!held),
47            CursorPolicy::Toggle if held => self.set_wanted(!self.wanted),
48            CursorPolicy::Toggle => {}
49        }
50    }
51
52    pub(crate) fn focus(&mut self, focused: bool) {
53        if self.focused == focused {
54            return;
55        }
56        self.focused = focused;
57        if focused {
58            if self.enabled && self.policy == CursorPolicy::Hold {
59                self.wanted = true;
60            }
61            if self.wanted {
62                self.request = Some(true);
63            }
64        } else {
65            self.alt = [false; 2];
66            self.request = Some(false);
67        }
68    }
69
70    pub(crate) fn applied(&mut self, locked: bool) {
71        self.locked = locked;
72    }
73
74    pub(crate) fn suspend(&mut self) {
75        self.enabled = false;
76        self.wanted = false;
77        self.request = Some(false);
78    }
79
80    #[cfg(any(target_arch = "wasm32", test))]
81    pub(crate) fn released(&mut self) {
82        self.locked = false;
83        self.wanted = false;
84        self.request = None;
85    }
86
87    pub(crate) fn locked(&self) -> bool {
88        self.locked
89    }
90
91    pub(crate) fn take_request(&mut self) -> Option<bool> {
92        self.request.take()
93    }
94
95    fn alt_held(&self) -> bool {
96        self.alt[0] || self.alt[1]
97    }
98
99    fn set_wanted(&mut self, wanted: bool) {
100        if self.wanted == wanted {
101            return;
102        }
103        self.wanted = wanted;
104        if self.focused || !wanted {
105            self.request = Some(wanted);
106        }
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn alt_edges_and_platform_release_preserve_explicit_cursor_control() {
116        let mut cursor = CursorCapture::new();
117        cursor.capture(true, CursorPolicy::Toggle);
118        assert_eq!(cursor.take_request(), Some(true));
119        assert!(!cursor.locked());
120
121        cursor.applied(true);
122        cursor.alt(0, true);
123        assert_eq!(cursor.take_request(), Some(false));
124        cursor.alt(1, true);
125        cursor.alt(0, false);
126        cursor.alt(1, false);
127        assert_eq!(cursor.take_request(), None);
128
129        cursor.capture(true, CursorPolicy::Hold);
130        assert_eq!(cursor.take_request(), Some(true));
131        cursor.alt(1, true);
132        assert_eq!(cursor.take_request(), Some(false));
133        cursor.alt(1, false);
134        assert_eq!(cursor.take_request(), Some(true));
135
136        cursor.capture(false, CursorPolicy::Toggle);
137        cursor.capture(true, CursorPolicy::Toggle);
138        assert_eq!(cursor.take_request(), Some(true));
139        cursor.applied(true);
140        cursor.released();
141        cursor.capture(true, CursorPolicy::Toggle);
142        assert_eq!(cursor.take_request(), None);
143        cursor.alt(0, true);
144        assert_eq!(cursor.take_request(), Some(true));
145    }
146}