1use std::any::Any;
2use std::cell::{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 static FOCUS_REQUEST: Cell<Option<u64>> = const { Cell::new(None) };
16}
17
18pub fn take_focus_request() -> Option<u64> {
19 FOCUS_REQUEST.with(|r| r.replace(None))
20}
21
22#[derive(Clone)]
28pub struct FocusRequester {
29 pub target: Rc<RefCell<Option<u64>>>,
31}
32
33impl FocusRequester {
34 pub fn new() -> Self {
35 Self {
36 target: Rc::new(RefCell::new(None)),
37 }
38 }
39
40 pub fn request_focus(&self) {
42 if let Some(id) = *self.target.borrow() {
43 FOCUS_REQUEST.with(|r| r.set(Some(id)));
44 }
45 }
46}
47
48impl Default for FocusRequester {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum FocusDirection {
57 Next,
58 Previous,
59 Left,
60 Right,
61 Up,
62 Down,
63}
64
65#[derive(Clone)]
70pub struct FocusManager {
71 pub chain: Vec<u64>,
73 pub focused: Option<u64>,
75}
76
77impl FocusManager {
78 pub fn new(chain: Vec<u64>, focused: Option<u64>) -> Self {
79 Self { chain, focused }
80 }
81
82 pub fn move_focus(&mut self, dir: FocusDirection) -> Option<u64> {
85 match dir {
86 FocusDirection::Next | FocusDirection::Previous => {
87 self.move_tab(dir == FocusDirection::Previous)
88 }
89 _ => None, }
91 }
92
93 pub fn move_tab(&mut self, reverse: bool) -> Option<u64> {
95 if self.chain.is_empty() {
96 return None;
97 }
98 let next = if let Some(cur) = self.focused {
99 if let Some(idx) = self.chain.iter().position(|&id| id == cur) {
100 if reverse {
101 if idx == 0 {
102 self.chain[self.chain.len() - 1]
103 } else {
104 self.chain[idx - 1]
105 }
106 } else {
107 self.chain[(idx + 1) % self.chain.len()]
108 }
109 } else {
110 self.chain[0]
111 }
112 } else {
113 self.chain[0]
114 };
115 self.focused = Some(next);
116 Some(next)
117 }
118
119 pub fn set_requester_target(requester: &FocusRequester, id: u64) {
121 *requester.target.borrow_mut() = Some(id);
122 }
123}
124
125#[derive(Default)]
126pub struct Composer {
127 pub slots: Vec<Box<dyn Any>>,
128 pub cursor: usize,
129 pub keyed_slots: HashMap<String, Box<dyn Any>>,
130}
131
132pub struct ComposeGuard {
133 scope: Scope,
134}
135
136impl ComposeGuard {
137 pub fn begin() -> Self {
138 COMPOSER.with(|c| c.borrow_mut().cursor = 0);
139
140 let scope = ROOT_SCOPE.with(|rs| {
141 if let Some(existing) = rs.borrow().clone() {
142 existing
143 } else {
144 let s = Scope::new();
145 *rs.borrow_mut() = Some(s.clone());
146 s
147 }
148 });
149
150 ComposeGuard { scope }
151 }
152
153 pub fn scope(&self) -> &Scope {
154 &self.scope
155 }
156}
157
158impl Drop for ComposeGuard {
159 fn drop(&mut self) {
160 }
164}
165
166pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Rc<T> {
168 COMPOSER.with(|c| {
169 let mut c = c.borrow_mut();
170 let cursor = c.cursor;
171 c.cursor += 1;
172
173 if cursor >= c.slots.len() {
174 let rc: Rc<T> = Rc::new(init());
175 c.slots.push(Box::new(rc.clone()));
176 return rc;
177 }
178
179 if let Some(rc) = c.slots[cursor].downcast_ref::<Rc<T>>() {
180 rc.clone()
181 } else {
182 log::warn!(
184 "remember: slot {} type changed; replacing. \
185 If this is due to conditional composition, prefer remember_with_key.",
186 cursor
187 );
188 let rc: Rc<T> = Rc::new(init());
189 c.slots[cursor] = Box::new(rc.clone());
190 rc
191 }
192 })
193}
194
195pub fn remember_with_key<T: 'static>(key: impl Into<String>, init: impl FnOnce() -> T) -> Rc<T> {
197 COMPOSER.with(|c| {
198 let mut c = c.borrow_mut();
199 let key = key.into();
200
201 if let Some(existing) = c.keyed_slots.get(&key) {
202 if let Some(rc) = existing.downcast_ref::<Rc<T>>() {
203 return rc.clone();
204 } else {
205 log::warn!(
206 "remember_with_key: key '{}' reused with a different type; replacing.",
207 key
208 );
209 }
210 }
211
212 if cfg!(debug_assertions) && c.keyed_slots.len() > 10_000 {
213 log::warn!(
214 "remember_with_key: more than 10k keys stored; \
215 are you generating unbounded dynamic keys (e.g., using timestamps)?"
216 );
217 }
218
219 let rc: Rc<T> = Rc::new(init());
220 c.keyed_slots.insert(key, Box::new(rc.clone()));
221 rc
222 })
223}
224
225pub fn remember_state<T: 'static>(init: impl FnOnce() -> T) -> Rc<RefCell<T>> {
226 remember(|| RefCell::new(init()))
227}
228
229pub fn remember_state_with_key<T: 'static>(
230 key: impl Into<String>,
231 init: impl FnOnce() -> T,
232) -> Rc<RefCell<T>> {
233 remember_with_key(key, || RefCell::new(init()))
234}
235
236pub struct Frame {
238 pub scene: Scene,
239 pub hit_regions: Vec<HitRegion>,
240 pub semantics_nodes: Vec<SemNode>,
241 pub focus_chain: Vec<u64>,
242}
243
244#[derive(Clone, Default)]
245pub struct HitRegion {
246 pub id: u64,
247 pub rect: Rect,
248 pub on_click: Option<Rc<dyn Fn()>>,
249 pub on_scroll: Option<Rc<dyn Fn(crate::Vec2) -> crate::Vec2>>,
250 pub focusable: bool,
251 pub on_pointer_down: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
252 pub on_pointer_move: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
253 pub on_pointer_up: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
254 pub on_pointer_enter: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
255 pub on_pointer_leave: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
256 pub z_index: f32,
257 pub on_text_change: Option<Rc<dyn Fn(String)>>,
258 pub on_text_submit: Option<Rc<dyn Fn(String)>>,
259 pub tf_state_key: Option<u64>,
262
263 pub tf_multiline: bool,
265
266 pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
268 pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
269 pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
270 pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
271 pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
272 pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
273
274 pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
275
276 pub cursor: Option<crate::CursorIcon>,
278}
279
280impl HitRegion {
281 pub fn from_modifier(id: u64, rect: Rect, m: &crate::modifier::Modifier) -> Self {
285 Self {
286 id,
287 rect,
288 z_index: m.z_index,
289 on_pointer_down: m.on_pointer_down.clone(),
290 on_pointer_move: m.on_pointer_move.clone(),
291 on_pointer_up: m.on_pointer_up.clone(),
292 on_pointer_enter: m.on_pointer_enter.clone(),
293 on_pointer_leave: m.on_pointer_leave.clone(),
294 on_action: m.on_action.clone(),
295 cursor: m.cursor,
296 on_drag_start: m.on_drag_start.clone(),
297 on_drag_end: m.on_drag_end.clone(),
298 on_drag_enter: m.on_drag_enter.clone(),
299 on_drag_over: m.on_drag_over.clone(),
300 on_drag_leave: m.on_drag_leave.clone(),
301 on_drop: m.on_drop.clone(),
302 ..Default::default()
303 }
304 }
305}
306
307#[derive(Clone)]
315pub struct SemNode {
316 pub id: u64,
318
319 pub parent: Option<u64>,
321
322 pub role: Role,
323 pub label: Option<String>,
324 pub rect: Rect,
325 pub focused: bool,
326 pub enabled: bool,
327}
328
329pub struct Scheduler {
330 next_id: u64,
331 pub focused: Option<u64>,
332 pub size: (u32, u32),
333}
334
335impl Default for Scheduler {
336 fn default() -> Self {
337 Self::new()
338 }
339}
340
341impl Scheduler {
342 pub fn new() -> Self {
343 Self {
344 next_id: 1,
345 focused: None,
346 size: (1280, 800),
347 }
348 }
349
350 pub fn id(&mut self) -> u64 {
351 let id = self.next_id;
352 self.next_id += 1;
353 id
354 }
355
356 pub fn id_count(&self) -> u64 {
357 self.next_id - 1
358 }
359
360 pub fn repose<F>(
361 &mut self,
362 mut build_root: F,
363 layout_paint: impl Fn(&View, (u32, u32)) -> (Scene, Vec<HitRegion>, Vec<SemNode>),
364 ) -> Frame
365 where
366 F: FnMut(&mut Scheduler) -> View,
367 {
368 let guard = ComposeGuard::begin();
369 let root = guard.scope.run(|| build_root(self));
370 let (scene, hits, sem) = layout_paint(&root, self.size);
371
372 let focus_chain: Vec<u64> = hits.iter().filter(|h| h.focusable).map(|h| h.id).collect();
373
374 Frame {
375 scene,
376 hit_regions: hits,
377 semantics_nodes: sem,
378 focus_chain,
379 }
380 }
381}
382
383#[cfg(test)]
385pub fn clear_composer() {
386 COMPOSER.with(|c| {
387 let mut c = c.borrow_mut();
388 c.slots.clear();
389 c.keyed_slots.clear();
390 c.cursor = 0;
391 });
392 ROOT_SCOPE.with(|rs| {
393 *rs.borrow_mut() = None;
394 });
395}