1use std::any::Any;
2use std::cell::{Cell, RefCell};
3use std::panic::Location;
4use std::rc::Rc;
5
6use rustc_hash::FxHashMap;
7
8use crate::scope::Scope;
9use crate::{Rect, Scene, View, semantics::Role};
10
11thread_local! {
12 pub static COMPOSER: RefCell<Composer> = RefCell::new(Composer::default());
13 static ROOT_SCOPE: RefCell<Option<Scope>> = const { RefCell::new(None) };
14
15 static FOCUS_REQUEST: Cell<Option<u64>> = const { Cell::new(None) };
19}
20
21pub const CLEAR_FOCUS_MARKER: u64 = u64::MAX;
23
24pub fn take_focus_request() -> Option<u64> {
25 FOCUS_REQUEST.with(|r| r.replace(None))
26}
27
28#[derive(Clone)]
34pub struct FocusRequester {
35 pub target: Rc<RefCell<Option<u64>>>,
37}
38
39impl FocusRequester {
40 pub fn new() -> Self {
41 Self {
42 target: Rc::new(RefCell::new(None)),
43 }
44 }
45
46 pub fn request_focus(&self) {
48 if let Some(id) = *self.target.borrow() {
49 FOCUS_REQUEST.with(|r| r.set(Some(id)));
50 }
51 }
52
53 pub fn free_focus(&self) {
57 FOCUS_REQUEST.with(|r| r.set(Some(CLEAR_FOCUS_MARKER)));
58 }
59
60 pub fn capture_focus(&self) {
65 self.request_focus();
66 }
67}
68
69impl Default for FocusRequester {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub enum FocusDirection {
78 Next,
79 Previous,
80 Left,
81 Right,
82 Up,
83 Down,
84}
85
86#[derive(Clone)]
91pub struct FocusManager {
92 pub chain: Vec<u64>,
94 pub focused: Option<u64>,
96 pub hit_regions: Vec<HitRegion>,
98}
99
100impl FocusManager {
101 pub fn new(chain: Vec<u64>, focused: Option<u64>) -> Self {
102 Self {
103 chain,
104 focused,
105 hit_regions: Vec::new(),
106 }
107 }
108
109 pub fn move_focus(&mut self, dir: FocusDirection) -> Option<u64> {
112 match dir {
113 FocusDirection::Next | FocusDirection::Previous => {
114 self.move_tab(dir == FocusDirection::Previous)
115 }
116 _ => None, }
118 }
119
120 pub fn clear_focus(&self, _force: bool) {
125 FOCUS_REQUEST.with(|r| r.set(Some(CLEAR_FOCUS_MARKER)));
126 }
127
128 pub fn move_focus_spatial(
131 &mut self,
132 dir: FocusDirection,
133 hit_regions: &[HitRegion],
134 ) -> Option<u64> {
135 let next = spatial_focus_next(&self.chain, hit_regions, self.focused, dir)?;
136 self.focused = Some(next);
137 Some(next)
138 }
139
140 pub fn move_tab(&mut self, reverse: bool) -> Option<u64> {
144 if self.chain.is_empty() {
145 return None;
146 }
147 let current_group = self.focused.and_then(|cur| {
148 self.hit_regions
149 .iter()
150 .find(|h| h.id == cur)
151 .and_then(|h| h.focus_group_id)
152 });
153 let next = if let Some(group_id) = current_group {
154 let sub_chain: Vec<u64> = self
156 .chain
157 .iter()
158 .copied()
159 .filter(|&id| {
160 id == group_id
161 || self
162 .hit_regions
163 .iter()
164 .any(|h| h.id == id && h.focus_group_id == Some(group_id))
165 })
166 .collect();
167 if sub_chain.is_empty() {
168 return None;
169 }
170 if let Some(cur) = self.focused {
171 if let Some(idx) = sub_chain.iter().position(|&id| id == cur) {
172 if reverse {
173 if idx == 0 {
174 sub_chain[sub_chain.len() - 1]
175 } else {
176 sub_chain[idx - 1]
177 }
178 } else {
179 sub_chain[(idx + 1) % sub_chain.len()]
180 }
181 } else {
182 sub_chain[0]
183 }
184 } else if reverse {
185 sub_chain[sub_chain.len() - 1]
186 } else {
187 sub_chain[0]
188 }
189 } else {
190 if let Some(cur) = self.focused {
191 if let Some(idx) = self.chain.iter().position(|&id| id == cur) {
192 if reverse {
193 if idx == 0 {
194 self.chain[self.chain.len() - 1]
195 } else {
196 self.chain[idx - 1]
197 }
198 } else {
199 self.chain[(idx + 1) % self.chain.len()]
200 }
201 } else {
202 self.chain[0]
203 }
204 } else if reverse {
205 self.chain[self.chain.len() - 1]
206 } else {
207 self.chain[0]
208 }
209 };
210 self.focused = Some(next);
211 Some(next)
212 }
213
214 pub fn set_requester_target(requester: &FocusRequester, id: u64) {
216 *requester.target.borrow_mut() = Some(id);
217 }
218}
219
220pub fn spatial_focus_next(
225 chain: &[u64],
226 hit_regions: &[HitRegion],
227 current: Option<u64>,
228 dir: FocusDirection,
229) -> Option<u64> {
230 if chain.is_empty() {
231 return None;
232 }
233
234 let current_rect =
235 current.and_then(|id| hit_regions.iter().find(|h| h.id == id).map(|h| h.rect));
236
237 match dir {
239 FocusDirection::Next | FocusDirection::Previous => {
240 let mut fm = FocusManager {
241 chain: chain.to_vec(),
242 focused: current,
243 hit_regions: hit_regions.to_vec(),
244 };
245 return fm.move_tab(dir == FocusDirection::Previous);
246 }
247 _ => {}
248 }
249
250 let (cx, cy) = match current_rect {
251 Some(r) => (r.x + r.w / 2.0, r.y + r.h / 2.0),
252 None => {
253 return None;
255 }
256 };
257
258 let mut best: Option<(u64, f32)> = None;
259
260 for &id in chain {
261 if Some(id) == current {
262 continue;
263 }
264 let Some(hr) = hit_regions.iter().find(|h| h.id == id) else {
265 continue;
266 };
267 let r = hr.rect;
268 let other_cx = r.x + r.w / 2.0;
269 let other_cy = r.y + r.h / 2.0;
270 let dx = other_cx - cx;
271 let dy = other_cy - cy;
272
273 let in_direction = match dir {
274 FocusDirection::Left => dx < 0.0 && dy.abs() <= r.h.max(1.0),
275 FocusDirection::Right => dx > 0.0 && dy.abs() <= r.h.max(1.0),
276 FocusDirection::Up => dy < 0.0 && dx.abs() <= r.w.max(1.0),
277 FocusDirection::Down => dy > 0.0 && dx.abs() <= r.w.max(1.0),
278 _ => false,
279 };
280
281 if !in_direction {
282 continue;
283 }
284
285 let dist = dx * dx + dy * dy;
286 let weight = dist / (r.w * r.h + 1.0).max(1.0);
287
288 match best {
289 Some((_, best_weight)) if weight >= best_weight => {}
290 _ => best = Some((id, weight)),
291 }
292 }
293
294 best.map(|(id, _)| id)
295}
296
297#[derive(Default)]
298pub struct Composer {
299 pub slots: Vec<Box<dyn Any>>,
300 pub slot_callers: Vec<&'static Location<'static>>,
303 pub cursor: usize,
304 pub keyed_slots: FxHashMap<String, Box<dyn Any>>,
305 pub scope_caches: FxHashMap<String, crate::scope_cache::ScopeCache>,
308}
309
310pub struct ComposeGuard {
311 scope: Scope,
312}
313
314impl ComposeGuard {
315 pub fn begin() -> Self {
316 COMPOSER.with(|c| c.borrow_mut().cursor = 0);
317
318 let scope = ROOT_SCOPE.with(|rs| {
319 if let Some(existing) = rs.borrow().clone() {
320 existing
321 } else {
322 let s = Scope::new();
323 *rs.borrow_mut() = Some(s.clone());
324 s
325 }
326 });
327
328 ComposeGuard { scope }
329 }
330
331 pub fn scope(&self) -> &Scope {
332 &self.scope
333 }
334}
335
336impl Drop for ComposeGuard {
337 fn drop(&mut self) {
338 }
342}
343
344#[track_caller]
347pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Rc<T> {
348 let caller = Location::caller();
351 COMPOSER.with(|c| {
352 let mut c = c.borrow_mut();
353 let cursor = c.cursor;
354 c.cursor += 1;
355
356 if cursor >= c.slots.len() {
357 let rc: Rc<T> = Rc::new(init());
358 c.slots.push(Box::new(rc.clone()));
359 c.slot_callers.push(caller);
360 return rc;
361 }
362
363 let stored_caller = c.slot_callers.get(cursor).copied();
364 if stored_caller != Some(caller) {
365 let rc: Rc<T> = Rc::new(init());
366 c.slots[cursor] = Box::new(rc.clone());
367 if cursor < c.slot_callers.len() {
368 c.slot_callers[cursor] = caller;
369 } else {
370 c.slot_callers.push(caller);
371 }
372 return rc;
373 }
374
375 if let Some(rc) = c.slots[cursor].downcast_ref::<Rc<T>>() {
376 rc.clone()
377 } else {
378 log::warn!(
379 "remember: slot {} type changed {}. \
380 Use remember_with_key(key, || ...) for conditional branches.",
381 cursor,
382 std::any::type_name::<T>(),
383 );
384 let rc: Rc<T> = Rc::new(init());
385 c.slots[cursor] = Box::new(rc.clone());
386 rc
387 }
388 })
389}
390
391pub fn remember_with_key<T: 'static>(key: impl Into<String>, init: impl FnOnce() -> T) -> Rc<T> {
393 COMPOSER.with(|c| {
394 let mut c = c.borrow_mut();
395 let key = key.into();
396
397 if let Some(existing) = c.keyed_slots.get(&key) {
398 if let Some(rc) = existing.downcast_ref::<Rc<T>>() {
399 return rc.clone();
400 } else {
401 log::warn!(
402 "remember_with_key: key '{}' reused with a different type; replacing.",
403 key
404 );
405 }
406 }
407
408 if cfg!(debug_assertions) && c.keyed_slots.len() > 10_000 {
409 log::warn!(
410 "remember_with_key: more than 10k keys stored; \
411 are you generating unbounded dynamic keys (e.g., using timestamps)?"
412 );
413 }
414
415 let rc: Rc<T> = Rc::new(init());
416 c.keyed_slots.insert(key, Box::new(rc.clone()));
417 rc
418 })
419}
420
421#[track_caller]
424pub fn remember_state<T: 'static>(init: impl FnOnce() -> T) -> Rc<RefCell<T>> {
425 remember(|| RefCell::new(init()))
426}
427
428pub fn remember_state_with_key<T: 'static>(
430 key: impl Into<String>,
431 init: impl FnOnce() -> T,
432) -> Rc<RefCell<T>> {
433 remember_with_key(key, || RefCell::new(init()))
434}
435
436#[derive(Clone)]
438pub struct Frame {
439 pub scene: Scene,
440 pub hit_regions: Vec<HitRegion>,
441 pub semantics_nodes: Vec<SemNode>,
442 pub focus_chain: Vec<u64>,
443}
444
445#[derive(Clone, Default)]
446pub struct HitRegion {
447 pub id: u64,
448 pub rect: Rect,
449 pub depth: u32,
452 pub parent: Option<u64>,
453 pub on_click: Option<Rc<dyn Fn()>>,
454 pub on_double_click: Option<Rc<dyn Fn()>>,
455 pub on_long_click: Option<Rc<dyn Fn()>>,
456 pub on_scroll: Option<Rc<dyn Fn(crate::Vec2) -> crate::Vec2>>,
457 pub focusable: bool,
458 pub on_pointer_down: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
459 pub on_pointer_move: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
460 pub on_pointer_up: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
461 pub on_pointer_cancel: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
462 pub on_pointer_enter: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
463 pub on_pointer_leave: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
464 pub z_index: f32,
465 pub disabled: bool,
466 pub on_text_change: Option<Rc<dyn Fn(String)>>,
467 pub on_text_submit: Option<Rc<dyn Fn(String)>>,
468 pub tf_state_key: Option<u64>,
471
472 pub tf_multiline: bool,
474
475 pub tf_content_origin: Option<(f32, f32)>,
480
481 pub tf_enabled: bool,
483
484 pub tf_read_only: bool,
486
487 pub tf_value: String,
489
490 pub tf_font_size_dp: f32,
493
494 pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
496 pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
497 pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
498 pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
499 pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
500 pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
501 pub drag_preview: Option<crate::dnd::DragPreview>,
503
504 pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
505
506 pub on_key_event: Option<Rc<dyn Fn(crate::input::KeyEvent) -> bool>>,
509 pub on_preview_key_event: Option<Rc<dyn Fn(crate::input::KeyEvent) -> bool>>,
511
512 pub cursor: Option<crate::CursorIcon>,
514
515 pub focus_group_id: Option<u64>,
520
521 pub keyboard_type: crate::text::KeyboardType,
524 pub capitalization: crate::text::KeyboardCapitalization,
525 pub ime_action: crate::text::ImeAction,
526 pub auto_correct: Option<bool>,
529
530 pub interaction_source: Option<crate::modifier::InteractionSource>,
535}
536
537impl HitRegion {
538 pub fn from_modifier(id: u64, rect: Rect, m: &crate::modifier::Modifier) -> Self {
542 Self {
543 id,
544 rect,
545 z_index: m.z_index,
546 on_click: m.on_click.clone(),
547 on_double_click: m.on_double_click.clone(),
548 on_long_click: m.on_long_click.clone(),
549 on_pointer_down: m.on_pointer_down.clone(),
550 on_pointer_move: m.on_pointer_move.clone(),
551 on_pointer_up: m.on_pointer_up.clone(),
552 on_pointer_cancel: m.on_pointer_cancel.clone(),
553 on_pointer_enter: m.on_pointer_enter.clone(),
554 on_pointer_leave: m.on_pointer_leave.clone(),
555 on_action: m.on_action.clone(),
556 on_key_event: m.on_key_event.clone(),
557 on_preview_key_event: m.on_preview_key_event.clone(),
558 cursor: m.cursor,
559 on_drag_start: m.on_drag_start.clone(),
560 on_drag_end: m.on_drag_end.clone(),
561 on_drag_enter: m.on_drag_enter.clone(),
562 on_drag_over: m.on_drag_over.clone(),
563 on_drag_leave: m.on_drag_leave.clone(),
564 on_scroll: m.on_scroll.clone(),
565 on_drop: m.on_drop.clone(),
566 drag_preview: m.drag_preview.clone(),
567 disabled: m.disabled,
568 tf_enabled: true,
569 tf_read_only: false,
570 ..Default::default()
571 }
572 }
573}
574
575#[derive(Clone)]
583pub struct SemNode {
584 pub id: u64,
586
587 pub parent: Option<u64>,
589
590 pub role: Role,
591 pub label: Option<String>,
592 pub rect: Rect,
593 pub focused: bool,
594 pub enabled: bool,
595 pub selectable_group: bool,
597 pub checked: Option<bool>,
598 pub selected: Option<bool>,
599 pub value: Option<String>,
600}
601
602impl Default for SemNode {
603 fn default() -> Self {
604 Self {
605 id: 0,
606 parent: None,
607 role: Role::default(),
608 label: None,
609 rect: Rect::default(),
610 focused: false,
611 enabled: true,
612 selectable_group: false,
613 checked: None,
614 selected: None,
615 value: None,
616 }
617 }
618}
619
620pub struct Scheduler {
621 next_id: u64,
622 scope_key_to_id: FxHashMap<String, u32>,
625 next_scope_id: u32,
626 current_scope: Option<String>,
630 scope_local_counters: FxHashMap<String, u32>,
632 pub focused: Option<u64>,
633 pub size: (u32, u32),
634}
635
636impl Default for Scheduler {
637 fn default() -> Self {
638 Self::new()
639 }
640}
641
642impl Scheduler {
643 pub fn new() -> Self {
644 Self {
645 next_id: 1,
646 scope_key_to_id: FxHashMap::default(),
647 next_scope_id: 1,
648 current_scope: None,
649 scope_local_counters: FxHashMap::default(),
650 focused: None,
651 size: (1280, 800),
652 }
653 }
654
655 pub fn enter_scope(&mut self, key: &str) {
660 self.current_scope = Some(key.to_string());
661 self.scope_local_counters.insert(key.to_string(), 0);
663 self.get_or_create_scope_id(key);
665 }
666
667 pub fn exit_scope(&mut self) {
669 self.current_scope = None;
670 }
671
672 fn get_or_create_scope_id(&mut self, key: &str) -> u32 {
673 if let Some(&id) = self.scope_key_to_id.get(key) {
674 id
675 } else {
676 let id = self.next_scope_id;
677 self.next_scope_id += 1;
678 self.scope_key_to_id.insert(key.to_string(), id);
679 id
680 }
681 }
682
683 pub fn id(&mut self) -> u64 {
684 if let Some(key) = &self.current_scope {
685 let scope_id = self.scope_key_to_id.get(key).copied().unwrap_or(0);
687 let local = self.scope_local_counters.get_mut(key).unwrap();
688 let id = *local;
689 *local += 1;
690 (scope_id as u64) << 32 | id as u64
691 } else {
692 let id = self.next_id;
694 self.next_id += 1;
695 id
696 }
697 }
698
699 pub fn id_count(&self) -> u64 {
700 self.next_id - 1
701 }
702
703 pub fn snapshot_id(&self) -> u64 {
706 self.next_id
707 }
708
709 pub fn advance_id(&mut self, count: u32) {
712 self.next_id += count as u64;
713 }
714
715 pub fn ids_used_since(&self, prev_id: u64) -> u32 {
718 (self.next_id - prev_id) as u32
719 }
720
721 pub fn repose<F>(
722 &mut self,
723 mut build_root: F,
724 layout_paint: impl Fn(&View, (u32, u32)) -> (Scene, Vec<HitRegion>, Vec<SemNode>),
725 ) -> Frame
726 where
727 F: FnMut(&mut Scheduler) -> View,
728 {
729 let guard = ComposeGuard::begin();
730 let root = guard.scope.run(|| build_root(self));
731 let (scene, hits, sem) = layout_paint(&root, self.size);
732
733 let focus_chain: Vec<u64> = hits.iter().filter(|h| h.focusable).map(|h| h.id).collect();
734
735 Frame {
736 scene,
737 hit_regions: hits,
738 semantics_nodes: sem,
739 focus_chain,
740 }
741 }
742}
743
744#[cfg(test)]
746pub fn clear_composer() {
747 COMPOSER.with(|c| {
748 let mut c = c.borrow_mut();
749 c.slots.clear();
750 c.slot_callers.clear();
751 c.keyed_slots.clear();
752 c.scope_caches.clear();
753 c.cursor = 0;
754 });
755 ROOT_SCOPE.with(|rs| {
756 *rs.borrow_mut() = None;
757 });
758}