cranpose_ui/
focus_dispatch.rs1use std::{cell::RefCell, collections::VecDeque, rc::Rc};
2
3use cranpose_core::{
4 NodeId,
5 collections::map::{HashMap, HashSet},
6};
7use cranpose_foundation::FocusState;
8
9pub(crate) trait FocusTargetHandle {
10 fn set_focus_state(&self, state: FocusState);
11}
12
13struct FocusInvalidationManager {
14 dirty_nodes: HashSet<NodeId>,
15 is_processing: bool,
16 active_focus_target: Option<NodeId>,
17 focus_targets: HashMap<NodeId, Vec<Rc<dyn FocusTargetHandle>>>,
18 pending_focus_requests: VecDeque<NodeId>,
19 dispatching_focus: bool,
20}
21
22impl FocusInvalidationManager {
23 fn new() -> Self {
24 Self {
25 dirty_nodes: HashSet::default(),
26 is_processing: false,
27 active_focus_target: None,
28 focus_targets: HashMap::default(),
29 pending_focus_requests: VecDeque::new(),
30 dispatching_focus: false,
31 }
32 }
33
34 fn schedule_invalidation(&mut self, node_id: NodeId) {
35 self.dirty_nodes.insert(node_id);
36 }
37
38 fn has_pending_invalidation(&self) -> bool {
39 !self.dirty_nodes.is_empty()
40 }
41
42 fn set_active_focus_target(&mut self, node_id: Option<NodeId>) {
43 if self.active_focus_target == node_id {
44 return;
45 }
46 for changed in self.active_focus_target.into_iter().chain(node_id) {
47 crate::semantics_dispatch::schedule_semantics_invalidation(changed);
48 }
49 self.active_focus_target = node_id;
50 crate::request_render_invalidation();
51 }
52
53 fn active_focus_target(&self) -> Option<NodeId> {
54 self.active_focus_target
55 }
56
57 fn register_focus_target(&mut self, node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
60 let handles = self.focus_targets.entry(node_id).or_default();
61 if handles.is_empty() {
62 crate::semantics_dispatch::schedule_semantics_invalidation(node_id);
63 }
64 handles.push(handle);
65 }
66
67 fn unregister_focus_target(&mut self, node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
68 let Some(handles) = self.focus_targets.get_mut(&node_id) else {
69 return;
70 };
71 handles.retain(|existing| !Rc::ptr_eq(existing, handle));
72 if handles.is_empty() {
73 self.focus_targets.remove(&node_id);
74 crate::semantics_dispatch::schedule_semantics_invalidation(node_id);
75 if self.active_focus_target == Some(node_id) {
76 self.set_active_focus_target(None);
77 }
78 }
79 }
80
81 fn has_focus_target(&self, node_id: NodeId) -> bool {
82 self.focus_targets.contains_key(&node_id)
83 }
84
85 fn focus_target_handles(&self, node_id: NodeId) -> Vec<Rc<dyn FocusTargetHandle>> {
86 self.focus_targets
87 .get(&node_id)
88 .cloned()
89 .unwrap_or_default()
90 }
91
92 fn swap_active_focus_target(&mut self, node_id: NodeId) -> Option<NodeId> {
93 if self.active_focus_target == Some(node_id) {
94 return None;
95 }
96 let previous = self.active_focus_target;
97 self.set_active_focus_target(Some(node_id));
98 previous
99 }
100
101 fn take_first_focus_request_if_idle(&mut self) -> Option<NodeId> {
102 if self.dispatching_focus {
103 return None;
104 }
105 let next = self.pending_focus_requests.pop_front()?;
106 self.dispatching_focus = true;
107 Some(next)
108 }
109
110 fn take_next_focus_request(&mut self) -> Option<NodeId> {
111 self.pending_focus_requests.pop_front()
112 }
113
114 fn finish_focus_dispatch(&mut self) {
115 self.dispatching_focus = false;
116 }
117
118 fn take_pending_for_processing(&mut self) -> Option<Vec<NodeId>> {
119 if self.is_processing {
120 return None;
121 }
122
123 self.is_processing = true;
124 Some(self.dirty_nodes.drain().collect())
125 }
126
127 fn finish_processing<I>(&mut self, remaining: I)
128 where
129 I: IntoIterator<Item = NodeId>,
130 {
131 self.dirty_nodes.extend(remaining);
132 self.is_processing = false;
133 }
134
135 fn clear(&mut self) {
136 self.dirty_nodes.clear();
137 }
138}
139
140pub(crate) struct FocusInvalidationState {
141 manager: RefCell<FocusInvalidationManager>,
142 order: RefCell<Vec<crate::FocusEntry>>,
143 requested_move: std::cell::Cell<Option<crate::FocusDirection>>,
144}
145
146impl FocusInvalidationState {
147 pub(crate) fn new() -> Self {
148 Self {
149 manager: RefCell::new(FocusInvalidationManager::new()),
150 order: RefCell::new(Vec::new()),
151 requested_move: std::cell::Cell::new(None),
152 }
153 }
154
155 pub(crate) fn set_focus_order(&self, entries: Vec<crate::FocusEntry>) {
156 *self.order.borrow_mut() = entries;
157 }
158
159 pub(crate) fn request_move(&self, direction: crate::FocusDirection) {
160 self.requested_move.set(Some(direction));
161 }
162
163 pub(crate) fn take_requested_move(&self) -> Option<crate::FocusDirection> {
164 self.requested_move.take()
165 }
166
167 pub(crate) fn with_focus_order<T>(&self, reader: impl FnOnce(&[crate::FocusEntry]) -> T) -> T {
168 reader(&self.order.borrow())
169 }
170
171 fn schedule_invalidation(&self, node_id: NodeId) {
172 self.manager.borrow_mut().schedule_invalidation(node_id);
173 }
174
175 fn has_pending_invalidation(&self) -> bool {
176 self.manager.borrow().has_pending_invalidation()
177 }
178
179 fn set_active_focus_target(&self, node_id: Option<NodeId>) {
180 self.manager.borrow_mut().set_active_focus_target(node_id);
181 }
182
183 fn active_focus_target(&self) -> Option<NodeId> {
184 self.manager.borrow().active_focus_target()
185 }
186
187 fn register_focus_target(&self, node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
188 self.manager
189 .borrow_mut()
190 .register_focus_target(node_id, handle);
191 }
192
193 fn unregister_focus_target(&self, node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
194 self.manager
195 .borrow_mut()
196 .unregister_focus_target(node_id, handle);
197 }
198
199 pub(crate) fn has_focus_target(&self, node_id: NodeId) -> bool {
200 self.manager.borrow().has_focus_target(node_id)
201 }
202
203 pub(crate) fn clear_active_focus(&self) -> bool {
204 let previous = {
205 let mut manager = self.manager.borrow_mut();
206 let previous = manager.active_focus_target();
207 manager.set_active_focus_target(None);
208 previous
209 };
210 let Some(previous) = previous else {
211 return false;
212 };
213 let handles = self.manager.borrow().focus_target_handles(previous);
214 for handle in handles {
215 handle.set_focus_state(FocusState::Inactive);
216 }
217 true
218 }
219
220 pub(crate) fn request_focus(&self, node_id: NodeId) -> bool {
221 let accepted = {
222 let mut manager = self.manager.borrow_mut();
223 if !manager.has_focus_target(node_id) {
224 false
225 } else {
226 manager.pending_focus_requests.push_back(node_id);
227 true
228 }
229 };
230 if accepted {
231 self.drain_focus_requests();
232 }
233 accepted
234 }
235
236 fn drain_focus_requests(&self) {
237 let Some(first) = self.manager.borrow_mut().take_first_focus_request_if_idle() else {
238 return;
239 };
240
241 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
242 let mut current = first;
243 loop {
244 self.apply_focus_change(current);
245 match self.manager.borrow_mut().take_next_focus_request() {
246 Some(next) => current = next,
247 None => break,
248 }
249 }
250 }));
251
252 self.manager.borrow_mut().finish_focus_dispatch();
253
254 if let Err(payload) = result {
255 std::panic::resume_unwind(payload);
256 }
257 }
258
259 fn apply_focus_change(&self, node_id: NodeId) {
260 let previous = self.manager.borrow_mut().swap_active_focus_target(node_id);
261
262 if let Some(previous) = previous {
263 let losing_handles = self.manager.borrow().focus_target_handles(previous);
264 for handle in losing_handles {
265 handle.set_focus_state(FocusState::Inactive);
266 }
267 }
268
269 let gaining_handles = self.manager.borrow().focus_target_handles(node_id);
270 for handle in gaining_handles {
271 handle.set_focus_state(FocusState::Active);
272 }
273 }
274
275 fn process_invalidations<F>(&self, processor: F)
276 where
277 F: FnMut(NodeId),
278 {
279 let Some(nodes) = self.manager.borrow_mut().take_pending_for_processing() else {
280 return;
281 };
282
283 self.process_pending_nodes(nodes, processor);
284 }
285
286 fn clear(&self) {
287 self.manager.borrow_mut().clear();
288 }
289
290 fn process_pending_nodes<F>(&self, nodes: Vec<NodeId>, mut processor: F)
291 where
292 F: FnMut(NodeId),
293 {
294 let mut remaining = nodes.into_iter();
295 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
296 for node_id in remaining.by_ref() {
297 processor(node_id);
298 }
299 }));
300
301 self.manager.borrow_mut().finish_processing(remaining);
302
303 if let Err(payload) = result {
304 std::panic::resume_unwind(payload);
305 }
306 }
307}
308
309pub fn schedule_focus_invalidation(node_id: NodeId) {
314 crate::render_state::with_focus_dispatch(|state| state.schedule_invalidation(node_id));
315}
316
317pub fn has_pending_focus_invalidations() -> bool {
319 crate::render_state::with_focus_dispatch(FocusInvalidationState::has_pending_invalidation)
320}
321
322pub fn set_active_focus_target(node_id: Option<NodeId>) {
327 crate::render_state::with_focus_dispatch(|state| state.set_active_focus_target(node_id));
328}
329
330pub fn active_focus_target() -> Option<NodeId> {
332 crate::render_state::with_focus_dispatch(FocusInvalidationState::active_focus_target)
333}
334
335pub(crate) fn register_focus_target(node_id: NodeId, handle: Rc<dyn FocusTargetHandle>) {
336 crate::render_state::with_focus_dispatch(|state| state.register_focus_target(node_id, handle));
337}
338
339pub(crate) fn unregister_focus_target(node_id: NodeId, handle: &Rc<dyn FocusTargetHandle>) {
340 crate::render_state::with_focus_dispatch(|state| {
341 state.unregister_focus_target(node_id, handle);
342 });
343}
344
345#[cfg(test)]
346pub(crate) fn request_focus(node_id: NodeId) -> bool {
347 crate::render_state::with_focus_dispatch(|state| state.request_focus(node_id))
348}
349
350pub(crate) fn has_focus_target(node_id: NodeId) -> bool {
352 crate::render_state::with_focus_dispatch(|state| state.has_focus_target(node_id))
353}
354
355pub(crate) fn request_focus_in_context(node_id: NodeId) -> bool {
356 let Some(app_context) = crate::render_state::current_app_context_id_opt() else {
357 return false;
358 };
359 request_focus_for(app_context, node_id).unwrap_or(false)
360}
361
362pub(crate) fn clear_active_focus() -> bool {
364 crate::render_state::with_focus_dispatch(FocusInvalidationState::clear_active_focus)
365}
366
367pub(crate) fn request_focus_for(
368 app_context: crate::render_state::AppContextId,
369 node_id: NodeId,
370) -> Option<bool> {
371 crate::render_state::with_focus_dispatch_by_app_context(app_context, |state| {
372 state.request_focus(node_id)
373 })
374}
375
376pub fn process_focus_invalidations<F>(processor: F)
382where
383 F: FnMut(NodeId),
384{
385 crate::render_state::with_focus_dispatch(|state| state.process_invalidations(processor));
386}
387
388pub fn clear_focus_invalidations() {
390 crate::render_state::with_focus_dispatch(FocusInvalidationState::clear);
391}
392
393#[cfg(test)]
394#[path = "tests/focus_dispatch_tests.rs"]
395mod tests;
396
397pub fn set_keyboard_focus_visible(visible: bool) -> bool {
402 crate::thread_state::cells(|cells| cells.keyboard_focus_visible.replace(visible) != visible)
403}
404
405pub fn keyboard_focus_visible() -> bool {
407 crate::thread_state::cells(|cells| cells.keyboard_focus_visible.get())
408}