1use 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 #[must_use = "the ambient world is only active while this guard is alive"]
98 fn enter_ambient() -> SurfaceGuard {
99 let prev_surface = set_current_surface(SurfaceHandle::NONE);
100 SurfaceGuard {
101 _layout: LayoutContext::enter_ambient(),
102 _overlay: OverlayContext::enter_ambient(),
103 _focus: FocusContext::enter_ambient(),
104 _input_region: InputRegionContext::enter_ambient(),
105 _force_tick: ForceTickContext::enter_ambient(),
106 _window_commands: WindowCommandContext::enter_ambient(),
107 _services: ServiceContext::enter_ambient(),
108 _prev_surface: RestoreSurface(prev_surface),
109 }
110 }
111}
112
113impl Drop for Surface {
114 fn drop(&mut self) {
115 SURFACES.with(|s| {
116 s.borrow_mut().remove(&self.handle);
117 });
118 }
119}
120
121#[must_use = "the surface is only active while this guard is alive"]
124pub struct SurfaceGuard {
125 _layout: LayoutGuard,
126 _overlay: OverlayGuard,
127 _focus: FocusGuard,
128 _input_region: InputRegionGuard,
129 _force_tick: ForceTickGuard,
130 _window_commands: WindowCommandGuard,
131 _services: ServiceGuard,
132 _prev_surface: RestoreSurface,
133}
134
135struct RestoreSurface(SurfaceHandle);
136
137impl Drop for RestoreSurface {
138 fn drop(&mut self) {
139 set_current_surface(self.0);
140 }
141}
142
143thread_local! {
144 static SURFACES: RefCell<HashMap<SurfaceHandle, Weak<Surface>>> =
147 RefCell::new(HashMap::new());
148 static NEXT_HANDLE: Cell<u64> = const { Cell::new(1) };
150 static HOOK_INSTALLED: Cell<bool> = const { Cell::new(false) };
151}
152
153fn next_handle() -> SurfaceHandle {
154 NEXT_HANDLE.with(|c| {
155 let id = c.get();
156 c.set(id + 1);
157 SurfaceHandle(id)
158 })
159}
160
161fn install_enter_hook() {
165 HOOK_INSTALLED.with(|installed| {
166 if installed.replace(true) {
167 return;
168 }
169 set_surface_enter_hook(|handle| {
170 if handle.is_none() {
171 let guard = Surface::enter_ambient();
172 return SurfaceEnterGuard::new(move || drop(guard));
173 }
174 let surface = SURFACES.with(|s| s.borrow().get(&handle).and_then(Weak::upgrade));
175 match surface {
176 Some(surface) => {
177 let guard = surface.enter();
178 SurfaceEnterGuard::new(move || drop(guard))
179 }
180 None => SurfaceEnterGuard::noop(),
182 }
183 });
184 });
185}
186
187#[cfg(test)]
188mod tests {
189 use reactive_core::{current_surface, effect, signal};
190
191 use super::Surface;
192
193 #[test]
196 fn effect_reenters_its_surface_layout_world() {
197 use layout_reactive::{
198 AvailableSpace, LayoutStyle, compute_layout, new_leaf, track_layout,
199 };
200 use std::cell::RefCell;
201 use std::rc::Rc;
202
203 let a = Surface::new();
204 let b = Surface::new();
205 assert_ne!(a.handle(), b.handle());
206 assert!(!a.handle().is_none());
207
208 let shared = signal(0i32);
210
211 let ran_under: Rc<RefCell<Vec<u64>>> = Rc::new(RefCell::new(Vec::new()));
214 let ran_c = Rc::clone(&ran_under);
215 let read = shared.read_only();
216 let a_node = {
217 let _g = a.enter();
218 let (node, _) = new_leaf(LayoutStyle::new().width(10.0).height(10.0)).unwrap();
219 let _e = effect(move || {
220 read.get();
221 ran_c.borrow_mut().push(current_surface().0);
222 });
223 std::mem::forget(_e);
225 node
226 };
227
228 ran_under.borrow_mut().clear();
229
230 {
232 let _g = b.enter();
233 shared.set(1);
234 }
235 assert_eq!(
236 ran_under.borrow().as_slice(),
237 &[a.handle().0],
238 "A's effect must run under A's surface, not B's"
239 );
240
241 {
243 let _g = a.enter();
244 compute_layout(
245 a_node,
246 AvailableSpace::Definite(100.0),
247 AvailableSpace::Definite(100.0),
248 )
249 .unwrap();
250 assert_eq!(track_layout(a_node).unwrap().get().width, 10.0);
251 }
252 {
253 let _g = b.enter();
254 assert!(
255 track_layout(a_node).is_none(),
256 "A's node must not exist in B's layout world"
257 );
258 }
259 }
260
261 #[test]
263 fn an_effect_owned_by_no_surface_reenters_the_ambient_world() {
264 use layout_reactive::{
265 AvailableSpace, LayoutStyle, compute_layout, new_leaf, track_layout,
266 };
267 use std::cell::RefCell;
268 use std::rc::Rc;
269
270 use super::Surface;
271
272 let (ambient_node, _) = new_leaf(LayoutStyle::new().width(42.0).height(10.0)).unwrap();
274 compute_layout(
275 ambient_node,
276 AvailableSpace::Definite(100.0),
277 AvailableSpace::Definite(100.0),
278 )
279 .unwrap();
280
281 let other = Surface::new();
282 let shared = signal(0i32);
283 let read = shared.read_only();
284 let seen: Rc<RefCell<Vec<Option<f32>>>> = Rc::new(RefCell::new(Vec::new()));
285 let seen_c = Rc::clone(&seen);
286 let watcher = effect(move || {
287 read.get();
288 seen_c
289 .borrow_mut()
290 .push(track_layout(ambient_node).map(|rect| rect.get().width));
291 });
292
293 seen.borrow_mut().clear();
294 {
295 let _g = other.enter();
296 shared.set(1);
297 }
298 assert_eq!(
299 seen.borrow().as_slice(),
300 &[Some(42.0)],
301 "an ambient effect must resolve against the ambient layout world, not the active surface's"
302 );
303 drop(watcher);
304 }
305
306 #[test]
310 fn provide_inject_is_per_surface_and_survives_into_effects() {
311 use std::cell::RefCell;
312 use std::rc::Rc;
313
314 use services_core::{provide, try_inject};
315
316 let a = Surface::new();
317 let b = Surface::new();
318
319 {
320 let _g = a.enter();
321 provide(String::from("A")).unwrap();
322 assert_eq!(try_inject::<String>().as_deref(), Some("A"));
323 }
324 {
325 let _g = b.enter();
326 provide(String::from("B")).unwrap();
327 assert_eq!(try_inject::<String>().as_deref(), Some("B"));
328 }
329
330 let shared = signal(0i32);
331 let read = shared.read_only();
332 let seen: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
333 let seen_c = Rc::clone(&seen);
334 let ea = {
335 let _g = a.enter();
336 effect(move || {
337 read.get();
338 seen_c
339 .borrow_mut()
340 .push(try_inject::<String>().unwrap_or_default());
341 })
342 };
343
344 seen.borrow_mut().clear();
345 {
346 let _g = b.enter();
347 shared.set(1);
348 }
349 assert_eq!(
350 seen.borrow().as_slice(),
351 &[String::from("A")],
352 "A's effect must inject A's context even when fired from B"
353 );
354 drop(ea);
355 }
356
357 #[test]
361 fn global_signal_reruns_all_surfaces_each_under_its_context() {
362 use std::cell::RefCell;
363 use std::rc::Rc;
364
365 let a = Surface::new();
366 let b = Surface::new();
367
368 let global = signal(0i32);
370
371 let log: Rc<RefCell<Vec<(char, u64)>>> = Rc::new(RefCell::new(Vec::new()));
373
374 let log_a = Rc::clone(&log);
375 let read_a = global.read_only();
376 let ea = {
377 let _g = a.enter();
378 effect(move || {
379 read_a.get();
380 log_a.borrow_mut().push(('a', current_surface().0));
381 })
382 };
383
384 let log_b = Rc::clone(&log);
385 let read_b = global.read_only();
386 let eb = {
387 let _g = b.enter();
388 effect(move || {
389 read_b.get();
390 log_b.borrow_mut().push(('b', current_surface().0));
391 })
392 };
393
394 log.borrow_mut().clear();
395
396 global.set(1);
398
399 let entries = log.borrow().clone();
400 assert!(
401 entries.contains(&('a', a.handle().0)),
402 "A's effect must re-run under A: {entries:?}"
403 );
404 assert!(
405 entries.contains(&('b', b.handle().0)),
406 "B's effect must re-run under B: {entries:?}"
407 );
408
409 drop(ea);
410 drop(eb);
411 }
412}