1use crate::api::math::Point2;
2use crate::api::Window;
3use crate::constants::{ACCEL_HANDLERS, GYRO_HANDLERS, PRE_RENDER_HANDLERS, TICK_HANDLERS};
4use pax_language::interpreter::property_resolution::IdentifierResolver;
5use pax_manifest::cartridge_generation::{TRANSITION_PHASE_ENTER, TRANSITION_PHASE_EXIT};
6use pax_manifest::UniqueTemplateNodeIdentifier;
7use pax_message::{NativeMessage, ScreenshotData};
8use pax_runtime_api::properties::{
9 drain_effects, drain_effects_with_report, property_has_direct_outbound,
10 property_outbound_debug_names, register_effect_property, register_effect_property_with_name,
11 UntypedProperty,
12};
13use pax_runtime_api::{
14 borrow, borrow_mut, use_RefCell, Event, Interpolatable, LightShape, MouseOut, MouseOver,
15 Property, RenderContext, SceneLight, SceneLighting, Store, Variable,
16};
17use_RefCell!();
18use kurbo::{Affine, Point};
19use std::any::{Any, TypeId};
20use std::cell::Cell;
21use std::collections::{HashMap, HashSet};
22use std::hash::Hash;
23use std::rc::{Rc, Weak};
24use std::sync::OnceLock;
25use std::time::Instant;
26
27use crate::{ExpandedNode, Globals};
28
29#[cfg(feature = "designtime")]
30use crate::{ComponentInstance, InstanceNode};
31
32fn effect_drain_instrumentation_enabled() -> bool {
33 static ENABLED: OnceLock<bool> = OnceLock::new();
34 *ENABLED.get_or_init(|| {
35 std::env::var("PAX_EFFECT_DRAIN_INSTRUMENTATION")
36 .map(|value| {
37 let value = value.to_ascii_lowercase();
38 matches!(value.as_str(), "1" | "true" | "yes" | "on")
39 })
40 .unwrap_or(false)
41 })
42}
43
44fn format_effect_top(top_effects: &[(String, usize)]) -> String {
45 top_effects
46 .iter()
47 .map(|(name, count)| format!("{count}x {name}"))
48 .collect::<Vec<_>>()
49 .join("; ")
50}
51
52fn summarize_debug_names(names: Vec<String>) -> Vec<(String, usize)> {
53 let mut counts = HashMap::new();
54 for name in names {
55 let category = name
56 .split_once(" (")
57 .map(|(category, _)| category)
58 .unwrap_or(&name)
59 .to_owned();
60 *counts.entry(category).or_insert(0) += 1;
61 }
62 let mut counts = counts.into_iter().collect::<Vec<_>>();
63 counts.sort_by(|(left_name, left_count), (right_name, right_count)| {
64 right_count
65 .cmp(left_count)
66 .then_with(|| left_name.cmp(right_name))
67 });
68 counts
69}
70
71impl Interpolatable for ExpandedNodeIdentifier {}
72
73fn transform_scene_light_for_layer(
74 mut light: SceneLight,
75 source_to_root: Affine,
76 root_to_target: Affine,
77) -> SceneLight {
78 if matches!(light.shape, LightShape::Point) {
79 let point = Point::new(light.position.x, light.position.y);
80 let point = root_to_target * (source_to_root * point);
81 light.position.x = point.x;
82 light.position.y = point.y;
83 }
84 light
85}
86
87#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
88pub struct ExpandedNodeIdentifier(pub u32);
90
91impl ExpandedNodeIdentifier {
92 pub fn to_u32(&self) -> u32 {
94 self.0
95 }
96}
97
98pub struct RuntimeContext {
100 next_uid: Cell<ExpandedNodeIdentifier>,
101 messages: RefCell<Vec<NativeMessage>>,
102 globals: RefCell<Globals>,
103 root_expanded_node: RefCell<Weak<ExpandedNode>>,
104 #[cfg(feature = "designtime")]
105 pub userland_frame_instance_node: RefCell<Option<Rc<dyn InstanceNode>>>,
106 #[cfg(feature = "designtime")]
107 pub userland_root_expanded_node: RefCell<Option<Rc<ExpandedNode>>>,
108 node_cache: RefCell<NodeCache>,
109 last_topmost_element: RefCell<Weak<ExpandedNode>>,
110 active_touch_targets: RefCell<HashMap<i64, ExpandedNodeIdentifier>>,
111 queued_custom_events: RefCell<Vec<(Rc<ExpandedNode>, &'static str)>>,
112 queued_renders: RefCell<Vec<Rc<ExpandedNode>>>,
113 pub layer_count: Cell<usize>,
114 pub dirty_canvases: Rc<RefCell<Vec<bool>>>,
115 dirty_canvas_nodes: RefCell<HashSet<ExpandedNodeIdentifier>>,
116 canvas_node_light_masks: RefCell<HashMap<ExpandedNodeIdentifier, u32>>,
117 lighting_overflow_counts: RefCell<HashMap<usize, usize>>,
118 targeted_canvas_replay_node_ids: RefCell<HashMap<usize, HashSet<u32>>>,
119 removed_canvas_nodes: RefCell<Vec<(usize, u32)>>,
120 occlusion_dirty: Cell<bool>,
121 layer_canvas_plan_generation: Cell<u32>,
122 canvas_drawable_layers: RefCell<HashSet<usize>>,
123 import_settings_node_count: Cell<usize>,
124 tick_handler_nodes: RefCell<Vec<ExpandedNodeIdentifier>>,
125 pre_render_handler_nodes: RefCell<Vec<ExpandedNodeIdentifier>>,
126 gyro_handler_nodes: RefCell<Vec<ExpandedNodeIdentifier>>,
127 accel_handler_nodes: RefCell<Vec<ExpandedNodeIdentifier>>,
128 screenshot_map: Rc<RefCell<HashMap<u32, ScreenshotData>>>,
129 scroller_surface_states: RefCell<HashMap<u32, ScrollerSurfaceState>>,
130 layer_scroller_owners: RefCell<HashMap<usize, ExpandedNodeIdentifier>>,
131 root_scroller_id: Cell<Option<u32>>,
132 visual_viewport_state: Cell<Option<VisualViewportState>>,
133}
134
135#[derive(Clone, Copy, Debug, Default)]
136pub struct ScrollerSurfaceState {
138 pub viewport_width: f64,
139 pub viewport_height: f64,
140 pub content_width: f64,
141 pub content_height: f64,
142 pub scroll_x: f64,
143 pub scroll_y: f64,
144 pub presentation_scroll_x: f64,
145 pub presentation_scroll_y: f64,
146 pub clip_content: bool,
147}
148
149#[derive(Clone, Copy, Debug, PartialEq, Eq)]
150pub enum ScrollerSurfaceStateChange {
152 Unchanged,
153 ScrollOnly,
154 Structural,
155}
156
157impl ScrollerSurfaceState {
158 fn close_enough(lhs: f64, rhs: f64) -> bool {
159 (lhs - rhs).abs() <= 1e-4
160 }
161
162 fn structural_eq(&self, other: &Self) -> bool {
163 Self::close_enough(self.viewport_width, other.viewport_width)
164 && Self::close_enough(self.viewport_height, other.viewport_height)
165 && Self::close_enough(self.content_width, other.content_width)
166 && Self::close_enough(self.content_height, other.content_height)
167 && self.clip_content == other.clip_content
168 }
169
170 fn scroll_eq(&self, other: &Self) -> bool {
171 Self::close_enough(self.scroll_x, other.scroll_x)
172 && Self::close_enough(self.scroll_y, other.scroll_y)
173 && Self::close_enough(self.presentation_scroll_x, other.presentation_scroll_x)
174 && Self::close_enough(self.presentation_scroll_y, other.presentation_scroll_y)
175 }
176}
177
178#[derive(Clone, Copy, Debug, Default)]
179pub struct VisualViewportState {
181 pub width: f64,
182 pub height: f64,
183 pub offset_x: f64,
184 pub offset_y: f64,
185 pub page_scroll_x: f64,
186 pub page_scroll_y: f64,
187}
188
189struct NodeCache {
190 eid_to_node: HashMap<ExpandedNodeIdentifier, Rc<ExpandedNode>>,
191 uni_to_eid: HashMap<UniqueTemplateNodeIdentifier, Vec<ExpandedNodeIdentifier>>,
192}
193
194impl NodeCache {
195 fn new() -> Self {
196 Self {
197 eid_to_node: Default::default(),
198 uni_to_eid: Default::default(),
199 }
200 }
201
202 fn add_to_cache(&mut self, node: &Rc<ExpandedNode>) {
204 self.eid_to_node.insert(node.id, Rc::clone(&node));
205 let uni = borrow!(node.instance_node)
206 .base()
207 .template_node_identifier
208 .clone();
209 if let Some(uni) = uni {
210 self.uni_to_eid.entry(uni).or_default().push(node.id);
211 }
212 }
213
214 fn remove_from_cache(&mut self, node: &Rc<ExpandedNode>) {
216 self.eid_to_node.remove(&node.id);
217 if let Some(uni) = &borrow!(node.instance_node).base().template_node_identifier {
218 self.uni_to_eid
219 .entry(uni.clone())
220 .or_default()
221 .retain(|&n| n != node.id);
222 }
223 }
224}
225
226impl RuntimeContext {
227 #[cfg(not(feature = "designtime"))]
228 pub fn new(globals: Globals) -> Self {
230 Self {
231 next_uid: Cell::new(ExpandedNodeIdentifier(0)),
232 messages: RefCell::new(Vec::new()),
233 globals: RefCell::new(globals),
234 root_expanded_node: RefCell::new(Weak::new()),
235 node_cache: RefCell::new(NodeCache::new()),
236 active_touch_targets: Default::default(),
237 queued_custom_events: Default::default(),
238 queued_renders: Default::default(),
239 layer_count: Cell::default(),
240 last_topmost_element: Default::default(),
241 dirty_canvases: Default::default(),
242 dirty_canvas_nodes: Default::default(),
243 canvas_node_light_masks: Default::default(),
244 lighting_overflow_counts: Default::default(),
245 targeted_canvas_replay_node_ids: Default::default(),
246 removed_canvas_nodes: Default::default(),
247 occlusion_dirty: Cell::new(true),
248 layer_canvas_plan_generation: Cell::new(1),
249 canvas_drawable_layers: Default::default(),
250 import_settings_node_count: Cell::new(0),
251 tick_handler_nodes: Default::default(),
252 pre_render_handler_nodes: Default::default(),
253 gyro_handler_nodes: Default::default(),
254 accel_handler_nodes: Default::default(),
255 screenshot_map: Default::default(),
256 scroller_surface_states: Default::default(),
257 layer_scroller_owners: Default::default(),
258 root_scroller_id: Cell::new(None),
259 visual_viewport_state: Cell::new(None),
260 }
261 }
262
263 #[cfg(feature = "designtime")]
264 pub fn new(globals: Globals, userland: Rc<ComponentInstance>) -> Self {
266 Self {
267 next_uid: Cell::new(ExpandedNodeIdentifier(0)),
268 messages: RefCell::new(Vec::new()),
269 globals: RefCell::new(globals),
270 root_expanded_node: RefCell::new(Weak::new()),
271 userland_frame_instance_node: RefCell::new(Some(userland)),
272 userland_root_expanded_node: Default::default(),
273 node_cache: RefCell::new(NodeCache::new()),
274 active_touch_targets: Default::default(),
275 queued_custom_events: Default::default(),
276 queued_renders: Default::default(),
277 layer_count: Cell::default(),
278 last_topmost_element: Default::default(),
279 dirty_canvases: Default::default(),
280 dirty_canvas_nodes: Default::default(),
281 canvas_node_light_masks: Default::default(),
282 lighting_overflow_counts: Default::default(),
283 targeted_canvas_replay_node_ids: Default::default(),
284 removed_canvas_nodes: Default::default(),
285 occlusion_dirty: Cell::new(true),
286 layer_canvas_plan_generation: Cell::new(1),
287 canvas_drawable_layers: Default::default(),
288 import_settings_node_count: Cell::new(0),
289 tick_handler_nodes: Default::default(),
290 pre_render_handler_nodes: Default::default(),
291 gyro_handler_nodes: Default::default(),
292 accel_handler_nodes: Default::default(),
293 screenshot_map: Default::default(),
294 scroller_surface_states: Default::default(),
295 layer_scroller_owners: Default::default(),
296 root_scroller_id: Cell::new(None),
297 visual_viewport_state: Cell::new(None),
298 }
299 }
300
301 #[cfg(feature = "designtime")]
302 pub fn new_empty(globals: Globals) -> Self {
304 Self {
305 next_uid: Cell::new(ExpandedNodeIdentifier(0)),
306 messages: RefCell::new(Vec::new()),
307 globals: RefCell::new(globals),
308 root_expanded_node: RefCell::new(Weak::new()),
309 userland_frame_instance_node: RefCell::new(None),
310 userland_root_expanded_node: Default::default(),
311 node_cache: RefCell::new(NodeCache::new()),
312 active_touch_targets: Default::default(),
313 queued_custom_events: Default::default(),
314 queued_renders: Default::default(),
315 layer_count: Cell::default(),
316 last_topmost_element: Default::default(),
317 dirty_canvases: Default::default(),
318 dirty_canvas_nodes: Default::default(),
319 canvas_node_light_masks: Default::default(),
320 lighting_overflow_counts: Default::default(),
321 targeted_canvas_replay_node_ids: Default::default(),
322 removed_canvas_nodes: Default::default(),
323 occlusion_dirty: Cell::new(true),
324 layer_canvas_plan_generation: Cell::new(1),
325 canvas_drawable_layers: Default::default(),
326 import_settings_node_count: Cell::new(0),
327 tick_handler_nodes: Default::default(),
328 pre_render_handler_nodes: Default::default(),
329 gyro_handler_nodes: Default::default(),
330 accel_handler_nodes: Default::default(),
331 screenshot_map: Default::default(),
332 scroller_surface_states: Default::default(),
333 layer_scroller_owners: Default::default(),
334 root_scroller_id: Cell::new(None),
335 visual_viewport_state: Cell::new(None),
336 }
337 }
338
339 pub fn register_root_expanded_node(&self, root: &Rc<ExpandedNode>) {
341 *borrow_mut!(self.root_expanded_node) = Rc::downgrade(root);
342 }
343
344 pub fn clear_root_expanded_node(&self) {
346 *borrow_mut!(self.root_expanded_node) = Weak::new();
347 }
348
349 pub fn add_to_cache(&self, node: &Rc<ExpandedNode>) {
351 borrow_mut!(self.node_cache).add_to_cache(node);
352 self.register_node_lifecycle_handlers(node);
353 if node.is_import_settings_node() {
354 self.import_settings_node_count
355 .set(self.import_settings_node_count.get() + 1);
356 }
357 self.mark_occlusion_dirty();
358 }
359
360 pub fn remove_from_cache(&self, node: &Rc<ExpandedNode>) {
362 borrow_mut!(self.node_cache).remove_from_cache(node);
363 borrow_mut!(self.canvas_node_light_masks).remove(&node.id);
364 borrow_mut!(self.active_touch_targets).retain(|_, target| *target != node.id);
365 self.unregister_node_lifecycle_handlers(node.id);
366 if node.is_import_settings_node() {
367 self.import_settings_node_count
368 .set(self.import_settings_node_count.get().saturating_sub(1));
369 }
370 self.mark_occlusion_dirty();
371 }
372
373 pub fn has_import_settings_nodes(&self) -> bool {
374 self.import_settings_node_count.get() > 0
375 }
376
377 pub fn register_node_effect_property(&self, node: ExpandedNodeIdentifier, prop: &Property<()>) {
378 self.register_node_effect_property_named(node, prop, "node effect");
379 }
380
381 pub fn register_node_effect_property_named(
382 &self,
383 node: ExpandedNodeIdentifier,
384 prop: &Property<()>,
385 effect_name: &str,
386 ) {
387 if effect_drain_instrumentation_enabled() {
388 let debug_name = self.node_effect_debug_name(node, effect_name);
389 register_effect_property_with_name(prop, &debug_name);
390 } else {
391 register_effect_property(prop);
392 }
393 }
394
395 pub fn register_expanded_node_effect_property_named(
396 &self,
397 node: &Rc<ExpandedNode>,
398 prop: &Property<()>,
399 effect_name: &str,
400 ) {
401 if effect_drain_instrumentation_enabled() {
402 let debug_name = Self::expanded_node_effect_debug_name(node, effect_name);
403 register_effect_property_with_name(prop, &debug_name);
404 } else {
405 register_effect_property(prop);
406 }
407 }
408
409 pub fn register_node_effect(
410 &self,
411 node: ExpandedNodeIdentifier,
412 dependencies: &[UntypedProperty],
413 effect: impl Fn() + 'static,
414 ) -> Property<()> {
415 let prop = Property::computed(effect, dependencies);
416 self.register_node_effect_property_named(node, &prop, "registered node effect");
417 prop
418 }
419
420 pub fn drain_node_effects(&self) {
421 const MAX_NODE_EFFECTS_PER_TICK: usize = 100_000;
422 if !effect_drain_instrumentation_enabled() {
423 let drained = drain_effects(MAX_NODE_EFFECTS_PER_TICK);
424 if drained == MAX_NODE_EFFECTS_PER_TICK {
425 log::warn!(
426 "reactive drain hit {} evaluations in one tick; deferring remaining work",
427 MAX_NODE_EFFECTS_PER_TICK
428 );
429 }
430 return;
431 }
432
433 let start = Instant::now();
434 let report = drain_effects_with_report(MAX_NODE_EFFECTS_PER_TICK);
435 let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
436 if report.budget_exhausted {
437 log::warn!(
438 "reactive drain hit {} evaluations in one tick; deferring remaining work",
439 MAX_NODE_EFFECTS_PER_TICK
440 );
441 }
442 if report.popped > 0 || report.cutoffs_evaluated > 0 {
443 let globals = self.globals();
444 let frame_dependents = summarize_debug_names(property_outbound_debug_names(
445 &globals.elapsed_frames.untyped(),
446 ));
447 let millis_dependents = summarize_debug_names(property_outbound_debug_names(
448 &globals.elapsed_millis.untyped(),
449 ));
450 let frame_clock = globals.elapsed_frames.untyped();
451 let millis_clock = globals.elapsed_millis.untyped();
452 let (entering, exiting, enter_cleanup, exit_cleanup, stale_clock_nodes) = {
453 let nodes = borrow!(self.node_cache);
454 nodes.eid_to_node.values().fold(
455 (0, 0, 0, 0, Vec::new()),
456 |(entering, exiting, enter_cleanup, exit_cleanup, mut stale_clock_nodes),
457 node| {
458 let phase = node.transition_phase.get();
459 if phase != TRANSITION_PHASE_ENTER
460 && phase != TRANSITION_PHASE_EXIT
461 && (property_has_direct_outbound(
462 &frame_clock,
463 &node.transition_playhead.untyped(),
464 ) || property_has_direct_outbound(
465 &millis_clock,
466 &node.transition_playhead_millis.untyped(),
467 ))
468 {
469 stale_clock_nodes
470 .push(Self::expanded_node_effect_debug_name(node, "idle clock"));
471 }
472 (
473 entering + usize::from(phase == TRANSITION_PHASE_ENTER),
474 exiting + usize::from(phase == TRANSITION_PHASE_EXIT),
475 enter_cleanup + usize::from(node.enter_cleanup_active.get()),
476 exit_cleanup + usize::from(node.exit_cleanup_active.get()),
477 stale_clock_nodes,
478 )
479 },
480 )
481 };
482 println!(
483 "[PaxEffectDrain] ran={} popped={} clean={} unregistered={} missing={} remaining={} cutoffs={} suppressed={} propagated={} remaining_cutoffs={} elapsed_ms={:.3} transitions=enter:{}/exit:{}/enter_cleanup:{}/exit_cleanup:{} stale_clocks=[{}] frame_deps=[{}] millis_deps=[{}] effects=[{}] cutoff_nodes=[{}]",
484 report.ran,
485 report.popped,
486 report.skipped_clean,
487 report.skipped_unregistered,
488 report.skipped_missing,
489 report.remaining,
490 report.cutoffs_evaluated,
491 report.cutoffs_suppressed,
492 report.cutoffs_propagated,
493 report.remaining_cutoffs,
494 elapsed_ms,
495 entering,
496 exiting,
497 enter_cleanup,
498 exit_cleanup,
499 stale_clock_nodes.join("; "),
500 format_effect_top(&frame_dependents),
501 format_effect_top(&millis_dependents),
502 format_effect_top(&report.top_effects),
503 format_effect_top(&report.top_cutoffs),
504 );
505 }
506 }
507
508 fn node_effect_debug_name(&self, node: ExpandedNodeIdentifier, effect_name: &str) -> String {
509 let node = borrow!(self.node_cache).eid_to_node.get(&node).cloned();
510 if let Some(node) = node {
511 Self::expanded_node_effect_debug_name(&node, effect_name)
512 } else {
513 format!("{effect_name} node={node:?}")
514 }
515 }
516
517 fn expanded_node_effect_debug_name(node: &Rc<ExpandedNode>, effect_name: &str) -> String {
518 let instance_node = borrow!(node.instance_node);
519 let base = instance_node.base();
520 let type_name = base
521 .template_node_type_id
522 .as_ref()
523 .map(|type_id| {
524 type_id
525 .get_pascal_identifier()
526 .unwrap_or_else(|| type_id.to_string())
527 })
528 .unwrap_or_else(|| "<unknown>".to_owned());
529
530 format!("{effect_name} type={type_name}")
531 }
532
533 pub fn tick_handler_nodes(&self) -> Vec<ExpandedNodeIdentifier> {
534 borrow!(self.tick_handler_nodes).clone()
535 }
536
537 pub fn pre_render_handler_nodes(&self) -> Vec<ExpandedNodeIdentifier> {
538 borrow!(self.pre_render_handler_nodes).clone()
539 }
540
541 pub fn gyro_handler_nodes(&self) -> Vec<ExpandedNodeIdentifier> {
542 borrow!(self.gyro_handler_nodes).clone()
543 }
544
545 pub fn accel_handler_nodes(&self) -> Vec<ExpandedNodeIdentifier> {
546 borrow!(self.accel_handler_nodes).clone()
547 }
548
549 fn register_node_lifecycle_handlers(&self, node: &Rc<ExpandedNode>) {
550 let registry = borrow!(node.instance_node)
551 .base()
552 .handler_registry
553 .as_ref()
554 .map(Rc::clone);
555 let Some(registry) = registry else {
556 return;
557 };
558 let handlers = &borrow!(registry).handlers;
559 if handlers.get(TICK_HANDLERS).is_some_and(|h| !h.is_empty()) {
560 self.register_lifecycle_handler(&self.tick_handler_nodes, node.id);
561 }
562 if handlers
563 .get(PRE_RENDER_HANDLERS)
564 .is_some_and(|h| !h.is_empty())
565 {
566 self.register_lifecycle_handler(&self.pre_render_handler_nodes, node.id);
567 }
568 if handlers.get(GYRO_HANDLERS).is_some_and(|h| !h.is_empty()) {
569 self.register_lifecycle_handler(&self.gyro_handler_nodes, node.id);
570 }
571 if handlers.get(ACCEL_HANDLERS).is_some_and(|h| !h.is_empty()) {
572 self.register_lifecycle_handler(&self.accel_handler_nodes, node.id);
573 }
574 }
575
576 fn unregister_node_lifecycle_handlers(&self, id: ExpandedNodeIdentifier) {
577 borrow_mut!(self.tick_handler_nodes).retain(|node_id| *node_id != id);
578 borrow_mut!(self.pre_render_handler_nodes).retain(|node_id| *node_id != id);
579 borrow_mut!(self.gyro_handler_nodes).retain(|node_id| *node_id != id);
580 borrow_mut!(self.accel_handler_nodes).retain(|node_id| *node_id != id);
581 }
582
583 fn register_lifecycle_handler(
584 &self,
585 handler_nodes: &RefCell<Vec<ExpandedNodeIdentifier>>,
586 id: ExpandedNodeIdentifier,
587 ) {
588 let mut handler_nodes = borrow_mut!(handler_nodes);
589 if !handler_nodes.contains(&id) {
590 handler_nodes.push(id);
591 }
592 }
593
594 pub fn get_expanded_node_by_eid(&self, id: ExpandedNodeIdentifier) -> Option<Rc<ExpandedNode>> {
596 borrow!(self.node_cache).eid_to_node.get(&id).cloned()
597 }
598
599 pub fn capture_touch_target(&self, identifier: i64, target: ExpandedNodeIdentifier) {
601 borrow_mut!(self.active_touch_targets).insert(identifier, target);
602 }
603
604 pub fn captured_touch_target(&self, identifier: i64) -> Option<Rc<ExpandedNode>> {
606 let target = borrow!(self.active_touch_targets)
607 .get(&identifier)
608 .copied()?;
609 self.get_expanded_node_by_eid(target)
610 }
611
612 pub fn release_touch_target(&self, identifier: i64) -> Option<Rc<ExpandedNode>> {
614 let target = borrow_mut!(self.active_touch_targets).remove(&identifier)?;
615 self.get_expanded_node_by_eid(target)
616 }
617
618 pub fn load_screenshot(&self, id: u32, data: ScreenshotData) -> bool {
620 borrow_mut!(self.screenshot_map).insert(id, data);
621 true
622 }
623
624 pub fn get_screenshot_map(&self) -> Rc<RefCell<HashMap<u32, ScreenshotData>>> {
626 Rc::clone(&self.screenshot_map)
627 }
628
629 pub fn set_scroller_surface_state(
631 &self,
632 id: u32,
633 state: ScrollerSurfaceState,
634 ) -> ScrollerSurfaceStateChange {
635 let change = {
636 let mut states = borrow_mut!(self.scroller_surface_states);
637 let change = match states.get(&id) {
638 Some(previous) if previous.structural_eq(&state) && previous.scroll_eq(&state) => {
639 ScrollerSurfaceStateChange::Unchanged
640 }
641 Some(previous) if previous.structural_eq(&state) => {
642 ScrollerSurfaceStateChange::ScrollOnly
643 }
644 _ => ScrollerSurfaceStateChange::Structural,
645 };
646 if change != ScrollerSurfaceStateChange::Unchanged {
647 states.insert(id, state);
648 }
649 change
650 };
651
652 match change {
653 ScrollerSurfaceStateChange::Unchanged => {}
654 ScrollerSurfaceStateChange::ScrollOnly => {
655 self.mark_layer_canvas_plans_dirty();
656 self.mark_scroller_content_layers_dirty(id);
657 }
658 ScrollerSurfaceStateChange::Structural => {
659 self.mark_layer_canvas_plans_dirty();
660 self.mark_occlusion_dirty();
661 }
662 }
663
664 change
665 }
666
667 pub fn update_scroller_surface_scroll(
670 &self,
671 id: u32,
672 scroll_x: f64,
673 scroll_y: f64,
674 presentation_scroll_x: f64,
675 presentation_scroll_y: f64,
676 ) -> ScrollerSurfaceStateChange {
677 let change = {
678 let mut states = borrow_mut!(self.scroller_surface_states);
679 let Some(state) = states.get_mut(&id) else {
680 return ScrollerSurfaceStateChange::Unchanged;
681 };
682 let next = ScrollerSurfaceState {
683 scroll_x,
684 scroll_y,
685 presentation_scroll_x,
686 presentation_scroll_y,
687 ..*state
688 };
689 let change = if state.scroll_eq(&next) {
690 ScrollerSurfaceStateChange::Unchanged
691 } else {
692 ScrollerSurfaceStateChange::ScrollOnly
693 };
694 if change != ScrollerSurfaceStateChange::Unchanged {
695 *state = next;
696 }
697 change
698 };
699
700 if change == ScrollerSurfaceStateChange::ScrollOnly {
701 self.mark_layer_canvas_plans_dirty();
702 }
703
704 change
705 }
706
707 pub fn remove_scroller_surface_state(&self, id: u32) {
709 borrow_mut!(self.scroller_surface_states).remove(&id);
710 self.mark_layer_canvas_plans_dirty();
711 self.mark_occlusion_dirty();
712 }
713
714 pub fn get_scroller_surface_state(&self, id: u32) -> Option<ScrollerSurfaceState> {
716 borrow!(self.scroller_surface_states).get(&id).cloned()
717 }
718
719 pub fn get_scroller_surface_scroll(&self, id: u32) -> Option<(f64, f64)> {
722 borrow!(self.scroller_surface_states).get(&id).map(|state| {
723 let scroll_x = if state.presentation_scroll_x.is_finite() {
724 state.presentation_scroll_x
725 } else {
726 state.scroll_x
727 };
728 let scroll_y = if state.presentation_scroll_y.is_finite() {
729 state.presentation_scroll_y
730 } else {
731 state.scroll_y
732 };
733 (scroll_x, scroll_y)
734 })
735 }
736
737 fn scroller_content_presentation_transform(&self, node: &ExpandedNode) -> Affine {
738 fn clamp_offset(value: f64, content: f64, viewport: f64) -> f64 {
739 if content <= viewport || !value.is_finite() {
740 return 0.0;
741 }
742 value.max(0.0).min((content - viewport).max(0.0))
743 }
744
745 let root_delegates_to_page_scroll = self.get_root_scroller_id() == Some(node.id.to_u32())
746 && self.get_visual_viewport_state().is_some();
747 if root_delegates_to_page_scroll {
748 return Affine::IDENTITY;
749 }
750
751 let instance_node = borrow!(node.instance_node);
752 let (scroll_x, scroll_y) = if self.get_root_scroller_id() == Some(node.id.to_u32()) {
753 if let Some(visual) = self.get_visual_viewport_state() {
754 let visual_x = visual.page_scroll_x + visual.offset_x;
755 let visual_y = visual.page_scroll_y + visual.offset_y;
756 if visual_x.is_finite() && visual_y.is_finite() {
757 if let Some(state) = self.get_scroller_surface_state(node.id.to_u32()) {
758 let viewport_width = if visual.width.is_finite() {
759 visual.width
760 } else {
761 state.viewport_width
762 };
763 let viewport_height = if visual.height.is_finite() {
764 visual.height
765 } else {
766 state.viewport_height
767 };
768 (
769 clamp_offset(visual_x, state.content_width, viewport_width),
770 clamp_offset(visual_y, state.content_height, viewport_height),
771 )
772 } else {
773 (visual_x, visual_y)
774 }
775 } else {
776 self.get_scroller_surface_scroll(node.id.to_u32())
777 .or_else(|| instance_node.resolve_scroll_offset(node))
778 .unwrap_or((0.0, 0.0))
779 }
780 } else {
781 self.get_scroller_surface_scroll(node.id.to_u32())
782 .or_else(|| instance_node.resolve_scroll_offset(node))
783 .unwrap_or((0.0, 0.0))
784 }
785 } else {
786 self.get_scroller_surface_scroll(node.id.to_u32())
787 .or_else(|| instance_node.resolve_scroll_offset(node))
788 .unwrap_or((0.0, 0.0))
789 };
790
791 if scroll_x.abs() <= f64::EPSILON && scroll_y.abs() <= f64::EPSILON {
792 return Affine::IDENTITY;
793 }
794
795 let world_transform = Affine::from(node.transform_and_bounds.get().transform);
796 let inverse_world = Affine::from(node.transform_and_bounds.get().transform.inverse());
797 world_transform * Affine::translate((-scroll_x, -scroll_y)) * inverse_world
798 }
799
800 pub(crate) fn presentation_scroll_transform_for_node(&self, node: &ExpandedNode) -> Affine {
804 let mut ancestors = Vec::new();
805 let mut current = node.render_parent_node();
806 while let Some(parent) = current {
807 current = parent.render_parent_node();
808 ancestors.push(parent);
809 }
810 ancestors.reverse();
811
812 ancestors
813 .into_iter()
814 .filter(|ancestor| borrow!(ancestor.instance_node).scrolls_content(ancestor))
815 .fold(Affine::IDENTITY, |transform, scroller| {
816 transform * self.scroller_content_presentation_transform(&scroller)
817 })
818 }
819
820 pub fn clear_layer_scroller_owners(&self) {
822 borrow_mut!(self.layer_scroller_owners).clear();
823 self.mark_layer_canvas_plans_dirty();
824 }
825
826 pub fn register_layer_scroller_owner(
828 &self,
829 layer_id: usize,
830 scroller_id: ExpandedNodeIdentifier,
831 ) {
832 borrow_mut!(self.layer_scroller_owners).insert(layer_id, scroller_id);
833 self.mark_layer_canvas_plans_dirty();
834 }
835
836 pub fn get_layer_scroller_owner(&self, layer_id: usize) -> Option<ExpandedNodeIdentifier> {
838 borrow!(self.layer_scroller_owners).get(&layer_id).copied()
839 }
840
841 fn mark_scroller_content_layers_dirty(&self, scroller_id: u32) {
842 let layers = borrow!(self.layer_scroller_owners)
843 .iter()
844 .filter_map(|(layer, owner)| (owner.to_u32() == scroller_id).then_some(*layer))
845 .collect::<Vec<_>>();
846 for layer in layers {
847 self.set_canvas_dirty(layer);
848 }
849 }
850
851 pub fn set_root_scroller_id(&self, id: Option<u32>) {
853 self.root_scroller_id.set(id);
854 self.mark_layer_canvas_plans_dirty();
855 self.mark_occlusion_dirty();
856 }
857
858 pub fn get_root_scroller_id(&self) -> Option<u32> {
860 self.root_scroller_id.get()
861 }
862
863 pub fn set_visual_viewport_state(&self, state: VisualViewportState) {
865 self.visual_viewport_state.set(Some(state));
866 self.mark_layer_canvas_plans_dirty();
867 self.mark_occlusion_dirty();
868 }
869
870 pub fn clear_visual_viewport_state(&self) {
872 self.visual_viewport_state.set(None);
873 self.mark_layer_canvas_plans_dirty();
874 self.mark_occlusion_dirty();
875 }
876
877 pub fn get_visual_viewport_state(&self) -> Option<VisualViewportState> {
879 self.visual_viewport_state.get()
880 }
881
882 pub fn resize_canvas_layers_to(&self, id: usize) {
884 let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
885 let old_len = dirty_canvases.len();
886 dirty_canvases.resize(id, false);
887 for dirty in dirty_canvases.iter_mut().skip(old_len) {
888 *dirty = true;
889 }
890 if old_len != id {
891 self.mark_layer_canvas_plans_dirty();
892 }
893 }
894
895 pub fn mark_layer_canvas_plans_dirty(&self) {
896 let next = self.layer_canvas_plan_generation.get().wrapping_add(1);
897 self.layer_canvas_plan_generation.set(next.max(1));
898 }
899
900 pub fn layer_canvas_plan_generation(&self) -> u32 {
901 self.layer_canvas_plan_generation.get()
902 }
903
904 pub fn set_canvas_drawable_layers(&self, layers: HashSet<usize>) {
906 let mut current = borrow_mut!(self.canvas_drawable_layers);
907 if *current == layers {
908 return;
909 }
910 *current = layers;
911 self.mark_layer_canvas_plans_dirty();
912 }
913
914 pub fn layer_has_canvas_drawables(&self, layer: usize) -> bool {
916 borrow!(self.canvas_drawable_layers).contains(&layer)
917 }
918
919 pub fn clear_all_dirty_canvases(&self) {
921 let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
922 for v in dirty_canvases.iter_mut() {
923 *v = false;
924 }
925 }
926
927 pub fn set_canvas_dirty(&self, id: usize) {
929 let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
930 if let Some(v) = dirty_canvases.get_mut(id) {
931 *v = true;
932 }
933 }
934
935 pub fn is_canvas_dirty(&self, id: &usize) -> bool {
937 *borrow!(self.dirty_canvases).get(*id).unwrap_or(&true)
938 }
939
940 pub fn dirty_canvas_layers(&self) -> Vec<usize> {
941 borrow!(self.dirty_canvases)
942 .iter()
943 .enumerate()
944 .filter_map(|(index, dirty)| dirty.then_some(index))
945 .collect()
946 }
947
948 pub fn has_dirty_canvas_layers(&self) -> bool {
949 borrow!(self.dirty_canvases).iter().any(|dirty| *dirty)
950 }
951
952 pub fn has_dirty_canvas_nodes(&self) -> bool {
953 !borrow!(self.dirty_canvas_nodes).is_empty()
954 }
955
956 pub fn has_canvas_node_removals(&self) -> bool {
957 !borrow!(self.removed_canvas_nodes).is_empty()
958 }
959
960 pub fn has_canvas_render_work(&self) -> bool {
961 self.has_dirty_canvas_layers() || self.has_canvas_node_removals()
962 }
963
964 pub fn mark_occlusion_dirty(&self) {
965 self.occlusion_dirty.set(true);
966 }
967
968 pub fn take_occlusion_dirty(&self) -> bool {
969 let dirty = self.occlusion_dirty.get();
970 self.occlusion_dirty.set(false);
971 dirty
972 }
973
974 pub fn set_all_canvases_dirty(&self) {
975 let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
976 for v in dirty_canvases.iter_mut() {
977 *v = true;
978 }
979 }
980
981 pub fn mark_canvas_node_dirty(&self, id: ExpandedNodeIdentifier) {
982 borrow_mut!(self.dirty_canvas_nodes).insert(id);
983 }
984
985 pub fn clear_canvas_node_dirty(&self, id: &ExpandedNodeIdentifier) {
986 borrow_mut!(self.dirty_canvas_nodes).remove(id);
987 }
988
989 pub fn is_canvas_node_dirty(&self, id: &ExpandedNodeIdentifier) -> bool {
990 borrow!(self.dirty_canvas_nodes).contains(id)
991 }
992
993 pub fn dirty_canvas_node_ids(&self) -> Vec<ExpandedNodeIdentifier> {
994 borrow!(self.dirty_canvas_nodes).iter().copied().collect()
995 }
996
997 pub fn mark_canvas_nodes_on_layer_dirty(&self, layer: usize) {
998 let node_cache = borrow!(self.node_cache);
999 let dirty_nodes = &mut *borrow_mut!(self.dirty_canvas_nodes);
1000 for node in node_cache.eid_to_node.values() {
1001 if node.occlusion.get().render_layer_id == layer
1002 && borrow!(node.instance_node).base().flags().layer == crate::api::Layer::Canvas
1003 {
1004 dirty_nodes.insert(node.id);
1005 }
1006 }
1007 }
1008
1009 pub fn canvas_node_light_mask(&self, id: ExpandedNodeIdentifier) -> u32 {
1011 borrow!(self.canvas_node_light_masks)
1012 .get(&id)
1013 .copied()
1014 .unwrap_or(0)
1015 }
1016
1017 pub fn collect_scene_lighting_for_layer(&self, layer: usize) -> SceneLighting {
1018 let layer_to_root_transforms = self.layer_to_root_transforms(layer);
1019 let root_to_target = layer_to_root_transforms[&layer].inverse();
1020 let node_cache = borrow!(self.node_cache);
1021 let mut scoped_lights = Vec::new();
1022 let mut topmost_ambient = None;
1023
1024 for node in node_cache.eid_to_node.values() {
1025 let occlusion = node.occlusion.get();
1026 let source_layer = occlusion.render_layer_id;
1027 let Some(source_to_root) = layer_to_root_transforms.get(&source_layer) else {
1028 continue;
1029 };
1030
1031 let (light, ambient) = {
1032 let instance_node = borrow!(node.instance_node);
1033 (
1034 instance_node.resolve_scene_light(node, self),
1035 instance_node.resolve_scene_ambient_light(node, self),
1036 )
1037 };
1038 if let Some(light) = light {
1039 if light.enabled {
1040 scoped_lights.push((
1041 occlusion.z_index,
1042 node.id,
1043 Self::nearest_light_frame(node),
1044 transform_scene_light_for_layer(light, *source_to_root, root_to_target),
1045 ));
1046 }
1047 }
1048 if let Some(ambient) = ambient {
1049 let should_replace = topmost_ambient
1050 .as_ref()
1051 .map(|(z_index, id, _)| (occlusion.z_index, node.id) >= (*z_index, *id))
1052 .unwrap_or(true);
1053 if should_replace {
1054 topmost_ambient = Some((occlusion.z_index, node.id, ambient));
1055 }
1056 }
1057 }
1058
1059 scoped_lights
1060 .sort_by(|left, right| right.0.cmp(&left.0).then_with(|| left.1.cmp(&right.1)));
1061 let overflow = scoped_lights
1062 .len()
1063 .saturating_sub(SceneLighting::MAX_LIGHTS);
1064 scoped_lights.truncate(SceneLighting::MAX_LIGHTS);
1065
1066 {
1067 let mut overflow_counts = borrow_mut!(self.lighting_overflow_counts);
1068 if overflow == 0 {
1069 overflow_counts.remove(&layer);
1070 } else if overflow_counts.insert(layer, overflow) != Some(overflow) {
1071 log::warn!(
1072 "canvas layer {layer} has more than {} enabled lights; omitting {overflow}",
1073 SceneLighting::MAX_LIGHTS
1074 );
1075 }
1076 }
1077
1078 let mut resolved_masks = Vec::new();
1079 for node in node_cache.eid_to_node.values() {
1080 if node.occlusion.get().render_layer_id != layer
1081 || borrow!(node.instance_node).base().flags().layer != crate::api::Layer::Canvas
1082 {
1083 continue;
1084 }
1085
1086 let frame_ancestry = Self::light_frame_ancestry(node);
1087 let mask = scoped_lights.iter().enumerate().fold(
1088 0u32,
1089 |mask, (slot, (_, _, owner_frame, _))| {
1090 if light_reaches_frame_ancestry(owner_frame.as_ref(), &frame_ancestry) {
1091 mask | (1u32 << slot)
1092 } else {
1093 mask
1094 }
1095 },
1096 );
1097 resolved_masks.push((node.id, mask));
1098 }
1099 drop(node_cache);
1100
1101 let mut changed_nodes = Vec::new();
1102 {
1103 let mut masks = borrow_mut!(self.canvas_node_light_masks);
1104 for (node_id, mask) in resolved_masks {
1105 if masks.insert(node_id, mask) != Some(mask) {
1106 changed_nodes.push(node_id);
1107 }
1108 }
1109 }
1110 borrow_mut!(self.dirty_canvas_nodes).extend(changed_nodes);
1111
1112 let lights = scoped_lights
1113 .into_iter()
1114 .map(|(_, _, _, light)| light)
1115 .collect::<Vec<_>>();
1116
1117 if let Some((_, _, ambient)) = topmost_ambient {
1118 SceneLighting {
1119 active: true,
1120 ambient_is_authored: true,
1121 ambient,
1122 lights,
1123 }
1124 } else {
1125 SceneLighting::with_default_ambient(lights)
1126 }
1127 }
1128
1129 fn nearest_light_frame(node: &ExpandedNode) -> Option<ExpandedNodeIdentifier> {
1130 let mut current = node.render_parent_node();
1131 while let Some(parent) = current {
1132 if borrow!(parent.instance_node).establishes_light_frame() {
1133 return Some(parent.id);
1134 }
1135 current = parent.render_parent_node();
1136 }
1137 None
1138 }
1139
1140 fn light_frame_ancestry(node: &ExpandedNode) -> HashSet<ExpandedNodeIdentifier> {
1141 let mut frames = HashSet::new();
1142 let mut current = node.render_parent_node();
1143 while let Some(parent) = current {
1144 if borrow!(parent.instance_node).establishes_light_frame() {
1145 frames.insert(parent.id);
1146 }
1147 current = parent.render_parent_node();
1148 }
1149 frames
1150 }
1151
1152 fn layer_to_root_transforms(&self, layer: usize) -> HashMap<usize, Affine> {
1153 let mut transforms = HashMap::new();
1154 transforms.insert(layer, self.layer_to_root_transform(layer));
1155
1156 let mut current_layer = layer;
1157 let mut visited = HashSet::new();
1158 while let Some(owner_id) = self.get_layer_scroller_owner(current_layer) {
1159 if !visited.insert(current_layer) {
1160 break;
1161 }
1162 let Some(owner_node) = self.get_expanded_node_by_eid(owner_id) else {
1163 break;
1164 };
1165 let owner_layer = owner_node.occlusion.get().render_layer_id;
1166 transforms
1167 .entry(owner_layer)
1168 .or_insert_with(|| self.layer_to_root_transform(owner_layer));
1169 if owner_layer == current_layer {
1170 break;
1171 }
1172 current_layer = owner_layer;
1173 }
1174
1175 transforms
1176 }
1177
1178 fn layer_to_root_transform(&self, layer: usize) -> Affine {
1179 let mut transform = Affine::IDENTITY;
1180 let mut current_layer = layer;
1181 let mut visited = HashSet::new();
1182
1183 while let Some(owner_id) = self.get_layer_scroller_owner(current_layer) {
1184 if !visited.insert(current_layer) {
1185 break;
1186 }
1187 let Some(owner_node) = self.get_expanded_node_by_eid(owner_id) else {
1188 break;
1189 };
1190 let owner_layer = owner_node.occlusion.get().render_layer_id;
1191 let owner_transform = self.canvas_surface_transform_for_node(&owner_node);
1192 let (scroll_x, scroll_y) = self
1193 .get_scroller_surface_scroll(owner_node.id.to_u32())
1194 .or_else(|| borrow!(owner_node.instance_node).resolve_scroll_offset(&owner_node))
1195 .unwrap_or((0.0, 0.0));
1196 transform = owner_transform * Affine::translate((-scroll_x, -scroll_y)) * transform;
1197
1198 if owner_layer == current_layer {
1199 break;
1200 }
1201 current_layer = owner_layer;
1202 }
1203
1204 transform
1205 }
1206
1207 fn canvas_surface_transform_for_node(&self, node: &ExpandedNode) -> Affine {
1208 let transform = Affine::from(node.transform_and_bounds.get().transform);
1209 let own_layer = node.occlusion.get().render_layer_id;
1210 let mut parent_frame_id = node.parent_frame.get();
1211 while let Some(current_parent_frame_id) = parent_frame_id {
1212 let Some(parent_frame) = self.get_expanded_node_by_eid(current_parent_frame_id) else {
1213 break;
1214 };
1215 if parent_frame.occlusion.get().render_layer_id != own_layer {
1216 return Affine::from(parent_frame.transform_and_bounds.get().transform.inverse())
1217 * transform;
1218 }
1219 parent_frame_id = parent_frame.parent_frame.get();
1220 }
1221 transform
1222 }
1223
1224 pub fn mark_canvas_nodes_on_layer_dirty_by_id(&self, layer: usize, node_ids: &[u32]) {
1225 let node_cache = borrow!(self.node_cache);
1226 let dirty_nodes = &mut *borrow_mut!(self.dirty_canvas_nodes);
1227 for node_id in node_ids {
1228 let id = ExpandedNodeIdentifier(*node_id);
1229 let Some(node) = node_cache.eid_to_node.get(&id) else {
1230 continue;
1231 };
1232 if node.occlusion.get().render_layer_id == layer
1233 && borrow!(node.instance_node).base().flags().layer == crate::api::Layer::Canvas
1234 {
1235 dirty_nodes.insert(id);
1236 }
1237 }
1238 }
1239
1240 pub fn mark_targeted_canvas_replay_nodes(&self, layer: usize, node_ids: &[u32]) {
1241 let mut targeted = borrow_mut!(self.targeted_canvas_replay_node_ids);
1242 let layer_nodes = targeted.entry(layer).or_default();
1243 layer_nodes.extend(node_ids.iter().copied());
1244 }
1245
1246 pub fn take_targeted_canvas_replay_node_ids(&self) -> HashMap<usize, HashSet<u32>> {
1247 let targeted = &mut *borrow_mut!(self.targeted_canvas_replay_node_ids);
1248 std::mem::take(targeted)
1249 }
1250
1251 pub fn mark_all_canvas_nodes_dirty(&self) {
1252 let node_cache = borrow!(self.node_cache);
1253 let dirty_nodes = &mut *borrow_mut!(self.dirty_canvas_nodes);
1254 for node in node_cache.eid_to_node.values() {
1255 if borrow!(node.instance_node).base().flags().layer == crate::api::Layer::Canvas {
1256 dirty_nodes.insert(node.id);
1257 }
1258 }
1259 }
1260
1261 pub fn enqueue_canvas_node_removal(&self, layer: usize, node_id: u32) {
1262 borrow_mut!(self.removed_canvas_nodes).push((layer, node_id));
1263 }
1264
1265 pub fn take_canvas_node_removals(&self) -> Vec<(usize, u32)> {
1266 let mut removals = borrow_mut!(self.removed_canvas_nodes);
1267 std::mem::take(&mut *removals)
1268 }
1269
1270 pub fn get_expanded_nodes_by_id(&self, id: &str) -> Vec<Rc<ExpandedNode>> {
1272 borrow!(self.node_cache)
1274 .eid_to_node
1275 .values()
1276 .filter(|val| {
1277 let common_props = val.get_common_properties();
1278 let common_props = borrow!(common_props);
1279 common_props.id.get().is_some_and(|i| i == id)
1280 })
1281 .cloned()
1282 .collect()
1283 }
1284
1285 pub fn get_expanded_nodes_by_global_ids(
1287 &self,
1288 uni: &UniqueTemplateNodeIdentifier,
1289 ) -> Vec<Rc<ExpandedNode>> {
1290 let node_cache = borrow!(self.node_cache);
1291 node_cache
1292 .uni_to_eid
1293 .get(uni)
1294 .map(|eids| {
1295 let mut nodes = vec![];
1296 for e in eids {
1297 if let Some(node) = node_cache.eid_to_node.get(e) {
1298 nodes.push(Rc::clone(node));
1299 } else {
1300 log::warn!(
1301 "failed to find node in engine for expanded node identifier: {:?}",
1302 e
1303 );
1304 }
1305 }
1306 nodes
1307 })
1308 .unwrap_or_default()
1309 }
1310
1311 pub fn get_elements_beneath_ray(
1316 &self,
1317 root: Option<Rc<ExpandedNode>>,
1318 ray: Point2<Window>,
1319 limit_one: bool,
1320 mut accum: Vec<Rc<ExpandedNode>>,
1321 hit_invisible: bool,
1322 ) -> Vec<Rc<ExpandedNode>> {
1323 let Some(root_node) = root.or_else(|| borrow!(self.root_expanded_node).upgrade()) else {
1329 return accum;
1330 };
1331 let mut to_process = vec![(root_node, false, Affine::IDENTITY, false)];
1332 while let Some((node, clipped, active_scroll_transform, retained_for_exit)) =
1333 to_process.pop()
1334 {
1335 node.compute_flattened_projected_children();
1337 if !hit_invisible
1338 && (retained_for_exit || node.transition_phase.get() == TRANSITION_PHASE_EXIT)
1339 {
1340 continue;
1341 }
1342 let (scroll_transform, clips_content) = {
1345 let instance_node = borrow!(node.instance_node);
1346 let scrolls_content = instance_node.scrolls_content(&node);
1347 let clips_content = instance_node.clips_content(&node);
1348 drop(instance_node);
1349 (
1350 if scrolls_content {
1351 self.scroller_content_presentation_transform(&node)
1352 } else {
1353 Affine::IDENTITY
1354 },
1355 clips_content,
1356 )
1357 };
1358 let descendant_scroll_transform = active_scroll_transform * scroll_transform;
1359 let hit = node.ray_cast_test(active_scroll_transform.inverse() * ray);
1360 if hit && !clipped {
1361 if hit_invisible
1362 || !borrow!(node.instance_node)
1363 .base()
1364 .flags()
1365 .invisible_to_raycasting
1366 {
1367 if limit_one {
1371 return vec![node];
1372 }
1373 accum.push(Rc::clone(&node));
1374 }
1375 }
1376 let clipped = clipped || (!hit && clips_content);
1377 let retained_children = borrow!(node.exiting_children).clone();
1378 to_process.extend(
1379 node.children
1380 .get()
1381 .iter()
1382 .cloned()
1383 .map(|v| {
1384 let retained_for_exit = retained_children
1385 .iter()
1386 .any(|retained| Rc::ptr_eq(retained, &v));
1387 let cp = v.get_common_properties();
1388 let unclippable = borrow!(cp).unclippable.get().unwrap_or(false);
1389 (
1390 v,
1391 clipped && !unclippable,
1392 descendant_scroll_transform,
1393 retained_for_exit,
1394 )
1395 })
1396 .rev(),
1397 )
1398 }
1399 accum
1400 }
1401 pub fn get_topmost_element_beneath_ray(
1403 self: &Rc<Self>,
1404 ray: Point2<Window>,
1405 ) -> Option<Rc<ExpandedNode>> {
1406 let res = self.get_elements_beneath_ray(None, ray, true, vec![], false);
1407 let Some(new_topmost) = res
1408 .into_iter()
1409 .next()
1410 .or_else(|| borrow!(self.root_expanded_node).upgrade())
1411 else {
1412 *borrow_mut!(self.last_topmost_element) = Weak::new();
1413 return None;
1414 };
1415
1416 let last_topmost = borrow!(self.last_topmost_element).upgrade();
1420 if Some(new_topmost.id) != last_topmost.as_ref().map(|n| n.id) {
1421 let (leaving, entering) = find_template_paths_to_common_ancestor(
1422 &last_topmost,
1423 &Some(Rc::clone(&new_topmost)),
1424 );
1425 for leave in leaving {
1426 leave.dispatch_mouse_out(Event::new(MouseOut {}), &self.globals(), self);
1427 }
1428 for enter in entering {
1429 enter.dispatch_mouse_over(Event::new(MouseOver {}), &self.globals(), self);
1430 }
1431 *borrow_mut!(self.last_topmost_element) = Rc::downgrade(&new_topmost);
1432 }
1433 Some(new_topmost)
1434 }
1435
1436 pub fn gen_uid(&self) -> ExpandedNodeIdentifier {
1437 let val = self.next_uid.get();
1438 let next_val = ExpandedNodeIdentifier(val.0 + 1);
1439 self.next_uid.set(next_val);
1440 val
1441 }
1442
1443 pub fn enqueue_native_message(&self, message: NativeMessage) {
1444 borrow_mut!(self.messages).push(message)
1445 }
1446
1447 pub fn take_native_messages(&self) -> Vec<NativeMessage> {
1448 let mut messages = borrow_mut!(self.messages);
1449 std::mem::take(&mut *messages)
1450 }
1451
1452 pub fn globals(&self) -> Globals {
1453 borrow!(self.globals).clone()
1454 }
1455
1456 pub fn edit_globals(&self, f: impl Fn(&mut Globals)) {
1457 let mut globals = borrow_mut!(self.globals);
1458 f(&mut globals);
1459 }
1460
1461 pub fn queue_custom_event(&self, source_expanded_node: Rc<ExpandedNode>, name: &'static str) {
1462 let mut queued_custom_events = borrow_mut!(self.queued_custom_events);
1463 queued_custom_events.push((source_expanded_node, name));
1464 }
1465
1466 pub fn flush_custom_events(self: &Rc<Self>) -> Result<(), String> {
1467 let mut queued_custom_event = borrow_mut!(self.queued_custom_events);
1468 let to_flush: Vec<_> = std::mem::take(queued_custom_event.as_mut());
1469 for (target, ident) in to_flush {
1470 target.dispatch_custom_event(ident, self)?;
1471 }
1472 Ok(())
1473 }
1474
1475 #[cfg(feature = "designtime")]
1476 pub fn get_userland_root_expanded_node(&self) -> Option<Rc<ExpandedNode>> {
1477 borrow!(self.userland_root_expanded_node).clone()
1478 }
1479
1480 #[cfg(feature = "designtime")]
1481 pub fn get_userland_root_instance_node(&self) -> Option<Rc<dyn InstanceNode>> {
1482 borrow!(self.userland_frame_instance_node).clone()
1483 }
1484
1485 #[cfg(feature = "designtime")]
1486 pub fn set_userland_root_expanded_node(&self, root: Option<Rc<ExpandedNode>>) {
1487 *borrow_mut!(self.userland_root_expanded_node) = root;
1488 }
1489
1490 #[cfg(feature = "designtime")]
1491 pub fn set_userland_root_instance_node(&self, node: Option<Rc<dyn InstanceNode>>) {
1492 *borrow_mut!(self.userland_frame_instance_node) = node;
1493 }
1494
1495 pub fn get_root_expanded_node(&self) -> Option<Rc<ExpandedNode>> {
1496 borrow!(self.root_expanded_node).upgrade()
1497 }
1498
1499 pub fn queue_render(&self, expanded_node: Rc<ExpandedNode>) {
1500 borrow_mut!(self.queued_renders).push(expanded_node);
1501 }
1502
1503 pub fn recurse_flush_queued_renders(self: &Rc<RuntimeContext>, rcs: &mut dyn RenderContext) {
1504 while !borrow!(self.queued_renders).is_empty() {
1505 for n in std::mem::take(&mut *borrow_mut!(self.queued_renders)) {
1506 n.recurse_render(self, rcs);
1507 }
1508 }
1509 }
1510}
1511
1512fn find_template_paths_to_common_ancestor(
1513 last_topmost: &Option<Rc<ExpandedNode>>,
1514 new_topmost: &Option<Rc<ExpandedNode>>,
1515) -> (Vec<Rc<ExpandedNode>>, Vec<Rc<ExpandedNode>>) {
1516 let mut last_path = template_path_from_root(last_topmost);
1517 let mut new_path = template_path_from_root(new_topmost);
1518 let common_prefix_len = last_path
1519 .iter()
1520 .zip(new_path.iter())
1521 .take_while(|(last, new)| last.id == new.id)
1522 .count();
1523
1524 last_path.drain(0..common_prefix_len);
1526 new_path.drain(0..common_prefix_len);
1527
1528 (last_path, new_path)
1529}
1530
1531fn template_path_from_root(node: &Option<Rc<ExpandedNode>>) -> Vec<Rc<ExpandedNode>> {
1532 let mut path = Vec::new();
1533 let mut current = node.clone();
1534 while let Some(node) = current {
1535 path.push(Rc::clone(&node));
1536 current = node.template_parent.upgrade();
1537 }
1538 path.reverse();
1539 path
1540}
1541
1542pub struct RuntimePropertiesStackFrame {
1551 symbols_within_frame: HashMap<String, Variable>,
1552 local_stores: Rc<RefCell<HashMap<TypeId, Box<dyn Any>>>>,
1553 parent: Option<Rc<RuntimePropertiesStackFrame>>,
1554}
1555
1556impl RuntimePropertiesStackFrame {
1557 pub fn new(symbols_within_frame: HashMap<String, Variable>) -> Rc<Self> {
1558 Rc::new(Self {
1559 symbols_within_frame,
1560 local_stores: Default::default(),
1561 parent: None,
1562 })
1563 }
1564
1565 pub fn push(self: &Rc<Self>, symbols_within_frame: HashMap<String, Variable>) -> Rc<Self> {
1566 Rc::new(RuntimePropertiesStackFrame {
1567 symbols_within_frame,
1568 local_stores: Default::default(),
1569 parent: Some(Rc::clone(self)),
1570 })
1571 }
1572
1573 pub fn pop(self: &Rc<Self>) -> Option<Rc<Self>> {
1574 self.parent.clone()
1575 }
1576
1577 pub fn insert_stack_local_store<T: Store>(&self, store: T) {
1578 let type_id = TypeId::of::<T>();
1579 borrow_mut!(self.local_stores).insert(type_id, Box::new(store));
1580 }
1581
1582 pub fn peek_stack_local_store<T: Store, V>(
1583 self: &Rc<Self>,
1584 f: impl FnOnce(&mut T) -> V,
1585 ) -> Result<V, String> {
1586 let mut current = Rc::clone(self);
1587 let type_id = TypeId::of::<T>();
1588
1589 while !borrow!(current.local_stores).contains_key(&type_id) {
1590 current = current
1591 .parent
1592 .clone()
1593 .ok_or_else(|| format!("couldn't find store in local stack"))?;
1594 }
1595 let v = {
1596 let mut stores = borrow_mut!(current.local_stores);
1597 let store = stores.get_mut(&type_id).unwrap().downcast_mut().unwrap();
1598 f(store)
1599 };
1600 Ok(v)
1601 }
1602
1603 pub fn resolve_symbol_as_variable(&self, symbol: &str) -> Option<Variable> {
1604 if let Some(e) = self.symbols_within_frame.get(&clean_symbol(symbol)) {
1605 Some(e.clone())
1606 } else {
1607 self.parent.as_ref()?.resolve_symbol_as_variable(symbol)
1608 }
1609 }
1610
1611 pub fn resolve_symbol_as_erased_property(&self, symbol: &str) -> Option<UntypedProperty> {
1612 if let Some(e) = self.symbols_within_frame.get(&clean_symbol(symbol)) {
1613 Some(e.clone().get_untyped_property().clone())
1614 } else {
1615 self.parent
1616 .as_ref()?
1617 .resolve_symbol_as_erased_property(symbol)
1618 }
1619 }
1620
1621 pub fn resolve_symbol(&self, symbol: &str) -> Option<Variable> {
1622 self.symbols_within_frame
1623 .get(&clean_symbol(symbol))
1624 .cloned()
1625 .or_else(|| self.parent.as_ref()?.resolve_symbol(symbol))
1626 }
1627}
1628
1629fn clean_symbol(symbol: &str) -> String {
1630 symbol.replace("self.", "").replace("this.", "")
1631}
1632
1633impl IdentifierResolver for RuntimePropertiesStackFrame {
1634 fn resolve(&self, name: String) -> Result<Variable, String> {
1635 self.resolve_symbol(&name)
1636 .ok_or_else(|| format!("Could not resolve symbol {}", name))
1637 }
1638}
1639
1640fn light_reaches_frame_ancestry(
1641 owner_frame: Option<&ExpandedNodeIdentifier>,
1642 frame_ancestry: &HashSet<ExpandedNodeIdentifier>,
1643) -> bool {
1644 owner_frame.is_none_or(|frame| frame_ancestry.contains(frame))
1645}
1646
1647pub struct ExpressionContext {
1651 pub stack_frame: Rc<RuntimePropertiesStackFrame>,
1652}
1653
1654#[cfg(test)]
1655mod light_scope_tests {
1656 use super::*;
1657 use crate::api::math::Transform2;
1658 use crate::{
1659 BaseInstance, CommonPropertiesInit, ComponentInstance, InstanceFlags, InstanceNode,
1660 InstantiationArgs, PropertiesInit, PropertiesScopeInit, RouteLocation, TransformAndBounds,
1661 };
1662 use pax_runtime_api::pax_value::{PaxAny, PaxValue};
1663 use pax_runtime_api::{Layer, Platform, SceneAmbientLight, TargetInfo, OS};
1664 use std::fmt;
1665
1666 #[derive(Clone, Copy)]
1667 enum TestLightingRole {
1668 Frame,
1669 Light(f64),
1670 Ambient(f64),
1671 Surface,
1672 }
1673
1674 struct TestLightingNode {
1675 base: BaseInstance,
1676 role: TestLightingRole,
1677 enabled: Cell<bool>,
1678 }
1679
1680 impl TestLightingNode {
1681 fn new(
1682 role: TestLightingRole,
1683 children: Vec<Rc<dyn InstanceNode>>,
1684 ) -> Rc<TestLightingNode> {
1685 Rc::new(Self {
1686 base: BaseInstance::new(
1687 node_args(children),
1688 InstanceFlags {
1689 invisible_to_slot: false,
1690 invisible_to_raycasting: true,
1691 layer: match role {
1692 TestLightingRole::Frame => Layer::DontCare,
1693 TestLightingRole::Light(_)
1694 | TestLightingRole::Ambient(_)
1695 | TestLightingRole::Surface => Layer::Canvas,
1696 },
1697 is_component: false,
1698 is_slot: false,
1699 },
1700 ),
1701 role,
1702 enabled: Cell::new(true),
1703 })
1704 }
1705
1706 fn as_instance(self: &Rc<Self>) -> Rc<dyn InstanceNode> {
1707 self.clone()
1708 }
1709
1710 fn set_enabled(&self, enabled: bool) {
1711 self.enabled.set(enabled);
1712 }
1713 }
1714
1715 impl InstanceNode for TestLightingNode {
1716 fn instantiate(args: InstantiationArgs) -> Rc<Self>
1717 where
1718 Self: Sized,
1719 {
1720 Rc::new(Self {
1721 base: BaseInstance::new(
1722 args,
1723 InstanceFlags {
1724 invisible_to_slot: false,
1725 invisible_to_raycasting: true,
1726 layer: Layer::Canvas,
1727 is_component: false,
1728 is_slot: false,
1729 },
1730 ),
1731 role: TestLightingRole::Surface,
1732 enabled: Cell::new(true),
1733 })
1734 }
1735
1736 fn resolve_debug(
1737 &self,
1738 f: &mut fmt::Formatter,
1739 _expanded_node: Option<&ExpandedNode>,
1740 ) -> fmt::Result {
1741 f.debug_struct("TestLightingNode").finish()
1742 }
1743
1744 fn resolve_scene_light(
1745 &self,
1746 _expanded_node: &ExpandedNode,
1747 _context: &RuntimeContext,
1748 ) -> Option<SceneLight> {
1749 match self.role {
1750 TestLightingRole::Light(intensity) if self.enabled.get() => Some(SceneLight {
1751 intensity,
1752 ..Default::default()
1753 }),
1754 _ => None,
1755 }
1756 }
1757
1758 fn resolve_scene_ambient_light(
1759 &self,
1760 _expanded_node: &ExpandedNode,
1761 _context: &RuntimeContext,
1762 ) -> Option<SceneAmbientLight> {
1763 match self.role {
1764 TestLightingRole::Ambient(intensity) if self.enabled.get() => {
1765 Some(SceneAmbientLight {
1766 intensity,
1767 ..Default::default()
1768 })
1769 }
1770 _ => None,
1771 }
1772 }
1773
1774 fn establishes_light_frame(&self) -> bool {
1775 matches!(self.role, TestLightingRole::Frame)
1776 }
1777
1778 fn base(&self) -> &BaseInstance {
1779 &self.base
1780 }
1781 }
1782
1783 struct ScopedLightingFixture {
1784 context: Rc<RuntimeContext>,
1785 _root: Rc<ExpandedNode>,
1786 root_light: Rc<TestLightingNode>,
1787 outer_light: Rc<TestLightingNode>,
1788 ambient: Rc<TestLightingNode>,
1789 root_surface: Rc<ExpandedNode>,
1790 outer_surface: Rc<ExpandedNode>,
1791 nested_surface: Rc<ExpandedNode>,
1792 sibling_surface: Rc<ExpandedNode>,
1793 sibling_frame: Rc<ExpandedNode>,
1794 }
1795
1796 fn test_globals() -> Globals {
1797 Globals {
1798 elapsed_frames: Property::new(0),
1799 elapsed_millis: Property::new(0),
1800 viewport: Property::new(TransformAndBounds {
1801 transform: Transform2::identity(),
1802 bounds: (100.0, 100.0),
1803 }),
1804 gyro: Property::new(Default::default()),
1805 accel: Property::new(Default::default()),
1806 route_location: Property::new(RouteLocation::root()),
1807 browser_allows_scroller_vector_layers: Property::new(true),
1808 browser_allows_nested_scroller_vector_layers: Property::new(true),
1809 platform: Platform::Unknown,
1810 os: OS::Unknown,
1811 target: TargetInfo::new(Platform::Unknown, OS::Unknown),
1812 get_elapsed_millis: Rc::new(|| 0),
1813 }
1814 }
1815
1816 fn properties_factory() -> crate::PropertiesFactory {
1817 Box::new(|_, expanded_node| {
1818 expanded_node
1819 .is_none()
1820 .then(|| Rc::new(RefCell::new(PaxAny::Builtin(PaxValue::default()))))
1821 })
1822 }
1823
1824 fn node_args(children: Vec<Rc<dyn InstanceNode>>) -> InstantiationArgs {
1825 InstantiationArgs {
1826 prototypical_common_properties: CommonPropertiesInit::Default,
1827 prototypical_properties: PropertiesInit::Factory(properties_factory()),
1828 handler_registry: None,
1829 children: Some(RefCell::new(children)),
1830 component_template: None,
1831 component_settings: None,
1832 template_node_identifier: None,
1833 template_node_type_id: None,
1834 template_node_selector_info: None,
1835 transition_config: Default::default(),
1836 properties_scope: PropertiesScopeInit::None,
1837 }
1838 }
1839
1840 fn component_args(template: Vec<Rc<dyn InstanceNode>>) -> InstantiationArgs {
1841 InstantiationArgs {
1842 prototypical_common_properties: CommonPropertiesInit::Default,
1843 prototypical_properties: PropertiesInit::Factory(properties_factory()),
1844 handler_registry: None,
1845 children: None,
1846 component_template: Some(RefCell::new(template)),
1847 component_settings: None,
1848 template_node_identifier: None,
1849 template_node_type_id: None,
1850 template_node_selector_info: None,
1851 transition_config: Default::default(),
1852 properties_scope: PropertiesScopeInit::None,
1853 }
1854 }
1855
1856 fn mount_test_tree(
1857 children: Vec<Rc<dyn InstanceNode>>,
1858 ) -> (Rc<RuntimeContext>, Rc<ExpandedNode>) {
1859 let root_component = ComponentInstance::instantiate(component_args(children));
1860 let context = Rc::new(RuntimeContext::new(test_globals()));
1861 let root = ExpandedNode::initialize_root(root_component, &context);
1862 root.recurse_update(&context);
1863 crate::engine::occlusion::update_node_occlusion(&root, &context);
1864 (context, root)
1865 }
1866
1867 fn clear_dirty_canvas_nodes(context: &RuntimeContext) {
1868 for id in context.dirty_canvas_node_ids() {
1869 context.clear_canvas_node_dirty(&id);
1870 }
1871 }
1872
1873 fn scoped_lighting_fixture() -> ScopedLightingFixture {
1874 let nested_light = TestLightingNode::new(TestLightingRole::Light(3.0), Vec::new());
1875 let nested_surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
1876 let nested_frame = TestLightingNode::new(
1877 TestLightingRole::Frame,
1878 vec![nested_light.as_instance(), nested_surface.as_instance()],
1879 );
1880
1881 let sibling_surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
1882 let sibling_frame =
1883 TestLightingNode::new(TestLightingRole::Frame, vec![sibling_surface.as_instance()]);
1884
1885 let outer_light = TestLightingNode::new(TestLightingRole::Light(2.0), Vec::new());
1886 let outer_surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
1887 let outer_frame = TestLightingNode::new(
1888 TestLightingRole::Frame,
1889 vec![
1890 outer_light.as_instance(),
1891 outer_surface.as_instance(),
1892 nested_frame.as_instance(),
1893 sibling_frame.as_instance(),
1894 ],
1895 );
1896
1897 let root_light = TestLightingNode::new(TestLightingRole::Light(1.0), Vec::new());
1898 let root_surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
1899 let ambient = TestLightingNode::new(TestLightingRole::Ambient(0.72), Vec::new());
1900 ambient.set_enabled(false);
1901
1902 let (context, root) = mount_test_tree(vec![
1903 root_light.as_instance(),
1904 root_surface.as_instance(),
1905 outer_frame.as_instance(),
1906 ambient.as_instance(),
1907 ]);
1908
1909 let root_children = root.children.get();
1910 let root_surface_node = root_children[1].clone();
1911 let outer_frame_node = root_children[2].clone();
1912 let outer_children = outer_frame_node.children.get();
1913 let outer_surface_node = outer_children[1].clone();
1914 let nested_frame_node = outer_children[2].clone();
1915 let sibling_frame_node = outer_children[3].clone();
1916 let nested_children = nested_frame_node.children.get();
1917 let nested_surface_node = nested_children[1].clone();
1918 let sibling_surface_node = sibling_frame_node.children.get()[0].clone();
1919
1920 ScopedLightingFixture {
1921 context,
1922 _root: root,
1923 root_light,
1924 outer_light,
1925 ambient,
1926 root_surface: root_surface_node,
1927 outer_surface: outer_surface_node,
1928 nested_surface: nested_surface_node,
1929 sibling_surface: sibling_surface_node,
1930 sibling_frame: sibling_frame_node,
1931 }
1932 }
1933
1934 #[test]
1935 fn root_lights_reach_every_frame_ancestry() {
1936 let ancestry = HashSet::from([ExpandedNodeIdentifier(10), ExpandedNodeIdentifier(20)]);
1937 assert!(light_reaches_frame_ancestry(None, &ancestry));
1938 }
1939
1940 #[test]
1941 fn framed_lights_reach_only_descendants_of_their_owner_frame() {
1942 let outer_frame = ExpandedNodeIdentifier(10);
1943 let nested_frame = ExpandedNodeIdentifier(20);
1944 let sibling_frame = ExpandedNodeIdentifier(30);
1945 let nested_ancestry = HashSet::from([outer_frame, nested_frame]);
1946
1947 assert!(light_reaches_frame_ancestry(
1948 Some(&outer_frame),
1949 &nested_ancestry
1950 ));
1951 assert!(light_reaches_frame_ancestry(
1952 Some(&nested_frame),
1953 &nested_ancestry
1954 ));
1955 assert!(!light_reaches_frame_ancestry(
1956 Some(&sibling_frame),
1957 &nested_ancestry
1958 ));
1959 }
1960
1961 #[test]
1962 fn mounted_tree_resolves_root_nested_and_sibling_light_masks() {
1963 let fixture = scoped_lighting_fixture();
1964
1965 let lighting = fixture.context.collect_scene_lighting_for_layer(0);
1966
1967 assert_eq!(
1968 lighting
1969 .lights
1970 .iter()
1971 .map(|light| light.intensity)
1972 .collect::<Vec<_>>(),
1973 vec![1.0, 2.0, 3.0]
1974 );
1975 assert!(!lighting.ambient_is_authored);
1976 assert_eq!(
1977 fixture
1978 .context
1979 .canvas_node_light_mask(fixture.root_surface.id),
1980 0b001
1981 );
1982 assert_eq!(
1983 fixture
1984 .context
1985 .canvas_node_light_mask(fixture.outer_surface.id),
1986 0b011
1987 );
1988 assert_eq!(
1989 fixture
1990 .context
1991 .canvas_node_light_mask(fixture.nested_surface.id),
1992 0b111
1993 );
1994 assert_eq!(
1995 fixture
1996 .context
1997 .canvas_node_light_mask(fixture.sibling_surface.id),
1998 0b011
1999 );
2000 }
2001
2002 #[test]
2003 fn slot_reallocation_and_render_reparenting_refresh_retained_masks() {
2004 let fixture = scoped_lighting_fixture();
2005 fixture.context.collect_scene_lighting_for_layer(0);
2006 clear_dirty_canvas_nodes(&fixture.context);
2007
2008 fixture.outer_light.set_enabled(false);
2009 let lighting = fixture.context.collect_scene_lighting_for_layer(0);
2010
2011 assert_eq!(
2012 lighting
2013 .lights
2014 .iter()
2015 .map(|light| light.intensity)
2016 .collect::<Vec<_>>(),
2017 vec![1.0, 3.0]
2018 );
2019 assert_eq!(
2020 fixture
2021 .context
2022 .canvas_node_light_mask(fixture.root_surface.id),
2023 0b01
2024 );
2025 assert_eq!(
2026 fixture
2027 .context
2028 .canvas_node_light_mask(fixture.outer_surface.id),
2029 0b01
2030 );
2031 assert_eq!(
2032 fixture
2033 .context
2034 .canvas_node_light_mask(fixture.nested_surface.id),
2035 0b11
2036 );
2037 assert!(fixture
2038 .context
2039 .is_canvas_node_dirty(&fixture.outer_surface.id));
2040 assert!(fixture
2041 .context
2042 .is_canvas_node_dirty(&fixture.nested_surface.id));
2043 assert!(!fixture
2044 .context
2045 .is_canvas_node_dirty(&fixture.root_surface.id));
2046
2047 clear_dirty_canvas_nodes(&fixture.context);
2048 *borrow_mut!(fixture.nested_surface.render_parent) = Rc::downgrade(&fixture.sibling_frame);
2049 fixture.context.collect_scene_lighting_for_layer(0);
2050
2051 assert_eq!(
2052 fixture
2053 .context
2054 .canvas_node_light_mask(fixture.nested_surface.id),
2055 0b01
2056 );
2057 assert!(fixture
2058 .context
2059 .is_canvas_node_dirty(&fixture.nested_surface.id));
2060 }
2061
2062 #[test]
2063 fn default_and_authored_ambient_keep_empty_direct_masks_distinct() {
2064 let fixture = scoped_lighting_fixture();
2065 fixture.root_light.set_enabled(false);
2066
2067 let default_ambient = fixture.context.collect_scene_lighting_for_layer(0);
2068 assert!(default_ambient.active);
2069 assert!(!default_ambient.ambient_is_authored);
2070 assert_eq!(
2071 fixture
2072 .context
2073 .canvas_node_light_mask(fixture.root_surface.id),
2074 0
2075 );
2076 assert_eq!(
2077 default_ambient.ambient.intensity,
2078 SceneLighting::DEFAULT_AMBIENT_INTENSITY
2079 );
2080
2081 fixture.ambient.set_enabled(true);
2082 let authored_ambient = fixture.context.collect_scene_lighting_for_layer(0);
2083 assert!(authored_ambient.active);
2084 assert!(authored_ambient.ambient_is_authored);
2085 assert_eq!(authored_ambient.ambient.intensity, 0.72);
2086 assert_eq!(
2087 fixture
2088 .context
2089 .canvas_node_light_mask(fixture.root_surface.id),
2090 0
2091 );
2092 }
2093
2094 #[test]
2095 fn light_overflow_selection_is_deterministic_and_recovers() {
2096 let lights = (0..10)
2097 .map(|index| {
2098 TestLightingNode::new(TestLightingRole::Light((index + 1) as f64), Vec::new())
2099 })
2100 .collect::<Vec<_>>();
2101 let surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2102 let mut templates = lights
2103 .iter()
2104 .rev()
2105 .map(TestLightingNode::as_instance)
2106 .collect::<Vec<_>>();
2107 templates.push(surface.as_instance());
2108 let (context, root) = mount_test_tree(templates);
2109 let expanded = root.children.get();
2110 let surface_node = expanded[10].clone();
2111
2112 let first = context.collect_scene_lighting_for_layer(0);
2113 let second = context.collect_scene_lighting_for_layer(0);
2114 let expected = vec![10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0];
2115 assert_eq!(
2116 first
2117 .lights
2118 .iter()
2119 .map(|light| light.intensity)
2120 .collect::<Vec<_>>(),
2121 expected
2122 );
2123 assert_eq!(first.lights, second.lights);
2124 assert_eq!(context.canvas_node_light_mask(surface_node.id), 0xff);
2125 assert_eq!(borrow!(context.lighting_overflow_counts).get(&0), Some(&2));
2126
2127 lights[0].set_enabled(false);
2128 lights[1].set_enabled(false);
2129 let recovered = context.collect_scene_lighting_for_layer(0);
2130 assert_eq!(
2131 recovered
2132 .lights
2133 .iter()
2134 .map(|light| light.intensity)
2135 .collect::<Vec<_>>(),
2136 expected
2137 );
2138 assert!(!borrow!(context.lighting_overflow_counts).contains_key(&0));
2139 }
2140
2141 #[test]
2142 fn touch_capture_releases_and_clears_removed_targets() {
2143 let surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2144 let (context, root) = mount_test_tree(vec![surface.as_instance()]);
2145 let surface_node = root.children.get()[0].clone();
2146
2147 context.capture_touch_target(41, surface_node.id);
2148 assert!(Rc::ptr_eq(
2149 &context.captured_touch_target(41).unwrap(),
2150 &surface_node
2151 ));
2152 assert!(Rc::ptr_eq(
2153 &context.release_touch_target(41).unwrap(),
2154 &surface_node
2155 ));
2156 assert!(context.captured_touch_target(41).is_none());
2157
2158 context.capture_touch_target(42, surface_node.id);
2159 context.remove_from_cache(&surface_node);
2160 assert!(context.captured_touch_target(42).is_none());
2161 assert!(context.release_touch_target(42).is_none());
2162 }
2163}