loam_app/session/
cursor.rs1#[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}