repose_core/
runtime.rs

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
54/// Slot-based remember (sequential composition only)
55pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Rc<T> {
56    COMPOSER.with(|c| {
57        let mut c = c.borrow_mut();
58        if c.cursor >= c.slots.len() {
59            c.slots.push(Box::new(Rc::new(init())));
60        }
61        let cursor = c.cursor;
62        c.cursor += 1;
63        let boxed = &c.slots[cursor];
64        boxed.downcast_ref::<Rc<T>>().unwrap().clone()
65    })
66}
67
68/// Key-based remember (safe with conditionals!)
69pub fn remember_with_key<T: 'static>(key: impl Into<String>, init: impl FnOnce() -> T) -> Rc<T> {
70    COMPOSER.with(|c| {
71        let mut c = c.borrow_mut();
72        let key = key.into();
73
74        if !c.keyed_slots.contains_key(&key) {
75            c.keyed_slots.insert(key.clone(), Box::new(Rc::new(init())));
76        }
77
78        c.keyed_slots
79            .get(&key)
80            .unwrap()
81            .downcast_ref::<Rc<T>>()
82            .unwrap()
83            .clone()
84    })
85}
86
87pub fn remember_state<T: 'static>(init: impl FnOnce() -> T) -> Rc<RefCell<T>> {
88    remember(|| RefCell::new(init()))
89}
90
91pub fn remember_state_with_key<T: 'static>(
92    key: impl Into<String>,
93    init: impl FnOnce() -> T,
94) -> Rc<RefCell<T>> {
95    remember_with_key(key, || RefCell::new(init()))
96}
97
98/// Frame — output of composition for a tick: scene + input/semantics.
99pub struct Frame {
100    pub scene: Scene,
101    pub hit_regions: Vec<HitRegion>,
102    pub semantics_nodes: Vec<SemNode>,
103    pub focus_chain: Vec<u64>,
104}
105
106#[derive(Clone)]
107pub struct HitRegion {
108    pub id: u64,
109    pub rect: Rect,
110    pub on_click: Option<Rc<dyn Fn()>>,
111    pub on_scroll: Option<Rc<dyn Fn(crate::Vec2) -> crate::Vec2>>,
112    pub focusable: bool,
113    pub on_pointer_down: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
114    pub on_pointer_move: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
115    pub on_pointer_up: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
116    pub on_pointer_enter: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
117    pub on_pointer_leave: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
118    pub z_index: f32,
119    pub on_text_change: Option<Rc<dyn Fn(String)>>,
120    pub on_text_submit: Option<Rc<dyn Fn(String)>>,
121}
122
123#[derive(Clone)]
124pub struct SemNode {
125    pub id: u64,
126    pub role: Role,
127    pub label: Option<String>,
128    pub rect: Rect,
129    pub focused: bool,
130    pub enabled: bool,
131}
132
133pub struct Scheduler {
134    next_id: u64,
135    pub focused: Option<u64>,
136    pub size: (u32, u32),
137}
138
139impl Scheduler {
140    pub fn new() -> Self {
141        Self {
142            next_id: 1,
143            focused: None,
144            size: (1280, 800),
145        }
146    }
147
148    pub fn id(&mut self) -> u64 {
149        let id = self.next_id;
150        self.next_id += 1;
151        id
152    }
153
154    pub fn repose<F>(
155        &mut self,
156        mut build_root: F,
157        layout_paint: impl Fn(&View, (u32, u32)) -> (Scene, Vec<HitRegion>, Vec<SemNode>),
158    ) -> Frame
159    where
160        F: FnMut(&mut Scheduler) -> View,
161    {
162        let guard = ComposeGuard::begin();
163        let root = guard.scope.run(|| build_root(self));
164        let (scene, hits, sem) = layout_paint(&root, self.size);
165
166        let focus_chain: Vec<u64> = hits.iter().filter(|h| h.focusable).map(|h| h.id).collect();
167
168        Frame {
169            scene,
170            hit_regions: hits,
171            semantics_nodes: sem,
172            focus_chain,
173        }
174    }
175}