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