1use std::any::Any;
2use std::cell::RefCell;
3use std::collections::HashMap;
4use std::rc::Rc;
5
6use crate::scope::Scope;
7use crate::{Rect, Scene, View, semantics::Role};
8
9thread_local! {
10 pub static COMPOSER: RefCell<Composer> = RefCell::new(Composer::default());
11 static ROOT_SCOPE: RefCell<Option<Scope>> = RefCell::new(None);
12}
13
14#[derive(Default)]
15pub struct Composer {
16 pub slots: Vec<Box<dyn Any>>,
17 pub cursor: usize,
18 pub keyed_slots: HashMap<String, Box<dyn Any>>,
19}
20
21pub struct ComposeGuard {
22 scope: Scope,
23}
24
25impl ComposeGuard {
26 pub fn begin() -> Self {
27 let scope = Scope::new();
28
29 COMPOSER.with(|c| {
30 let mut c = c.borrow_mut();
31 c.cursor = 0;
32 });
33
34 ROOT_SCOPE.with(|rs| {
35 *rs.borrow_mut() = Some(scope.clone());
36 });
37
38 ComposeGuard { scope }
39 }
40
41 pub fn scope(&self) -> &Scope {
42 &self.scope
43 }
44}
45
46impl Drop for ComposeGuard {
47 fn drop(&mut self) {
48 ROOT_SCOPE.with(|rs| {
49 *rs.borrow_mut() = None;
50 });
51 }
52}
53
54pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Rc<T> {
56 COMPOSER.with(|c| {
57 let mut c = c.borrow_mut();
58 let cursor = c.cursor;
59 c.cursor += 1;
60
61 if cursor >= c.slots.len() {
62 let rc: Rc<T> = Rc::new(init());
63 c.slots.push(Box::new(rc.clone()));
64 return rc;
65 }
66
67 if let Some(rc) = c.slots[cursor].downcast_ref::<Rc<T>>() {
68 rc.clone()
69 } else {
70 log::warn!(
72 "remember: slot {} type changed; replacing. \
73 If this is due to conditional composition, prefer remember_with_key.",
74 cursor
75 );
76 let rc: Rc<T> = Rc::new(init());
77 c.slots[cursor] = Box::new(rc.clone());
78 rc
79 }
80 })
81}
82
83pub fn remember_with_key<T: 'static>(key: impl Into<String>, init: impl FnOnce() -> T) -> Rc<T> {
85 COMPOSER.with(|c| {
86 let mut c = c.borrow_mut();
87 let key = key.into();
88
89 if let Some(existing) = c.keyed_slots.get(&key) {
90 if let Some(rc) = existing.downcast_ref::<Rc<T>>() {
91 return rc.clone();
92 } else {
93 log::warn!(
94 "remember_with_key: key '{}' reused with a different type; replacing.",
95 key
96 );
97 }
98 }
99
100 if cfg!(debug_assertions) && c.keyed_slots.len() > 10_000 {
101 log::warn!(
102 "remember_with_key: more than 10k keys stored; \
103 are you generating unbounded dynamic keys (e.g., using timestamps)?"
104 );
105 }
106
107 let rc: Rc<T> = Rc::new(init());
108 c.keyed_slots.insert(key, Box::new(rc.clone()));
109 rc
110 })
111}
112
113pub fn remember_state<T: 'static>(init: impl FnOnce() -> T) -> Rc<RefCell<T>> {
114 remember(|| RefCell::new(init()))
115}
116
117pub fn remember_state_with_key<T: 'static>(
118 key: impl Into<String>,
119 init: impl FnOnce() -> T,
120) -> Rc<RefCell<T>> {
121 remember_with_key(key, || RefCell::new(init()))
122}
123
124pub struct Frame {
126 pub scene: Scene,
127 pub hit_regions: Vec<HitRegion>,
128 pub semantics_nodes: Vec<SemNode>,
129 pub focus_chain: Vec<u64>,
130}
131
132#[derive(Clone)]
133pub struct HitRegion {
134 pub id: u64,
135 pub rect: Rect,
136 pub on_click: Option<Rc<dyn Fn()>>,
137 pub on_scroll: Option<Rc<dyn Fn(crate::Vec2) -> crate::Vec2>>,
138 pub focusable: bool,
139 pub on_pointer_down: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
140 pub on_pointer_move: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
141 pub on_pointer_up: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
142 pub on_pointer_enter: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
143 pub on_pointer_leave: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
144 pub z_index: f32,
145 pub on_text_change: Option<Rc<dyn Fn(String)>>,
146 pub on_text_submit: Option<Rc<dyn Fn(String)>>,
147 pub tf_state_key: Option<u64>,
150}
151
152#[derive(Clone)]
160pub struct SemNode {
161 pub id: u64,
163 pub role: Role,
164 pub label: Option<String>,
165 pub rect: Rect,
166 pub focused: bool,
167 pub enabled: bool,
168}
169
170pub struct Scheduler {
171 next_id: u64,
172 pub focused: Option<u64>,
173 pub size: (u32, u32),
174}
175
176impl Scheduler {
177 pub fn new() -> Self {
178 Self {
179 next_id: 1,
180 focused: None,
181 size: (1280, 800),
182 }
183 }
184
185 pub fn id(&mut self) -> u64 {
186 let id = self.next_id;
187 self.next_id += 1;
188 id
189 }
190
191 pub fn repose<F>(
192 &mut self,
193 mut build_root: F,
194 layout_paint: impl Fn(&View, (u32, u32)) -> (Scene, Vec<HitRegion>, Vec<SemNode>),
195 ) -> Frame
196 where
197 F: FnMut(&mut Scheduler) -> View,
198 {
199 let guard = ComposeGuard::begin();
200 let root = guard.scope.run(|| build_root(self));
201 let (scene, hits, sem) = layout_paint(&root, self.size);
202
203 let focus_chain: Vec<u64> = hits.iter().filter(|h| h.focusable).map(|h| h.id).collect();
204
205 Frame {
206 scene,
207 hit_regions: hits,
208 semantics_nodes: sem,
209 focus_chain,
210 }
211 }
212}