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>> = const { 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 COMPOSER.with(|c| c.borrow_mut().cursor = 0);
28
29 let scope = ROOT_SCOPE.with(|rs| {
30 if let Some(existing) = rs.borrow().clone() {
31 existing
32 } else {
33 let s = Scope::new();
34 *rs.borrow_mut() = Some(s.clone());
35 s
36 }
37 });
38
39 ComposeGuard { scope }
40 }
41
42 pub fn scope(&self) -> &Scope {
43 &self.scope
44 }
45}
46
47impl Drop for ComposeGuard {
48 fn drop(&mut self) {
49 }
53}
54
55pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Rc<T> {
57 COMPOSER.with(|c| {
58 let mut c = c.borrow_mut();
59 let cursor = c.cursor;
60 c.cursor += 1;
61
62 if cursor >= c.slots.len() {
63 let rc: Rc<T> = Rc::new(init());
64 c.slots.push(Box::new(rc.clone()));
65 return rc;
66 }
67
68 if let Some(rc) = c.slots[cursor].downcast_ref::<Rc<T>>() {
69 rc.clone()
70 } else {
71 log::warn!(
73 "remember: slot {} type changed; replacing. \
74 If this is due to conditional composition, prefer remember_with_key.",
75 cursor
76 );
77 let rc: Rc<T> = Rc::new(init());
78 c.slots[cursor] = Box::new(rc.clone());
79 rc
80 }
81 })
82}
83
84pub fn remember_with_key<T: 'static>(key: impl Into<String>, init: impl FnOnce() -> T) -> Rc<T> {
86 COMPOSER.with(|c| {
87 let mut c = c.borrow_mut();
88 let key = key.into();
89
90 if let Some(existing) = c.keyed_slots.get(&key) {
91 if let Some(rc) = existing.downcast_ref::<Rc<T>>() {
92 return rc.clone();
93 } else {
94 log::warn!(
95 "remember_with_key: key '{}' reused with a different type; replacing.",
96 key
97 );
98 }
99 }
100
101 if cfg!(debug_assertions) && c.keyed_slots.len() > 10_000 {
102 log::warn!(
103 "remember_with_key: more than 10k keys stored; \
104 are you generating unbounded dynamic keys (e.g., using timestamps)?"
105 );
106 }
107
108 let rc: Rc<T> = Rc::new(init());
109 c.keyed_slots.insert(key, Box::new(rc.clone()));
110 rc
111 })
112}
113
114pub fn remember_state<T: 'static>(init: impl FnOnce() -> T) -> Rc<RefCell<T>> {
115 remember(|| RefCell::new(init()))
116}
117
118pub fn remember_state_with_key<T: 'static>(
119 key: impl Into<String>,
120 init: impl FnOnce() -> T,
121) -> Rc<RefCell<T>> {
122 remember_with_key(key, || RefCell::new(init()))
123}
124
125pub struct Frame {
127 pub scene: Scene,
128 pub hit_regions: Vec<HitRegion>,
129 pub semantics_nodes: Vec<SemNode>,
130 pub focus_chain: Vec<u64>,
131}
132
133#[derive(Clone, Default)]
134pub struct HitRegion {
135 pub id: u64,
136 pub rect: Rect,
137 pub on_click: Option<Rc<dyn Fn()>>,
138 pub on_scroll: Option<Rc<dyn Fn(crate::Vec2) -> crate::Vec2>>,
139 pub focusable: bool,
140 pub on_pointer_down: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
141 pub on_pointer_move: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
142 pub on_pointer_up: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
143 pub on_pointer_enter: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
144 pub on_pointer_leave: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
145 pub z_index: f32,
146 pub on_text_change: Option<Rc<dyn Fn(String)>>,
147 pub on_text_submit: Option<Rc<dyn Fn(String)>>,
148 pub tf_state_key: Option<u64>,
151
152 pub tf_multiline: bool,
154
155 pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
157 pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
158 pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
159 pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
160 pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
161 pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
162
163 pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
164
165 pub cursor: Option<crate::CursorIcon>,
167}
168
169impl HitRegion {
170 pub fn from_modifier(id: u64, rect: Rect, m: &crate::modifier::Modifier) -> Self {
174 Self {
175 id,
176 rect,
177 z_index: m.z_index,
178 on_pointer_down: m.on_pointer_down.clone(),
179 on_pointer_move: m.on_pointer_move.clone(),
180 on_pointer_up: m.on_pointer_up.clone(),
181 on_pointer_enter: m.on_pointer_enter.clone(),
182 on_pointer_leave: m.on_pointer_leave.clone(),
183 on_action: m.on_action.clone(),
184 cursor: m.cursor,
185 on_drag_start: m.on_drag_start.clone(),
186 on_drag_end: m.on_drag_end.clone(),
187 on_drag_enter: m.on_drag_enter.clone(),
188 on_drag_over: m.on_drag_over.clone(),
189 on_drag_leave: m.on_drag_leave.clone(),
190 on_drop: m.on_drop.clone(),
191 ..Default::default()
192 }
193 }
194}
195
196#[derive(Clone)]
204pub struct SemNode {
205 pub id: u64,
207
208 pub parent: Option<u64>,
210
211 pub role: Role,
212 pub label: Option<String>,
213 pub rect: Rect,
214 pub focused: bool,
215 pub enabled: bool,
216}
217
218pub struct Scheduler {
219 next_id: u64,
220 pub focused: Option<u64>,
221 pub size: (u32, u32),
222}
223
224impl Default for Scheduler {
225 fn default() -> Self {
226 Self::new()
227 }
228}
229
230impl Scheduler {
231 pub fn new() -> Self {
232 Self {
233 next_id: 1,
234 focused: None,
235 size: (1280, 800),
236 }
237 }
238
239 pub fn id(&mut self) -> u64 {
240 let id = self.next_id;
241 self.next_id += 1;
242 id
243 }
244
245 pub fn id_count(&self) -> u64 {
246 self.next_id - 1
247 }
248
249 pub fn repose<F>(
250 &mut self,
251 mut build_root: F,
252 layout_paint: impl Fn(&View, (u32, u32)) -> (Scene, Vec<HitRegion>, Vec<SemNode>),
253 ) -> Frame
254 where
255 F: FnMut(&mut Scheduler) -> View,
256 {
257 let guard = ComposeGuard::begin();
258 let root = guard.scope.run(|| build_root(self));
259 let (scene, hits, sem) = layout_paint(&root, self.size);
260
261 let focus_chain: Vec<u64> = hits.iter().filter(|h| h.focusable).map(|h| h.id).collect();
262
263 Frame {
264 scene,
265 hit_regions: hits,
266 semantics_nodes: sem,
267 focus_chain,
268 }
269 }
270}