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_focus_spatial(
96 &mut self,
97 dir: FocusDirection,
98 hit_regions: &[HitRegion],
99 ) -> Option<u64> {
100 let next = spatial_focus_next(&self.chain, hit_regions, self.focused, dir)?;
101 self.focused = Some(next);
102 Some(next)
103 }
104
105 pub fn move_tab(&mut self, reverse: bool) -> Option<u64> {
107 if self.chain.is_empty() {
108 return None;
109 }
110 let next = if let Some(cur) = self.focused {
111 if let Some(idx) = self.chain.iter().position(|&id| id == cur) {
112 if reverse {
113 if idx == 0 {
114 self.chain[self.chain.len() - 1]
115 } else {
116 self.chain[idx - 1]
117 }
118 } else {
119 self.chain[(idx + 1) % self.chain.len()]
120 }
121 } else {
122 self.chain[0]
123 }
124 } else {
125 self.chain[0]
126 };
127 self.focused = Some(next);
128 Some(next)
129 }
130
131 pub fn set_requester_target(requester: &FocusRequester, id: u64) {
133 *requester.target.borrow_mut() = Some(id);
134 }
135}
136
137pub fn spatial_focus_next(
142 chain: &[u64],
143 hit_regions: &[HitRegion],
144 current: Option<u64>,
145 dir: FocusDirection,
146) -> Option<u64> {
147 if chain.is_empty() {
148 return None;
149 }
150
151 let current_rect =
152 current.and_then(|id| hit_regions.iter().find(|h| h.id == id).map(|h| h.rect));
153
154 match dir {
156 FocusDirection::Next | FocusDirection::Previous => {
157 let mut fm = FocusManager {
158 chain: chain.to_vec(),
159 focused: current,
160 };
161 return fm.move_tab(dir == FocusDirection::Previous);
162 }
163 _ => {}
164 }
165
166 let (cx, cy) = match current_rect {
167 Some(r) => (r.x + r.w / 2.0, r.y + r.h / 2.0),
168 None => return chain.first().copied(),
169 };
170
171 let mut best: Option<(u64, f32)> = None;
172
173 for &id in chain {
174 if Some(id) == current {
175 continue;
176 }
177 let Some(hr) = hit_regions.iter().find(|h| h.id == id) else {
178 continue;
179 };
180 let r = hr.rect;
181 let other_cx = r.x + r.w / 2.0;
182 let other_cy = r.y + r.h / 2.0;
183 let dx = other_cx - cx;
184 let dy = other_cy - cy;
185
186 let in_direction = match dir {
187 FocusDirection::Left => dx < 0.0 && dy.abs() <= r.h.max(1.0),
188 FocusDirection::Right => dx > 0.0 && dy.abs() <= r.h.max(1.0),
189 FocusDirection::Up => dy < 0.0 && dx.abs() <= r.w.max(1.0),
190 FocusDirection::Down => dy > 0.0 && dx.abs() <= r.w.max(1.0),
191 _ => false,
192 };
193
194 if !in_direction {
195 continue;
196 }
197
198 let dist = dx * dx + dy * dy;
199 let weight = dist / (r.w * r.h + 1.0).max(1.0);
200
201 match best {
202 Some((_, best_weight)) if weight >= best_weight => {}
203 _ => best = Some((id, weight)),
204 }
205 }
206
207 best.map(|(id, _)| id)
208}
209
210#[derive(Default)]
211pub struct Composer {
212 pub slots: Vec<Box<dyn Any>>,
213 pub cursor: usize,
214 pub keyed_slots: HashMap<String, Box<dyn Any>>,
215 pub scope_caches: HashMap<String, crate::scope_cache::ScopeCache>,
218}
219
220pub struct ComposeGuard {
221 scope: Scope,
222}
223
224impl ComposeGuard {
225 pub fn begin() -> Self {
226 COMPOSER.with(|c| c.borrow_mut().cursor = 0);
227
228 let scope = ROOT_SCOPE.with(|rs| {
229 if let Some(existing) = rs.borrow().clone() {
230 existing
231 } else {
232 let s = Scope::new();
233 *rs.borrow_mut() = Some(s.clone());
234 s
235 }
236 });
237
238 ComposeGuard { scope }
239 }
240
241 pub fn scope(&self) -> &Scope {
242 &self.scope
243 }
244}
245
246impl Drop for ComposeGuard {
247 fn drop(&mut self) {
248 }
252}
253
254pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Rc<T> {
256 COMPOSER.with(|c| {
257 let mut c = c.borrow_mut();
258 let cursor = c.cursor;
259 c.cursor += 1;
260
261 if cursor >= c.slots.len() {
262 let rc: Rc<T> = Rc::new(init());
263 c.slots.push(Box::new(rc.clone()));
264 return rc;
265 }
266
267 if let Some(rc) = c.slots[cursor].downcast_ref::<Rc<T>>() {
268 rc.clone()
269 } else {
270 log::warn!(
271 "remember: slot {} type changed {}. \
272 Use remember_with_key(key, || ...) for conditional branches.",
273 cursor,
274 std::any::type_name::<T>(),
275 );
276 let rc: Rc<T> = Rc::new(init());
282 c.slots[cursor] = Box::new(rc.clone());
283 rc
284 }
285 })
286}
287
288pub fn remember_with_key<T: 'static>(key: impl Into<String>, init: impl FnOnce() -> T) -> Rc<T> {
290 COMPOSER.with(|c| {
291 let mut c = c.borrow_mut();
292 let key = key.into();
293
294 if let Some(existing) = c.keyed_slots.get(&key) {
295 if let Some(rc) = existing.downcast_ref::<Rc<T>>() {
296 return rc.clone();
297 } else {
298 log::warn!(
299 "remember_with_key: key '{}' reused with a different type; replacing.",
300 key
301 );
302 }
303 }
304
305 if cfg!(debug_assertions) && c.keyed_slots.len() > 10_000 {
306 log::warn!(
307 "remember_with_key: more than 10k keys stored; \
308 are you generating unbounded dynamic keys (e.g., using timestamps)?"
309 );
310 }
311
312 let rc: Rc<T> = Rc::new(init());
313 c.keyed_slots.insert(key, Box::new(rc.clone()));
314 rc
315 })
316}
317
318pub fn remember_state<T: 'static>(init: impl FnOnce() -> T) -> Rc<RefCell<T>> {
319 remember(|| RefCell::new(init()))
320}
321
322pub fn remember_state_with_key<T: 'static>(
323 key: impl Into<String>,
324 init: impl FnOnce() -> T,
325) -> Rc<RefCell<T>> {
326 remember_with_key(key, || RefCell::new(init()))
327}
328
329#[derive(Clone)]
331pub struct Frame {
332 pub scene: Scene,
333 pub hit_regions: Vec<HitRegion>,
334 pub semantics_nodes: Vec<SemNode>,
335 pub focus_chain: Vec<u64>,
336}
337
338#[derive(Clone, Default)]
339pub struct HitRegion {
340 pub id: u64,
341 pub rect: Rect,
342 pub on_click: Option<Rc<dyn Fn()>>,
343 pub on_scroll: Option<Rc<dyn Fn(crate::Vec2) -> crate::Vec2>>,
344 pub focusable: bool,
345 pub on_pointer_down: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
346 pub on_pointer_move: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
347 pub on_pointer_up: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
348 pub on_pointer_cancel: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
349 pub on_pointer_enter: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
350 pub on_pointer_leave: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
351 pub z_index: f32,
352 pub on_text_change: Option<Rc<dyn Fn(String)>>,
353 pub on_text_submit: Option<Rc<dyn Fn(String)>>,
354 pub tf_state_key: Option<u64>,
357
358 pub tf_multiline: bool,
360
361 pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
363 pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
364 pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
365 pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
366 pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
367 pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
368
369 pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
370
371 pub cursor: Option<crate::CursorIcon>,
373}
374
375impl HitRegion {
376 pub fn from_modifier(id: u64, rect: Rect, m: &crate::modifier::Modifier) -> Self {
380 Self {
381 id,
382 rect,
383 z_index: m.z_index,
384 on_click: m.on_click.clone(),
385 on_pointer_down: m.on_pointer_down.clone(),
386 on_pointer_move: m.on_pointer_move.clone(),
387 on_pointer_up: m.on_pointer_up.clone(),
388 on_pointer_cancel: m.on_pointer_cancel.clone(),
389 on_pointer_enter: m.on_pointer_enter.clone(),
390 on_pointer_leave: m.on_pointer_leave.clone(),
391 on_action: m.on_action.clone(),
392 cursor: m.cursor,
393 on_drag_start: m.on_drag_start.clone(),
394 on_drag_end: m.on_drag_end.clone(),
395 on_drag_enter: m.on_drag_enter.clone(),
396 on_drag_over: m.on_drag_over.clone(),
397 on_drag_leave: m.on_drag_leave.clone(),
398 on_drop: m.on_drop.clone(),
399 ..Default::default()
400 }
401 }
402}
403
404#[derive(Clone)]
412pub struct SemNode {
413 pub id: u64,
415
416 pub parent: Option<u64>,
418
419 pub role: Role,
420 pub label: Option<String>,
421 pub rect: Rect,
422 pub focused: bool,
423 pub enabled: bool,
424 pub selectable_group: bool,
426}
427
428pub struct Scheduler {
429 next_id: u64,
430 scope_key_to_id: HashMap<String, u32>,
433 next_scope_id: u32,
434 current_scope: Option<String>,
438 scope_local_counters: HashMap<String, u32>,
440 pub focused: Option<u64>,
441 pub size: (u32, u32),
442}
443
444impl Default for Scheduler {
445 fn default() -> Self {
446 Self::new()
447 }
448}
449
450impl Scheduler {
451 pub fn new() -> Self {
452 Self {
453 next_id: 1,
454 scope_key_to_id: HashMap::new(),
455 next_scope_id: 1,
456 current_scope: None,
457 scope_local_counters: HashMap::new(),
458 focused: None,
459 size: (1280, 800),
460 }
461 }
462
463 pub fn enter_scope(&mut self, key: &str) {
468 self.current_scope = Some(key.to_string());
469 self.scope_local_counters.insert(key.to_string(), 0);
471 self.get_or_create_scope_id(key);
473 }
474
475 pub fn exit_scope(&mut self) {
477 self.current_scope = None;
478 }
479
480 fn get_or_create_scope_id(&mut self, key: &str) -> u32 {
481 if let Some(&id) = self.scope_key_to_id.get(key) {
482 id
483 } else {
484 let id = self.next_scope_id;
485 self.next_scope_id += 1;
486 self.scope_key_to_id.insert(key.to_string(), id);
487 id
488 }
489 }
490
491 pub fn id(&mut self) -> u64 {
492 if let Some(key) = &self.current_scope {
493 let scope_id = self.scope_key_to_id.get(key).copied().unwrap_or(0);
495 let local = self.scope_local_counters.get_mut(key).unwrap();
496 let id = *local;
497 *local += 1;
498 (scope_id as u64) << 32 | id as u64
499 } else {
500 let id = self.next_id;
502 self.next_id += 1;
503 id
504 }
505 }
506
507 pub fn id_count(&self) -> u64 {
508 self.next_id - 1
509 }
510
511 pub fn snapshot_id(&self) -> u64 {
514 self.next_id
515 }
516
517 pub fn advance_id(&mut self, count: u32) {
520 self.next_id += count as u64;
521 }
522
523 pub fn ids_used_since(&self, prev_id: u64) -> u32 {
526 (self.next_id - prev_id) as u32
527 }
528
529 pub fn repose<F>(
530 &mut self,
531 mut build_root: F,
532 layout_paint: impl Fn(&View, (u32, u32)) -> (Scene, Vec<HitRegion>, Vec<SemNode>),
533 ) -> Frame
534 where
535 F: FnMut(&mut Scheduler) -> View,
536 {
537 let guard = ComposeGuard::begin();
538 let root = guard.scope.run(|| build_root(self));
539 let (scene, hits, sem) = layout_paint(&root, self.size);
540
541 let focus_chain: Vec<u64> = hits.iter().filter(|h| h.focusable).map(|h| h.id).collect();
542
543 Frame {
544 scene,
545 hit_regions: hits,
546 semantics_nodes: sem,
547 focus_chain,
548 }
549 }
550}
551
552#[cfg(test)]
554pub fn clear_composer() {
555 COMPOSER.with(|c| {
556 let mut c = c.borrow_mut();
557 c.slots.clear();
558 c.keyed_slots.clear();
559 c.scope_caches.clear();
560 c.cursor = 0;
561 });
562 ROOT_SCOPE.with(|rs| {
563 *rs.borrow_mut() = None;
564 });
565}