1use std::any::Any;
2use std::cell::RefCell;
3use std::collections::HashSet;
4use std::panic::Location;
5use std::rc::Rc;
6use std::sync::atomic::{AtomicU64, Ordering};
7
8use rustc_hash::{FxHashMap, FxHashSet};
9
10use crate::effects::Dispose;
11use crate::scope::Scope;
12use crate::{CursorIcon, Rect, Scene, View, input::PhysicalKey, semantics::Role};
13
14thread_local! {
15 pub static COMPOSER: RefCell<Composer> = RefCell::new(Composer::default());
16 static ROOT_SCOPE: RefCell<Option<Scope>> = const { RefCell::new(None) };
17 static INITIALIZER_STACK: RefCell<Vec<InitializerKind>> =
18 const { RefCell::new(Vec::new()) };
19 static PENDING_INITIALIZER_CURSOR: RefCell<Option<usize>> =
20 const { RefCell::new(None) };
21 static KEYED_DISPOSERS: RefCell<FxHashMap<String, KeyedDisposerEntry>> =
22 RefCell::new(FxHashMap::default());
23 static KEYED_DISPOSER_ORDER: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
24 static PENDING_KEYED_REGISTRATIONS: RefCell<Vec<(String, Dispose, String)>> =
25 const { RefCell::new(Vec::new()) };
26
27 static FOCUS_REQUESTS: RefCell<std::collections::VecDeque<u64>> =
31 const { RefCell::new(std::collections::VecDeque::new()) };
32
33 static FOCUS_CAPTURED: RefCell<Option<u64>> = const { RefCell::new(None) };
36}
37
38pub const CLEAR_FOCUS_MARKER: u64 = u64::MAX;
39
40const ROOT_OWNER: &str = "\0repose:root-owner";
41
42struct KeyedDisposerEntry {
43 disposer: Dispose,
44 owners: FxHashSet<String>,
45}
46
47enum InitializerKind {
48 Sequential,
49 Keyed,
50}
51
52pub(crate) fn scope_owner_token(scope: Option<&str>) -> String {
53 match scope {
54 Some(scope) if !scope.is_empty() => scope.to_string(),
55 _ => ROOT_OWNER.to_string(),
56 }
57}
58
59fn queue_keyed_registration(key: String, disposer: Dispose, owner: String) {
60 let queued = PENDING_KEYED_REGISTRATIONS.try_with(|pending| {
61 pending.try_borrow_mut().ok().map(|mut pending| {
62 pending.push((key, disposer, owner));
63 true
64 })
65 });
66 if !matches!(queued, Ok(Some(true))) {
67 crate::request_frame();
68 }
69}
70
71pub(crate) fn flush_keyed_disposer_registrations() {
72 let pending = PENDING_KEYED_REGISTRATIONS.try_with(|pending| {
73 pending
74 .try_borrow_mut()
75 .ok()
76 .map(|mut pending| std::mem::take(&mut *pending))
77 });
78 let Ok(Some(pending)) = pending else {
79 crate::request_frame();
80 return;
81 };
82 for (key, disposer, owner) in pending {
83 register_keyed_disposer_for_owner(key, disposer, owner);
84 }
85}
86
87pub(crate) fn register_keyed_disposer(key: String, disposer: Dispose) {
88 let owner = scope_owner_token(crate::scope_cache::current_scope_key().as_deref());
89 register_keyed_disposer_for_owner(key, disposer, owner);
90}
91
92pub(crate) fn register_keyed_disposer_for_owner(key: String, disposer: Dispose, owner: String) {
93 let result = KEYED_DISPOSERS
94 .try_with(|registry| {
95 let mut registry = match registry.try_borrow_mut() {
96 Ok(registry) => registry,
97 Err(_) => return Err(()),
98 };
99 if let Some(entry) = registry.get_mut(&key) {
100 if entry.owners.contains(&owner) && !entry.disposer.ptr_eq(&disposer) {
101 let previous = std::mem::replace(&mut entry.disposer, disposer.clone());
102 return Ok((false, Some(previous)));
103 }
104 entry.owners.insert(owner.clone());
105 return Ok((false, None));
106 }
107 registry.insert(
108 key.clone(),
109 KeyedDisposerEntry {
110 disposer: disposer.clone(),
111 owners: FxHashSet::from_iter([owner.clone()]),
112 },
113 );
114 Ok((true, None))
115 })
116 .ok();
117 match result {
118 Some(Ok((true, previous))) => {
119 let _ = KEYED_DISPOSER_ORDER.try_with(|order| {
120 if let Ok(mut order) = order.try_borrow_mut()
121 && !order.iter().any(|registered| registered == &key)
122 {
123 order.push(key.clone());
124 }
125 });
126 drop(previous);
127 }
128 Some(Ok((false, previous))) => {
129 if let Some(previous) = previous {
130 run_keyed_disposer(previous);
131 }
132 }
133 Some(Err(())) | None => queue_keyed_registration(key, disposer, owner),
134 }
135}
136
137pub(crate) fn keyed_disposer_has_owner(key: &str, disposer: &Dispose, owner: &str) -> bool {
138 KEYED_DISPOSERS
139 .try_with(|registry| {
140 registry.try_borrow().ok().and_then(|registry| {
141 registry
142 .get(key)
143 .filter(|entry| entry.disposer.ptr_eq(disposer))
144 .map(|entry| entry.owners.contains(owner))
145 })
146 })
147 .ok()
148 .flatten()
149 .unwrap_or(false)
150}
151
152pub(crate) fn remove_keyed_disposer_for_owner(key: &str, disposer: &Dispose, owner: &str) -> bool {
153 let mut to_run = None;
154 let mut owner_removed = false;
155 let mut registry_removed = false;
156 let result = KEYED_DISPOSERS.try_with(|registry| {
157 let mut registry = match registry.try_borrow_mut() {
158 Ok(registry) => registry,
159 Err(_) => return false,
160 };
161 let Some(entry) = registry.get_mut(key) else {
162 return true;
163 };
164 if !entry.disposer.ptr_eq(disposer) {
165 return true;
166 }
167 owner_removed = entry.owners.remove(owner);
168 if entry.owners.is_empty()
169 && let Some(entry) = registry.remove(key)
170 {
171 to_run = Some(entry.disposer);
172 registry_removed = true;
173 }
174 true
175 });
176 if !matches!(result, Ok(true)) {
177 queue_keyed_registration(key.to_string(), disposer.clone(), owner.to_string());
178 return false;
179 }
180 if registry_removed {
181 let _ = KEYED_DISPOSER_ORDER.try_with(|order| {
182 if let Ok(mut order) = order.try_borrow_mut() {
183 order.retain(|registered| registered != key);
184 }
185 });
186 }
187 if let Some(disposer) = to_run {
188 run_keyed_disposer(disposer);
189 true
190 } else {
191 owner_removed
192 }
193}
194
195pub(crate) fn take_keyed_disposer(key: &str) -> Option<Dispose> {
196 let disposer = KEYED_DISPOSERS
197 .try_with(|registry| {
198 registry
199 .try_borrow_mut()
200 .ok()?
201 .remove(key)
202 .map(|entry| entry.disposer)
203 })
204 .ok()
205 .flatten();
206 if disposer.is_some() {
207 let _ = KEYED_DISPOSER_ORDER.try_with(|order| {
208 if let Ok(mut order) = order.try_borrow_mut() {
209 order.retain(|registered| registered != key);
210 }
211 });
212 }
213 disposer
214}
215
216pub(crate) fn take_all_keyed_disposers() -> Vec<Dispose> {
217 flush_keyed_disposer_registrations();
218 let order = KEYED_DISPOSER_ORDER
219 .try_with(|order| {
220 order
221 .try_borrow_mut()
222 .ok()
223 .map(|mut order| std::mem::take(&mut *order))
224 })
225 .ok()
226 .flatten()
227 .unwrap_or_default();
228
229 KEYED_DISPOSERS
230 .try_with(|registry| {
231 registry.try_borrow_mut().ok().map(|mut registry| {
232 let mut result = Vec::with_capacity(registry.len());
233 for key in order.into_iter().rev() {
234 if let Some(entry) = registry.remove(&key) {
235 result.push(entry.disposer);
236 }
237 }
238 result.extend(registry.drain().map(|(_, entry)| entry.disposer));
239 result
240 })
241 })
242 .ok()
243 .flatten()
244 .unwrap_or_default()
245}
246
247pub(crate) fn run_keyed_disposer(disposer: Dispose) {
248 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| disposer.run()));
249}
250
251static COMPONENT_ID: AtomicU64 = AtomicU64::new(1);
254
255pub fn unique_component_id() -> u64 {
257 COMPONENT_ID.fetch_add(1, Ordering::Relaxed)
258}
259
260pub fn take_focus_request() -> Option<u64> {
261 FOCUS_REQUESTS.with(|r| r.borrow_mut().pop_front())
262}
263
264pub fn drain_focus_requests() -> Vec<u64> {
269 if let Some(captured) = FOCUS_CAPTURED.with(|c| *c.borrow()) {
270 FOCUS_REQUESTS.with(|r| r.borrow_mut().clear());
271 return vec![captured];
272 }
273 FOCUS_REQUESTS.with(|r| r.borrow_mut().drain(..).collect())
274}
275
276pub fn focus_capture() -> Option<u64> {
278 FOCUS_CAPTURED.with(|c| *c.borrow())
279}
280
281#[derive(Clone)]
287pub struct FocusRequester {
288 pub target: Rc<RefCell<Option<u64>>>,
290}
291
292impl FocusRequester {
293 pub fn new() -> Self {
294 Self {
295 target: Rc::new(RefCell::new(None)),
296 }
297 }
298
299 pub fn request_focus(&self) {
304 let _ = self.try_request_focus();
305 }
306
307 pub fn try_request_focus(&self) -> bool {
311 let Some(id) = *self.target.borrow() else {
312 return false;
313 };
314 if let Some(captured) = FOCUS_CAPTURED.with(|c| *c.borrow())
315 && captured != id
316 {
317 return false;
318 }
319 FOCUS_REQUESTS.with(|r| r.borrow_mut().push_back(id));
320 true
321 }
322
323 pub fn request_focus_directed(&self, _direction: FocusDirection) -> bool {
328 self.try_request_focus()
329 }
330
331 pub fn free_focus(&self) {
336 if let Some(id) = *self.target.borrow() {
337 FOCUS_CAPTURED.with(|c| {
338 if *c.borrow() == Some(id) {
339 *c.borrow_mut() = None;
340 }
341 });
342 }
343 FOCUS_REQUESTS.with(|r| r.borrow_mut().push_back(CLEAR_FOCUS_MARKER));
344 }
345
346 pub fn capture_focus(&self) -> bool {
351 let Some(id) = *self.target.borrow() else {
352 return false;
353 };
354 FOCUS_CAPTURED.with(|c| *c.borrow_mut() = Some(id));
355 FOCUS_REQUESTS.with(|r| r.borrow_mut().push_back(id));
356 true
357 }
358}
359
360impl Default for FocusRequester {
361 fn default() -> Self {
362 Self::new()
363 }
364}
365
366#[derive(Clone, Copy, Debug, PartialEq, Eq)]
368pub enum FocusDirection {
369 Next,
370 Previous,
371 Left,
372 Right,
373 Up,
374 Down,
375}
376
377#[derive(Clone)]
382pub struct FocusManager {
383 pub chain: Vec<u64>,
385 pub focused: Option<u64>,
387 pub hit_regions: Vec<HitRegion>,
389}
390
391impl FocusManager {
392 pub fn new(chain: Vec<u64>, focused: Option<u64>) -> Self {
393 Self {
394 chain,
395 focused,
396 hit_regions: Vec::new(),
397 }
398 }
399
400 pub fn move_focus(&mut self, dir: FocusDirection) -> Option<u64> {
403 match dir {
404 FocusDirection::Next | FocusDirection::Previous => {
405 self.move_tab(dir == FocusDirection::Previous)
406 }
407 _ => None, }
409 }
410
411 pub fn clear_focus(&self, _force: bool) {
416 FOCUS_REQUESTS.with(|r| r.borrow_mut().push_back(CLEAR_FOCUS_MARKER));
417 }
418
419 pub fn move_focus_spatial(
422 &mut self,
423 dir: FocusDirection,
424 hit_regions: &[HitRegion],
425 ) -> Option<u64> {
426 let next = spatial_focus_next(&self.chain, hit_regions, self.focused, dir)?;
427 self.focused = Some(next);
428 Some(next)
429 }
430
431 pub fn move_tab(&mut self, reverse: bool) -> Option<u64> {
435 if self.chain.is_empty() {
436 return None;
437 }
438 let next = if let Some(sub_chain) =
439 focus_group_chain(&self.chain, &self.hit_regions, self.focused)
440 {
441 if sub_chain.is_empty() {
442 return None;
443 }
444 if let Some(cur) = self.focused {
445 if let Some(idx) = sub_chain.iter().position(|&id| id == cur) {
446 if reverse {
447 if idx == 0 {
448 sub_chain[sub_chain.len() - 1]
449 } else {
450 sub_chain[idx - 1]
451 }
452 } else {
453 sub_chain[(idx + 1) % sub_chain.len()]
454 }
455 } else {
456 sub_chain[0]
457 }
458 } else if reverse {
459 sub_chain[sub_chain.len() - 1]
460 } else {
461 sub_chain[0]
462 }
463 } else {
464 if let Some(cur) = self.focused {
465 if let Some(idx) = self.chain.iter().position(|&id| id == cur) {
466 if reverse {
467 if idx == 0 {
468 self.chain[self.chain.len() - 1]
469 } else {
470 self.chain[idx - 1]
471 }
472 } else {
473 self.chain[(idx + 1) % self.chain.len()]
474 }
475 } else {
476 self.chain[0]
477 }
478 } else if reverse {
479 self.chain[self.chain.len() - 1]
480 } else {
481 self.chain[0]
482 }
483 };
484 self.focused = Some(next);
485 Some(next)
486 }
487
488 pub fn set_requester_target(requester: &FocusRequester, id: u64) {
490 *requester.target.borrow_mut() = Some(id);
491 }
492}
493
494pub fn focus_group_chain(
498 chain: &[u64],
499 hit_regions: &[HitRegion],
500 current: Option<u64>,
501) -> Option<Vec<u64>> {
502 let cur = current?;
503 let group_id = hit_regions.iter().find(|h| h.id == cur)?.focus_group_id?;
504 Some(
505 chain
506 .iter()
507 .copied()
508 .filter(|&id| {
509 id == group_id
510 || hit_regions
511 .iter()
512 .any(|h| h.id == id && h.focus_group_id == Some(group_id))
513 })
514 .collect(),
515 )
516}
517
518pub fn spatial_focus_next(
523 chain: &[u64],
524 hit_regions: &[HitRegion],
525 current: Option<u64>,
526 dir: FocusDirection,
527) -> Option<u64> {
528 if chain.is_empty() {
529 return None;
530 }
531
532 let current_rect =
533 current.and_then(|id| hit_regions.iter().find(|h| h.id == id).map(|h| h.rect));
534
535 match dir {
537 FocusDirection::Next | FocusDirection::Previous => {
538 let mut fm = FocusManager {
539 chain: chain.to_vec(),
540 focused: current,
541 hit_regions: hit_regions.to_vec(),
542 };
543 return fm.move_tab(dir == FocusDirection::Previous);
544 }
545 _ => {}
546 }
547
548 let (cx, cy) = match current_rect {
549 Some(r) => (r.x + r.w / 2.0, r.y + r.h / 2.0),
550 None => {
551 return None;
553 }
554 };
555
556 let mut best: Option<(u64, f32)> = None;
557
558 let scoped: Vec<u64>;
560 let chain: &[u64] = match focus_group_chain(chain, hit_regions, current) {
561 Some(sub) => {
562 scoped = sub;
563 &scoped
564 }
565 None => chain,
566 };
567
568 for &id in chain {
569 if Some(id) == current {
570 continue;
571 }
572 let Some(hr) = hit_regions.iter().find(|h| h.id == id) else {
573 continue;
574 };
575 let r = hr.rect;
576 let other_cx = r.x + r.w / 2.0;
577 let other_cy = r.y + r.h / 2.0;
578 let dx = other_cx - cx;
579 let dy = other_cy - cy;
580
581 let in_direction = match dir {
582 FocusDirection::Left => dx < 0.0 && dy.abs() <= r.h.max(1.0),
583 FocusDirection::Right => dx > 0.0 && dy.abs() <= r.h.max(1.0),
584 FocusDirection::Up => dy < 0.0 && dx.abs() <= r.w.max(1.0),
585 FocusDirection::Down => dy > 0.0 && dx.abs() <= r.w.max(1.0),
586 _ => false,
587 };
588
589 if !in_direction {
590 continue;
591 }
592
593 let dist = dx * dx + dy * dy;
594 let weight = dist / (r.w * r.h + 1.0).max(1.0);
595
596 match best {
597 Some((_, best_weight)) if weight >= best_weight => {}
598 _ => best = Some((id, weight)),
599 }
600 }
601
602 best.map(|(id, _)| id)
603}
604
605#[derive(Default)]
606pub struct Composer {
607 pub slots: Vec<Box<dyn Any>>,
608 pub slot_callers: Vec<&'static Location<'static>>,
609 pub cursor: usize,
610 pub auto_counts: FxHashMap<String, usize>,
611 pub keyed_slots: FxHashMap<String, Box<dyn Any>>,
612 pub keyed_owner: FxHashMap<String, String>,
613 pub keyed_owners: FxHashMap<String, rustc_hash::FxHashSet<String>>,
614 pub live_keyed_owners: rustc_hash::FxHashSet<String>,
616 pub live_keyed_keys: rustc_hash::FxHashSet<String>,
618 pub cache_preserved_owners: rustc_hash::FxHashSet<String>,
621 pub scope_caches: FxHashMap<String, crate::scope_cache::ScopeCache>,
622 pub live_scope_keys: rustc_hash::FxHashSet<String>,
623}
624
625pub struct ComposeGuard {
626 scope: Scope,
627}
628
629pub(crate) fn keyed_disposer_order(key: &str) -> usize {
630 KEYED_DISPOSER_ORDER
631 .try_with(|order| {
632 order
633 .try_borrow()
634 .ok()
635 .and_then(|order| order.iter().position(|registered| registered == key))
636 })
637 .ok()
638 .flatten()
639 .unwrap_or(usize::MAX)
640}
641
642pub(crate) fn release_keyed_owner(key: &str, owner: &str) -> (bool, Option<Box<dyn Any>>) {
643 let result = COMPOSER
644 .try_with(|composer| {
645 let mut composer = match composer.try_borrow_mut() {
646 Ok(composer) => composer,
647 Err(_) => return (false, None, None),
648 };
649 let owners = match composer.keyed_owners.get_mut(key) {
650 Some(owners) => owners,
651 None => return (false, None, None),
652 };
653 owners.remove(owner);
654 if !owners.is_empty() {
655 return (true, None, None);
656 }
657 composer.keyed_owners.remove(key);
658 let old_owner = composer.keyed_owner.remove(key);
659 (true, composer.keyed_slots.remove(key), old_owner)
660 })
661 .unwrap_or((false, None, None));
662 drop(result.2);
663 (result.0, result.1)
664}
665
666pub(crate) fn take_dead_keyed_slots(composer: &mut Composer) -> Vec<(String, Box<dyn Any>)> {
680 fn owners_of<'a>(composer: &'a Composer, key: &str) -> Vec<&'a String> {
681 match composer.keyed_owners.get(key) {
682 Some(owners) => owners.iter().collect(),
683 None => composer.keyed_owner.get(key).into_iter().collect(),
684 }
685 }
686 let dead: Vec<String> = composer
687 .keyed_slots
688 .keys()
689 .filter(|key| {
690 let owners = owners_of(composer, key);
691 let scope_live = owners
692 .iter()
693 .any(|owner| composer.live_keyed_owners.contains(*owner));
694 if !scope_live {
695 return true;
696 }
697 if composer.live_keyed_keys.contains(*key) {
698 return false;
699 }
700 !owners
701 .iter()
702 .any(|owner| composer.cache_preserved_owners.contains(*owner))
703 })
704 .cloned()
705 .collect();
706 let mut removed = Vec::with_capacity(dead.len());
707 for key in dead {
708 if let Some(value) = composer.keyed_slots.remove(&key) {
709 removed.push((key.clone(), value));
710 }
711 composer.keyed_owner.remove(&key);
712 composer.keyed_owners.remove(&key);
713 }
714 removed
715}
716
717pub(crate) fn current_scope_key_for_remember() -> Option<String> {
718 crate::scope_cache::current_scope_key()
719}
720
721fn flush_initializer_cursor() {
722 let pending = PENDING_INITIALIZER_CURSOR
723 .try_with(|pending| {
724 pending
725 .try_borrow_mut()
726 .ok()
727 .and_then(|mut pending| pending.take())
728 })
729 .ok()
730 .flatten();
731 let Some(cursor) = pending else {
732 return;
733 };
734 let restored = COMPOSER
735 .try_with(|composer| {
736 composer
737 .try_borrow_mut()
738 .ok()
739 .map(|mut composer| composer.cursor = cursor)
740 })
741 .ok()
742 .flatten();
743 if restored.is_none() {
744 let _ = PENDING_INITIALIZER_CURSOR.try_with(|pending| {
745 if let Ok(mut pending) = pending.try_borrow_mut() {
746 *pending = Some(cursor);
747 }
748 });
749 }
750}
751
752impl ComposeGuard {
753 pub fn begin() -> Self {
754 flush_initializer_cursor();
755 COMPOSER.with(|c| {
756 let mut c = c.borrow_mut();
757 c.cursor = 0;
758 c.auto_counts.clear();
759 c.live_scope_keys.clear();
760 c.live_keyed_owners.clear();
761 c.live_keyed_keys.clear();
762 c.cache_preserved_owners.clear();
763 c.live_scope_keys.insert(String::new());
764 c.live_keyed_owners.insert(scope_owner_token(None));
765 });
766
767 let scope = ROOT_SCOPE.with(|rs| {
768 if let Some(existing) = rs.borrow().clone() {
769 existing
770 } else {
771 let s = Scope::new();
772 *rs.borrow_mut() = Some(s.clone());
773 s
774 }
775 });
776
777 ComposeGuard { scope }
778 }
779
780 pub fn scope(&self) -> &Scope {
781 &self.scope
782 }
783
784 pub fn live_scope_keys(&self) -> rustc_hash::FxHashSet<String> {
785 COMPOSER.with(|c| c.borrow().live_scope_keys.clone())
786 }
787}
788
789impl Drop for ComposeGuard {
790 fn drop(&mut self) {
791 let removed = COMPOSER.with(|c| {
792 let mut c = c.borrow_mut();
793 let n = c.cursor;
794 let mut removed = Vec::new();
795 if c.slots.len() > n {
796 removed.extend(c.slots.drain(n..));
797 }
798 if c.slot_callers.len() > n {
799 c.slot_callers.truncate(n);
800 }
801 removed
802 });
803 drop(removed);
804 crate::scope_cache::gc_dead_scopes();
805 }
806}
807
808pub fn shutdown_composition() {
813 let scope = ROOT_SCOPE.with(|rs| rs.borrow_mut().take());
814 if let Some(scope) = scope {
815 scope.dispose();
816 }
817 let removed = COMPOSER.with(|c| {
818 let mut c = c.borrow_mut();
819 let slots = std::mem::take(&mut c.slots);
820 let callers = std::mem::take(&mut c.slot_callers);
821 let keyed = std::mem::take(&mut c.keyed_slots);
822 let caches = std::mem::take(&mut c.scope_caches);
823 c.keyed_owner.clear();
824 c.keyed_owners.clear();
825 c.live_keyed_owners.clear();
826 c.live_keyed_keys.clear();
827 c.cache_preserved_owners.clear();
828 c.live_scope_keys.clear();
829 c.cursor = 0;
830 c.auto_counts.clear();
831 (slots, callers, keyed, caches)
832 });
833 drop(removed);
834 for disposer in take_all_keyed_disposers() {
835 run_keyed_disposer(disposer);
836 }
837 crate::animation_driver::shutdown();
838 crate::scope_cache::clear_all_scope_deps();
839 INITIALIZER_STACK.with(|stack| stack.borrow_mut().clear());
840 PENDING_INITIALIZER_CURSOR.with(|pending| {
841 let _ = pending.borrow_mut().take();
842 });
843}
844
845struct InitializerGuard {
846 active: bool,
847 cursor: Option<usize>,
848}
849
850impl InitializerGuard {
851 fn assert_sequential_allowed() {
852 assert!(
853 INITIALIZER_STACK.with(|stack| stack
854 .try_borrow()
855 .map(|stack| stack.is_empty())
856 .unwrap_or(false)),
857 "remember initializer re-entered composition; use an explicit keyed helper"
858 );
859 }
860
861 fn enter_sequential() -> (Self, usize) {
862 Self::assert_sequential_allowed();
863 let cursor = COMPOSER.with(|composer| {
864 let mut composer = composer.borrow_mut();
865 let cursor = composer.cursor;
866 composer.cursor = composer.cursor.wrapping_add(1);
867 cursor
868 });
869 let pushed = INITIALIZER_STACK
870 .try_with(|stack| {
871 stack.try_borrow_mut().ok().map(|mut stack| {
872 stack.push(InitializerKind::Sequential);
873 true
874 })
875 })
876 .ok()
877 .flatten();
878 if pushed != Some(true) {
879 let _ = COMPOSER.try_with(|composer| {
880 if let Ok(mut composer) = composer.try_borrow_mut() {
881 composer.cursor = cursor;
882 }
883 });
884 panic!("initializer stack busy");
885 }
886 (
887 Self {
888 active: true,
889 cursor: Some(cursor),
890 },
891 cursor,
892 )
893 }
894
895 fn assert_keyed_allowed() {
896 assert!(
897 INITIALIZER_STACK.with(|stack| stack
898 .try_borrow()
899 .map(|stack| stack.is_empty())
900 .unwrap_or(false)),
901 "remember initializer re-entered composition; use an explicit keyed helper"
902 );
903 }
904
905 fn enter_keyed() -> Self {
906 Self::assert_keyed_allowed();
907 INITIALIZER_STACK.with(|stack| {
908 stack
909 .try_borrow_mut()
910 .expect("initializer stack busy")
911 .push(InitializerKind::Keyed)
912 });
913 Self {
914 active: true,
915 cursor: None,
916 }
917 }
918
919 fn commit(&mut self) {
920 if !self.active {
921 return;
922 }
923 let _ = INITIALIZER_STACK.try_with(|stack| {
924 if let Ok(mut stack) = stack.try_borrow_mut() {
925 stack.pop();
926 }
927 });
928 self.active = false;
929 }
930}
931
932impl Drop for InitializerGuard {
933 fn drop(&mut self) {
934 if !self.active {
935 return;
936 }
937 if let Some(cursor) = self.cursor {
938 let restored = COMPOSER
939 .try_with(|composer| {
940 composer
941 .try_borrow_mut()
942 .ok()
943 .map(|mut composer| composer.cursor = cursor)
944 })
945 .ok()
946 .flatten();
947 if restored.is_none() {
948 let _ = PENDING_INITIALIZER_CURSOR.try_with(|pending| {
949 if let Ok(mut pending) = pending.try_borrow_mut() {
950 *pending = Some(cursor);
951 }
952 });
953 }
954 }
955 let _ = INITIALIZER_STACK.try_with(|stack| {
956 if let Ok(mut stack) = stack.try_borrow_mut() {
957 stack.pop();
958 }
959 });
960 }
961}
962
963#[track_caller]
966pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Rc<T> {
967 flush_initializer_cursor();
968 let caller = Location::caller();
969 let (mut initializer, cursor) = InitializerGuard::enter_sequential();
970
971 let existing = COMPOSER.with(|c| {
972 let c = c.borrow();
973 if cursor >= c.slots.len() || c.slot_callers.get(cursor).copied() != Some(caller) {
974 return None;
975 }
976 c.slots[cursor].downcast_ref::<Rc<T>>().cloned()
977 });
978 if let Some(rc) = existing {
979 initializer.commit();
980 return rc;
981 }
982
983 if COMPOSER.with(|c| {
984 let c = c.borrow();
985 cursor < c.slots.len() && c.slot_callers.get(cursor).copied() == Some(caller)
986 }) {
987 log::warn!(
988 "remember: slot {} type changed {}. \
989 Use remember_with_key(key, || ...) for conditional branches.",
990 cursor,
991 std::any::type_name::<T>(),
992 );
993 }
994
995 let rc = Rc::new(init());
996 let old = COMPOSER.with(|c| {
997 let mut c = c.borrow_mut();
998 let value: Box<dyn Any> = Box::new(rc.clone());
999 let old = if cursor < c.slots.len() {
1000 Some(std::mem::replace(&mut c.slots[cursor], value))
1001 } else {
1002 c.slots.push(value);
1003 None
1004 };
1005 if cursor < c.slot_callers.len() {
1006 c.slot_callers[cursor] = caller;
1007 } else {
1008 c.slot_callers.push(caller);
1009 }
1010 old
1011 });
1012 initializer.commit();
1013 drop(old);
1014 rc
1015}
1016
1017fn push_key_part(key: &mut String, part: &str) {
1018 key.push_str(&part.len().to_string());
1019 key.push(':');
1020 key.push_str(part);
1021 key.push('|');
1022}
1023
1024#[track_caller]
1029pub fn remember_auto<T: 'static>(slot: &str, init: impl FnOnce() -> T) -> Rc<T> {
1030 let caller = Location::caller();
1031 let scope = current_scope_key_for_remember().unwrap_or_default();
1032 let mut base = String::from("auto:v1|");
1033 push_key_part(&mut base, &scope);
1034 push_key_part(&mut base, caller.file());
1035 push_key_part(&mut base, &caller.line().to_string());
1036 push_key_part(&mut base, &caller.column().to_string());
1037 push_key_part(&mut base, slot);
1038 let occurrence = COMPOSER.with(|composer| {
1039 let mut composer = composer.borrow_mut();
1040 let count = composer.auto_counts.entry(base.clone()).or_insert(0);
1041 let occurrence = *count;
1042 *count += 1;
1043 occurrence
1044 });
1045 push_key_part(&mut base, &occurrence.to_string());
1046 remember_with_key(base, init)
1047}
1048
1049pub fn keyed<T>(key: impl Into<String>, f: impl FnOnce() -> T) -> T {
1050 let key = key.into();
1051 assert!(!key.is_empty(), "keyed scopes need non-empty keys");
1052 let parent = current_scope_key_for_remember().unwrap_or_default();
1053 let mut full_key = String::new();
1054 push_key_part(&mut full_key, &parent);
1055 push_key_part(&mut full_key, &key);
1056 crate::scope_cache::with_scope_key(&full_key, f)
1057}
1058
1059pub fn remember_with_key<T: 'static>(key: impl Into<String>, init: impl FnOnce() -> T) -> Rc<T> {
1061 flush_initializer_cursor();
1062 InitializerGuard::assert_keyed_allowed();
1063 let key = key.into();
1064 let scope = current_scope_key_for_remember();
1065 let owner = scope_owner_token(scope.as_deref());
1066 let existing = COMPOSER.with(|composer| {
1067 let composer = composer.borrow();
1068 composer
1069 .keyed_slots
1070 .get(&key)
1071 .and_then(|value| value.downcast_ref::<Rc<T>>().cloned())
1072 });
1073 if let Some(existing) = existing {
1074 let old_owner = COMPOSER.with(|composer| {
1075 let mut composer = composer.borrow_mut();
1076 composer.live_keyed_keys.insert(key.clone());
1077 let old_owner = composer.keyed_owner.insert(key.clone(), owner.clone());
1078 composer
1079 .keyed_owners
1080 .entry(key.clone())
1081 .or_default()
1082 .insert(owner.clone());
1083 composer.live_keyed_owners.insert(owner);
1084 old_owner
1085 });
1086 drop(old_owner);
1087 return existing;
1088 }
1089
1090 let (wrong_type, key_count_high) = COMPOSER.with(|composer| {
1096 let composer = composer.borrow();
1097 (
1098 composer.keyed_slots.contains_key(&key),
1099 composer.keyed_slots.len() > 10_000,
1100 )
1101 });
1102 if wrong_type {
1103 log::warn!(
1104 "remember_with_key: key '{}' reused with a different type; replacing.",
1105 key
1106 );
1107 }
1108 if key_count_high {
1109 log::warn!(
1110 "remember_with_key: {} keys stored; are you generating unbounded dynamic keys?",
1111 COMPOSER.with(|composer| composer.borrow().keyed_slots.len())
1112 );
1113 }
1114
1115 let mut initializer = InitializerGuard::enter_keyed();
1116 let value = Rc::new(init());
1117 let (result, old) = COMPOSER.with(|composer| {
1118 let mut composer = composer.borrow_mut();
1119 if let Some(existing) = composer.keyed_slots.get(&key)
1120 && let Some(existing) = existing.downcast_ref::<Rc<T>>()
1121 {
1122 let existing = existing.clone();
1123 composer.live_keyed_keys.insert(key.clone());
1124 composer.keyed_owner.insert(key.clone(), owner.clone());
1125 composer
1126 .keyed_owners
1127 .entry(key.clone())
1128 .or_default()
1129 .insert(owner.clone());
1130 composer.live_keyed_owners.insert(owner);
1131 return (existing, None);
1132 }
1133 let old = composer
1134 .keyed_slots
1135 .insert(key.clone(), Box::new(value.clone()));
1136 composer.live_keyed_keys.insert(key.clone());
1137 composer.keyed_owner.insert(key.clone(), owner.clone());
1138 composer
1139 .keyed_owners
1140 .entry(key)
1141 .or_default()
1142 .insert(owner.clone());
1143 (value, old)
1144 });
1145 initializer.commit();
1146 drop(old);
1147 result
1148}
1149
1150#[track_caller]
1153pub fn remember_state<T: 'static>(init: impl FnOnce() -> T) -> Rc<RefCell<T>> {
1154 remember(|| RefCell::new(init()))
1155}
1156
1157pub fn remember_state_with_key<T: 'static>(
1159 key: impl Into<String>,
1160 init: impl FnOnce() -> T,
1161) -> Rc<RefCell<T>> {
1162 remember_with_key(key, || RefCell::new(init()))
1163}
1164
1165#[track_caller]
1168pub fn remember_state_auto<T: 'static>(slot: &str, init: impl FnOnce() -> T) -> Rc<RefCell<T>> {
1169 remember_auto(slot, || RefCell::new(init()))
1170}
1171
1172#[derive(Clone)]
1174pub struct Frame {
1175 pub scene: Scene,
1176 pub hit_regions: Vec<HitRegion>,
1177 pub semantics_nodes: Vec<SemNode>,
1178 pub focus_chain: Vec<u64>,
1179}
1180
1181#[derive(Clone, Default)]
1184pub struct HitRegion {
1185 pub id: u64,
1186 pub rect: Rect,
1187 pub depth: u32,
1190 pub parent: Option<u64>,
1191 pub on_click: Option<Rc<dyn Fn()>>,
1192 pub on_double_click: Option<Rc<dyn Fn()>>,
1193 pub on_long_click: Option<Rc<dyn Fn()>>,
1194 pub on_scroll: Option<Rc<dyn Fn(crate::Vec2) -> crate::Vec2>>,
1195 pub on_scroll_with_source:
1196 Option<Rc<dyn Fn(crate::Vec2, crate::input::ScrollSource) -> crate::Vec2>>,
1197 pub focusable: bool,
1198 pub on_pointer_down: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
1199 pub on_pointer_move: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
1200 pub on_pointer_up: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
1201 pub on_pointer_cancel: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
1202 pub on_pointer_enter: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
1203 pub on_pointer_leave: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
1204 pub z_index: f32,
1205 pub disabled: bool,
1206 pub on_text_change: Option<Rc<dyn Fn(String)>>,
1207 pub on_text_submit: Option<Rc<dyn Fn(String)>>,
1208 pub tf_state_key: Option<u64>,
1211
1212 pub tf_multiline: bool,
1214
1215 pub tf_content_origin: Option<(f32, f32)>,
1220
1221 pub tf_enabled: bool,
1223
1224 pub tf_read_only: bool,
1226
1227 pub tf_sensitive: bool,
1228
1229 pub tf_value: String,
1231
1232 pub tf_font_size: crate::units::Sp,
1235
1236 pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
1238 pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
1239 pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
1240 pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
1241 pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
1242 pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
1243 pub drag_preview: Option<crate::dnd::DragPreview>,
1245
1246 pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
1247
1248 pub on_set_progress: Option<Rc<dyn Fn(f32)>>,
1249 pub on_expand: Option<Rc<dyn Fn()>>,
1250 pub on_collapse: Option<Rc<dyn Fn()>>,
1251 pub on_dismiss: Option<Rc<dyn Fn()>>,
1252
1253 pub on_key_event: Option<Rc<dyn Fn(crate::input::KeyEvent) -> bool>>,
1256 pub on_preview_key_event: Option<Rc<dyn Fn(crate::input::KeyEvent) -> bool>>,
1258 pub on_ime: Option<Rc<dyn Fn(crate::input::ImeEvent) -> bool>>,
1261
1262 pub cursor: Option<crate::CursorIcon>,
1264
1265 pub focus_group_id: Option<u64>,
1270
1271 pub keyboard_type: crate::text::KeyboardType,
1274 pub capitalization: crate::text::KeyboardCapitalization,
1275 pub ime_action: crate::text::ImeAction,
1276 pub auto_correct: Option<bool>,
1279
1280 pub interaction_source: Option<crate::modifier::InteractionSource>,
1285}
1286
1287impl HitRegion {
1288 pub fn from_modifier(id: u64, rect: Rect, m: &crate::modifier::Modifier) -> Self {
1292 Self {
1293 id,
1294 rect,
1295 z_index: m.z_index,
1296 on_click: m.on_click.clone(),
1297 on_double_click: m.on_double_click.clone(),
1298 on_long_click: m.on_long_click.clone(),
1299 on_pointer_down: m.on_pointer_down.clone(),
1300 on_pointer_move: m.on_pointer_move.clone(),
1301 on_pointer_up: m.on_pointer_up.clone(),
1302 on_pointer_cancel: m.on_pointer_cancel.clone(),
1303 on_pointer_enter: m.on_pointer_enter.clone(),
1304 on_pointer_leave: m.on_pointer_leave.clone(),
1305 on_action: m.on_action.clone(),
1306 on_set_progress: m.on_set_progress.clone(),
1307 on_expand: m.on_expand.clone(),
1308 on_collapse: m.on_collapse.clone(),
1309 on_dismiss: m.on_dismiss.clone(),
1310 on_key_event: m.on_key_event.clone(),
1311 on_preview_key_event: m.on_preview_key_event.clone(),
1312 on_ime: m.on_ime.clone(),
1313 cursor: m.cursor.clone(),
1314 on_drag_start: m.on_drag_start.clone(),
1315 on_drag_end: m.on_drag_end.clone(),
1316 on_drag_enter: m.on_drag_enter.clone(),
1317 on_drag_over: m.on_drag_over.clone(),
1318 on_drag_leave: m.on_drag_leave.clone(),
1319 on_scroll: m.on_scroll.clone(),
1320 on_scroll_with_source: m.on_scroll_with_source.clone(),
1321 on_drop: m.on_drop.clone(),
1322 drag_preview: m.drag_preview.clone(),
1323 disabled: m.disabled,
1324 tf_enabled: true,
1325 tf_read_only: false,
1326 ..Default::default()
1327 }
1328 }
1329}
1330
1331pub fn route_action_to_point(
1336 hits: &[HitRegion],
1337 action: &crate::shortcuts::Action,
1338 center: crate::Vec2,
1339) -> bool {
1340 let mut best: Option<&HitRegion> = None;
1341 for h in hits {
1342 if h.disabled || h.on_action.is_none() {
1343 continue;
1344 }
1345 let r = h.rect;
1346 if center.x < r.x || center.x > r.x + r.w || center.y < r.y || center.y > r.y + r.h {
1347 continue;
1348 }
1349 let better = match best {
1350 None => true,
1351 Some(b) => (h.z_index, h.depth) >= (b.z_index, b.depth),
1352 };
1353 if better {
1354 best = Some(h);
1355 }
1356 }
1357 let mut current = best.map(|h| h.id);
1358 while let Some(id) = current {
1359 let Some(h) = hits.iter().find(|h| h.id == id) else {
1360 break;
1361 };
1362 if !h.disabled
1363 && let Some(cb) = &h.on_action
1364 && cb(action.clone())
1365 {
1366 return true;
1367 }
1368 current = h.parent;
1369 }
1370 false
1371}
1372
1373#[derive(Clone)]
1381pub struct SemNode {
1382 pub id: u64,
1384
1385 pub parent: Option<u64>,
1387
1388 pub role: Role,
1389 pub label: Option<String>,
1390 pub rect: Rect,
1391 pub focused: bool,
1392 pub enabled: bool,
1393 pub selectable_group: bool,
1395 pub checked: Option<bool>,
1396 pub selected: Option<bool>,
1397 pub value: Option<String>,
1398 pub heading: bool,
1399 pub live_region: Option<crate::semantics::LiveRegion>,
1400 pub state_description: Option<String>,
1401 pub traversal_index: Option<f32>,
1402 pub is_traversal_group: bool,
1403 pub hidden: bool,
1404 pub collection_rows: Option<usize>,
1405 pub collection_columns: Option<usize>,
1406 pub row_index: Option<usize>,
1407 pub column_index: Option<usize>,
1408 pub progress: Option<crate::semantics::ProgressRangeInfo>,
1409 pub error: Option<String>,
1410 pub expanded: Option<bool>,
1411 pub scrollable: bool,
1412 pub can_expand: bool,
1413 pub can_collapse: bool,
1414 pub can_dismiss: bool,
1415}
1416
1417impl Default for SemNode {
1418 fn default() -> Self {
1419 Self {
1420 id: 0,
1421 parent: None,
1422 role: Role::default(),
1423 label: None,
1424 rect: Rect::default(),
1425 focused: false,
1426 enabled: true,
1427 selectable_group: false,
1428 checked: None,
1429 selected: None,
1430 value: None,
1431 heading: false,
1432 live_region: None,
1433 state_description: None,
1434 traversal_index: None,
1435 is_traversal_group: false,
1436 hidden: false,
1437 collection_rows: None,
1438 collection_columns: None,
1439 row_index: None,
1440 column_index: None,
1441 progress: None,
1442 error: None,
1443 expanded: None,
1444 scrollable: false,
1445 can_expand: false,
1446 can_collapse: false,
1447 can_dismiss: false,
1448 }
1449 }
1450}
1451
1452pub struct Scheduler {
1453 next_id: u64,
1454 scope_key_to_id: FxHashMap<String, u32>,
1457 next_scope_id: u32,
1458 current_scope: Vec<String>,
1463 scope_local_counters: FxHashMap<String, u32>,
1465 pub focused: Option<u64>,
1466 pub size: (u32, u32),
1467 pub pointer_pos_px: Option<(f32, f32)>,
1470 pub held_keys: HashSet<PhysicalKey>,
1477 pub window_focused: bool,
1480 pub touch_points: Vec<(u64, f32, f32)>,
1487 pub mouse_primary: bool,
1491 pub mouse_secondary: bool,
1492 pub mouse_middle: bool,
1493 pub cursor_override: Option<CursorIcon>,
1501}
1502
1503impl Default for Scheduler {
1504 fn default() -> Self {
1505 Self::new()
1506 }
1507}
1508
1509impl Scheduler {
1510 pub fn new() -> Self {
1511 Self {
1512 next_id: 1,
1513 scope_key_to_id: FxHashMap::default(),
1514 next_scope_id: 1,
1515 current_scope: Vec::new(),
1516 scope_local_counters: FxHashMap::default(),
1517 focused: None,
1518 size: (1280, 800),
1519 pointer_pos_px: None,
1520 held_keys: HashSet::new(),
1521 window_focused: true,
1522 touch_points: Vec::new(),
1523 mouse_primary: false,
1524 mouse_secondary: false,
1525 mouse_middle: false,
1526 cursor_override: None,
1527 }
1528 }
1529
1530 pub fn enter_scope(&mut self, key: &str) {
1535 if !self.current_scope.iter().any(|k| k == key) {
1536 self.scope_local_counters.insert(key.to_string(), 0);
1537 }
1538 self.current_scope.push(key.to_string());
1539 self.get_or_create_scope_id(key);
1540 }
1541
1542 pub fn exit_scope(&mut self) {
1545 self.current_scope.pop();
1546 }
1547
1548 pub fn scope_guard<'a>(&'a mut self, key: &str) -> SchedulerScopeGuard<'a> {
1552 self.enter_scope(key);
1553 SchedulerScopeGuard { sched: self }
1554 }
1555
1556 pub fn scope_guard_raw(&mut self, key: &str) -> SchedulerScopeGuardRaw {
1560 let ptr = self as *mut Scheduler;
1561 unsafe { SchedulerScopeGuardRaw::enter(ptr, key) }
1562 }
1563
1564 fn get_or_create_scope_id(&mut self, key: &str) -> u32 {
1565 if let Some(&id) = self.scope_key_to_id.get(key) {
1566 id
1567 } else {
1568 let id = self.next_scope_id;
1569 self.next_scope_id += 1;
1570 self.scope_key_to_id.insert(key.to_string(), id);
1571 id
1572 }
1573 }
1574
1575 pub fn id(&mut self) -> u64 {
1576 if let Some(key) = self.current_scope.last().cloned() {
1577 let scope_id = self.scope_key_to_id.get(&key).copied().unwrap_or(0);
1578 let local = self.scope_local_counters.get_mut(&key).unwrap();
1579 let id = *local;
1580 *local += 1;
1581 (scope_id as u64) << 32 | id as u64
1582 } else {
1583 let id = self.next_id;
1585 self.next_id += 1;
1586 id
1587 }
1588 }
1589
1590 pub fn id_count(&self) -> u64 {
1591 self.next_id - 1
1592 }
1593
1594 pub fn is_held(&self, key: PhysicalKey) -> bool {
1596 self.held_keys.contains(&key)
1597 }
1598
1599 pub fn snapshot_id(&self) -> u64 {
1602 self.next_id
1603 }
1604
1605 pub fn advance_id(&mut self, count: u32) {
1608 self.next_id += count as u64;
1609 }
1610
1611 pub fn ids_used_since(&self, prev_id: u64) -> u32 {
1614 (self.next_id - prev_id) as u32
1615 }
1616
1617 pub fn gc_scope_state(&mut self, live_keys: &rustc_hash::FxHashSet<String>) {
1618 self.scope_key_to_id
1619 .retain(|key, _| live_keys.contains(key));
1620 self.scope_local_counters
1621 .retain(|key, _| live_keys.contains(key));
1622 }
1623}
1624
1625pub struct SchedulerScopeGuard<'a> {
1627 sched: &'a mut Scheduler,
1628}
1629
1630impl Drop for SchedulerScopeGuard<'_> {
1631 fn drop(&mut self) {
1632 self.sched.exit_scope();
1633 }
1634}
1635
1636pub struct SchedulerScopeGuardRaw {
1643 sched: *mut Scheduler,
1644}
1645
1646impl SchedulerScopeGuardRaw {
1647 pub unsafe fn enter(sched: *mut Scheduler, key: &str) -> Self {
1652 unsafe {
1653 (*sched).enter_scope(key);
1654 }
1655 Self { sched }
1656 }
1657}
1658
1659impl Drop for SchedulerScopeGuardRaw {
1660 fn drop(&mut self) {
1661 unsafe {
1662 (*self.sched).exit_scope();
1663 }
1664 }
1665}
1666
1667impl Scheduler {
1668 pub fn repose<F>(
1669 &mut self,
1670 mut build_root: F,
1671 layout_paint: impl Fn(&View, (u32, u32)) -> (Scene, Vec<HitRegion>, Vec<SemNode>),
1672 ) -> Frame
1673 where
1674 F: FnMut(&mut Scheduler) -> View,
1675 {
1676 let guard = ComposeGuard::begin();
1677 let root = guard.scope.run(|| build_root(self));
1678 let (scene, hits, sem) = layout_paint(&root, self.size);
1679
1680 let focus_chain: Vec<u64> = hits.iter().filter(|h| h.focusable).map(|h| h.id).collect();
1681
1682 let live_scope_keys = guard.live_scope_keys();
1683 let frame = Frame {
1684 scene,
1685 hit_regions: hits,
1686 semantics_nodes: sem,
1687 focus_chain,
1688 };
1689 drop(guard);
1690 self.gc_scope_state(&live_scope_keys);
1691 frame
1692 }
1693}
1694
1695#[cfg(test)]
1698pub fn clear_composer() {
1699 shutdown_composition();
1700}
1701
1702#[cfg(test)]
1703mod focus_trap_tests {
1704 use super::*;
1705
1706 fn region(id: u64, x: f32, group: Option<u64>) -> HitRegion {
1707 HitRegion {
1708 id,
1709 rect: Rect {
1710 x,
1711 y: 0.0,
1712 w: 10.0,
1713 h: 10.0,
1714 },
1715 focus_group_id: group,
1716 ..Default::default()
1717 }
1718 }
1719
1720 #[test]
1721 fn arrows_stay_inside_group() {
1722 let chain = vec![1, 2, 3, 4];
1725 let regions = vec![
1726 region(1, 0.0, None),
1727 region(2, 20.0, Some(9)),
1728 region(3, 40.0, Some(9)),
1729 region(4, 60.0, None),
1730 ];
1731 assert_eq!(
1732 spatial_focus_next(&chain, ®ions, Some(2), FocusDirection::Left),
1733 None,
1734 "outsider 1 is left of 2 but outside the group: trapped"
1735 );
1736 assert_eq!(
1737 spatial_focus_next(&chain, ®ions, Some(2), FocusDirection::Right),
1738 Some(3)
1739 );
1740 assert_eq!(
1741 spatial_focus_next(&chain, ®ions, Some(3), FocusDirection::Left),
1742 Some(2)
1743 );
1744 assert_eq!(
1745 spatial_focus_next(&chain, ®ions, Some(1), FocusDirection::Right),
1746 Some(2),
1747 "ungrouped focus still sees the full chain"
1748 );
1749 }
1750
1751 #[test]
1752 fn tab_cycles_inside_group() {
1753 let chain = vec![1, 2, 3, 4];
1754 let regions = vec![
1755 region(1, 0.0, None),
1756 region(2, 20.0, Some(9)),
1757 region(3, 40.0, Some(9)),
1758 region(4, 60.0, None),
1759 ];
1760 let mut fm = FocusManager::new(chain, Some(2));
1761 fm.hit_regions = regions;
1762 assert_eq!(fm.move_tab(false), Some(3));
1763 assert_eq!(fm.move_tab(false), Some(2));
1764 assert_eq!(fm.move_tab(true), Some(3));
1765 }
1766
1767 #[test]
1768 fn tab_from_outside_can_enter_group() {
1769 let chain = vec![1, 2, 3, 4];
1770 let regions = vec![
1771 region(1, 0.0, None),
1772 region(2, 20.0, Some(9)),
1773 region(3, 40.0, Some(9)),
1774 region(4, 60.0, None),
1775 ];
1776 let mut fm = FocusManager::new(chain, Some(1));
1777 fm.hit_regions = regions;
1778 assert_eq!(fm.move_tab(false), Some(2));
1779 let mut fm = FocusManager::new(vec![1, 2, 3, 4], Some(1));
1780 fm.hit_regions = vec![
1781 region(1, 0.0, None),
1782 region(2, 20.0, Some(9)),
1783 region(3, 40.0, Some(9)),
1784 region(4, 60.0, None),
1785 ];
1786 assert_eq!(fm.move_tab(true), Some(4));
1787 }
1788
1789 #[test]
1790 fn empty_group_never_moves() {
1791 let chain = vec![1, 4];
1792 let regions = vec![region(1, 0.0, None), region(4, 60.0, None)];
1793 let mut fm = FocusManager::new(chain, Some(1));
1794 fm.hit_regions = regions.clone();
1795 assert_eq!(fm.move_tab(false), Some(4));
1796 let chain = vec![1, 2, 4];
1797 let regions = vec![
1798 region(1, 0.0, None),
1799 region(2, 20.0, Some(77)),
1800 region(4, 60.0, None),
1801 ];
1802 let mut fm = FocusManager::new(chain, Some(2));
1803 fm.hit_regions = regions;
1804 assert_eq!(fm.move_tab(false), Some(2));
1805 assert_eq!(
1806 spatial_focus_next(&fm.chain, &fm.hit_regions, Some(2), FocusDirection::Right),
1807 None
1808 );
1809 }
1810}