telar_ui_core/
surface_context.rs1use std::cell::{Cell, RefCell};
14use std::collections::HashMap;
15use std::rc::{Rc, Weak};
16
17use layout_reactive::{LayoutContext, LayoutGuard};
18use platform_core::{WindowCommandContext, WindowCommandGuard};
19use reactive_core::{
20 SurfaceEnterGuard, SurfaceHandle, set_current_surface, set_surface_enter_hook,
21};
22use services_core::{ServiceContext, ServiceGuard};
23use ui_tree::{ForceTickContext, ForceTickGuard, OverlayContext, OverlayGuard};
24
25use crate::focus::{FocusContext, FocusGuard};
26use crate::input_region::{InputRegionContext, InputRegionGuard};
27
28pub struct Surface {
31 handle: SurfaceHandle,
32 layout: LayoutContext,
33 overlay: OverlayContext,
34 focus: FocusContext,
35 input_region: InputRegionContext,
36 force_tick: ForceTickContext,
37 window_commands: WindowCommandContext,
38 services: ServiceContext,
42}
43
44impl Surface {
45 pub fn new() -> Rc<Self> {
49 install_enter_hook();
50 let handle = next_handle();
51 let surface = Rc::new(Self {
52 handle,
53 layout: LayoutContext::new(),
54 overlay: OverlayContext::new(),
55 focus: FocusContext::new(),
56 input_region: InputRegionContext::new(),
57 force_tick: ForceTickContext::new(),
58 window_commands: WindowCommandContext::new(),
59 services: ServiceContext::new(),
60 });
61 SURFACES.with(|s| s.borrow_mut().insert(handle, Rc::downgrade(&surface)));
62 surface
63 }
64
65 pub fn handle(&self) -> SurfaceHandle {
67 self.handle
68 }
69
70 #[must_use = "the surface is only active while this guard is alive"]
74 pub fn enter(&self) -> SurfaceGuard {
75 let prev_surface = set_current_surface(self.handle);
77 SurfaceGuard {
78 _layout: self.layout.enter(),
79 _overlay: self.overlay.enter(),
80 _focus: self.focus.enter(),
81 _input_region: self.input_region.enter(),
82 _force_tick: self.force_tick.enter(),
83 _window_commands: self.window_commands.enter(),
84 _services: self.services.enter(),
85 _prev_surface: RestoreSurface(prev_surface),
86 }
87 }
88}
89
90impl Drop for Surface {
91 fn drop(&mut self) {
92 SURFACES.with(|s| {
93 s.borrow_mut().remove(&self.handle);
94 });
95 }
96}
97
98#[must_use = "the surface is only active while this guard is alive"]
101pub struct SurfaceGuard {
102 _layout: LayoutGuard,
103 _overlay: OverlayGuard,
104 _focus: FocusGuard,
105 _input_region: InputRegionGuard,
106 _force_tick: ForceTickGuard,
107 _window_commands: WindowCommandGuard,
108 _services: ServiceGuard,
109 _prev_surface: RestoreSurface,
110}
111
112struct RestoreSurface(SurfaceHandle);
113
114impl Drop for RestoreSurface {
115 fn drop(&mut self) {
116 set_current_surface(self.0);
117 }
118}
119
120thread_local! {
121 static SURFACES: RefCell<HashMap<SurfaceHandle, Weak<Surface>>> =
124 RefCell::new(HashMap::new());
125 static NEXT_HANDLE: Cell<u64> = const { Cell::new(1) };
127 static HOOK_INSTALLED: Cell<bool> = const { Cell::new(false) };
128}
129
130fn next_handle() -> SurfaceHandle {
131 NEXT_HANDLE.with(|c| {
132 let id = c.get();
133 c.set(id + 1);
134 SurfaceHandle(id)
135 })
136}
137
138fn install_enter_hook() {
142 HOOK_INSTALLED.with(|installed| {
143 if installed.replace(true) {
144 return;
145 }
146 set_surface_enter_hook(|handle| {
147 let surface = SURFACES.with(|s| s.borrow().get(&handle).and_then(Weak::upgrade));
148 match surface {
149 Some(surface) => {
150 let guard = surface.enter();
151 SurfaceEnterGuard::new(move || drop(guard))
152 }
153 None => SurfaceEnterGuard::noop(),
154 }
155 });
156 });
157}
158
159#[cfg(test)]
160mod tests {
161 use reactive_core::{current_surface, effect, signal};
162
163 use super::Surface;
164
165 #[test]
168 fn effect_reenters_its_surface_layout_world() {
169 use layout_reactive::{
170 AvailableSpace, LayoutStyle, compute_layout, new_leaf, track_layout,
171 };
172 use std::cell::RefCell;
173 use std::rc::Rc;
174
175 let a = Surface::new();
176 let b = Surface::new();
177 assert_ne!(a.handle(), b.handle());
178 assert!(!a.handle().is_none());
179
180 let shared = signal(0i32);
182
183 let ran_under: Rc<RefCell<Vec<u64>>> = Rc::new(RefCell::new(Vec::new()));
186 let ran_c = Rc::clone(&ran_under);
187 let read = shared.read_only();
188 let a_node = {
189 let _g = a.enter();
190 let (node, _) = new_leaf(LayoutStyle::new().width(10.0).height(10.0)).unwrap();
191 let _e = effect(move || {
192 read.get();
193 ran_c.borrow_mut().push(current_surface().0);
194 });
195 std::mem::forget(_e);
197 node
198 };
199
200 ran_under.borrow_mut().clear();
201
202 {
204 let _g = b.enter();
205 shared.set(1);
206 }
207 assert_eq!(
208 ran_under.borrow().as_slice(),
209 &[a.handle().0],
210 "A's effect must run under A's surface, not B's"
211 );
212
213 {
215 let _g = a.enter();
216 compute_layout(
217 a_node,
218 AvailableSpace::Definite(100.0),
219 AvailableSpace::Definite(100.0),
220 )
221 .unwrap();
222 assert_eq!(track_layout(a_node).unwrap().get().width, 10.0);
223 }
224 {
225 let _g = b.enter();
226 assert!(
227 track_layout(a_node).is_none(),
228 "A's node must not exist in B's layout world"
229 );
230 }
231 }
232
233 #[test]
237 fn provide_inject_is_per_surface_and_survives_into_effects() {
238 use std::cell::RefCell;
239 use std::rc::Rc;
240
241 use services_core::{provide, try_inject};
242
243 let a = Surface::new();
244 let b = Surface::new();
245
246 {
247 let _g = a.enter();
248 provide(String::from("A")).unwrap();
249 assert_eq!(try_inject::<String>().as_deref(), Some("A"));
250 }
251 {
252 let _g = b.enter();
253 provide(String::from("B")).unwrap();
254 assert_eq!(try_inject::<String>().as_deref(), Some("B"));
255 }
256
257 let shared = signal(0i32);
258 let read = shared.read_only();
259 let seen: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
260 let seen_c = Rc::clone(&seen);
261 let ea = {
262 let _g = a.enter();
263 effect(move || {
264 read.get();
265 seen_c
266 .borrow_mut()
267 .push(try_inject::<String>().unwrap_or_default());
268 })
269 };
270
271 seen.borrow_mut().clear();
272 {
273 let _g = b.enter();
274 shared.set(1);
275 }
276 assert_eq!(
277 seen.borrow().as_slice(),
278 &[String::from("A")],
279 "A's effect must inject A's context even when fired from B"
280 );
281 drop(ea);
282 }
283
284 #[test]
288 fn global_signal_reruns_all_surfaces_each_under_its_context() {
289 use std::cell::RefCell;
290 use std::rc::Rc;
291
292 let a = Surface::new();
293 let b = Surface::new();
294
295 let global = signal(0i32);
297
298 let log: Rc<RefCell<Vec<(char, u64)>>> = Rc::new(RefCell::new(Vec::new()));
300
301 let log_a = Rc::clone(&log);
302 let read_a = global.read_only();
303 let ea = {
304 let _g = a.enter();
305 effect(move || {
306 read_a.get();
307 log_a.borrow_mut().push(('a', current_surface().0));
308 })
309 };
310
311 let log_b = Rc::clone(&log);
312 let read_b = global.read_only();
313 let eb = {
314 let _g = b.enter();
315 effect(move || {
316 read_b.get();
317 log_b.borrow_mut().push(('b', current_surface().0));
318 })
319 };
320
321 log.borrow_mut().clear();
322
323 global.set(1);
325
326 let entries = log.borrow().clone();
327 assert!(
328 entries.contains(&('a', a.handle().0)),
329 "A's effect must re-run under A: {entries:?}"
330 );
331 assert!(
332 entries.contains(&('b', b.handle().0)),
333 "B's effect must re-run under B: {entries:?}"
334 );
335
336 drop(ea);
337 drop(eb);
338 }
339}