Skip to main content

pax_runtime/engine/
expanded_node.rs

1use crate::api::TextInput;
2use crate::node_interface::NodeLocal;
3use pax_language::Computable;
4use pax_runtime_api::pax_value::{ImplToFromPaxAny, PaxAny, ToFromPaxAny};
5use pax_runtime_api::{
6    borrow, borrow_mut, use_RefCell, Focus, Interpolatable, Layer, NativeLiquidGlassScope,
7    PaxValue, Percent, Property, SelectStart, Variable,
8};
9
10use crate::api::math::Point2;
11use crate::constants::{
12    ACCEL_HANDLERS, BUTTON_CLICK_HANDLERS, CHECKBOX_CHANGE_HANDLERS, CLICK_HANDLERS,
13    CONTEXT_MENU_HANDLERS, DOUBLE_CLICK_HANDLERS, DROP_HANDLERS, FOCUSED_HANDLERS, GYRO_HANDLERS,
14    KEY_DOWN_HANDLERS, KEY_PRESS_HANDLERS, KEY_UP_HANDLERS, MOUSE_DOWN_HANDLERS,
15    MOUSE_MOVE_HANDLERS, MOUSE_OUT_HANDLERS, MOUSE_OVER_HANDLERS, MOUSE_UP_HANDLERS,
16    PHOTO_PICKER_CHANGE_HANDLERS, SCROLL_HANDLERS, SELECT_START_HANDLERS, SLIDER_CHANGE_HANDLERS,
17    TAP_HANDLERS, TEXTBOX_CHANGE_HANDLERS, TEXTBOX_INPUT_HANDLERS, TEXT_INPUT_HANDLERS,
18    TOUCH_CANCEL_HANDLERS, TOUCH_END_HANDLERS, TOUCH_MOVE_HANDLERS, TOUCH_START_HANDLERS,
19    WHEEL_HANDLERS,
20};
21use_RefCell!();
22use crate::cartridge::evaluate_timeline_duration;
23use crate::{ExpandedNodeIdentifier, Globals, LayoutHull, LayoutProperties, TransformAndBounds};
24use core::fmt;
25use std::cell::Cell;
26use std::collections::{BTreeMap, HashMap, HashSet};
27use std::rc::{Rc, Weak};
28
29// Keep the source handles alive, not the parent node. Equality of current
30// values is insufficient: a different source must establish a new subscription.
31#[derive(Clone)]
32struct ParentBindingSources {
33    parent: ExpandedNodeIdentifier,
34    properties: Vec<pax_runtime_api::properties::UntypedProperty>,
35}
36
37impl ParentBindingSources {
38    fn same_as(&self, other: &Self) -> bool {
39        self.parent == other.parent
40            && self.properties.len() == other.properties.len()
41            && self
42                .properties
43                .iter()
44                .zip(&other.properties)
45                .all(|(a, b)| a.get_id() == b.get_id())
46    }
47}
48
49#[derive(PartialEq)]
50struct ChildStructure {
51    binding_generation: u64,
52    rendered: Vec<ExpandedNodeIdentifier>,
53    active: Vec<ExpandedNodeIdentifier>,
54    exiting: Vec<ExpandedNodeIdentifier>,
55}
56
57use crate::api::{
58    Accel, Axis, ButtonClick, CheckboxChange, Click, CommonProperties, ContextMenu, DoubleClick,
59    Drop, Event, Gyro, KeyDown, KeyPress, KeyUp, LayoutRole, MouseDown, MouseMove, MouseOut,
60    MouseOver, MouseUp, NodeContext, PhotoPickerChange, RenderContext, Scroll, Size, SliderChange,
61    TextboxChange, TextboxInput, TouchCancel, TouchEnd, TouchMove, TouchStart, Wheel, Window,
62};
63use pax_manifest::cartridge_generation::{
64    TRANSITION_GENERATION_SYMBOL, TRANSITION_PHASE_ENTER, TRANSITION_PHASE_EXIT,
65    TRANSITION_PHASE_IDLE, TRANSITION_PHASE_SYMBOL, TRANSITION_PLAYHEAD_MILLIS_SYMBOL,
66    TRANSITION_PLAYHEAD_SYMBOL, TRANSITION_TAKEOVER_SYMBOL,
67};
68use pax_manifest::{
69    ExpressionInfo, LocationInfo, SelectorExpr, SettingsBlockElement, TypeId,
70    UniqueTemplateNodeIdentifier, ValueDefinition,
71};
72
73use crate::{
74    add_symmetric_padding_to_content_layout_hull, apply_container_frame, apply_padding_frame,
75    compute_tab, ComponentInstance, ContainerFrame, HandlerLocation, InstanceNode, InstanceNodePtr,
76    ReceivedChildrenSource, RuntimeContext, RuntimePropertiesStackFrame,
77};
78
79#[derive(Clone)]
80pub struct RuntimeSettingsLayer {
81    pub provider_id: ExpandedNodeIdentifier,
82    pub provider_type_id: TypeId,
83    /// The provider component's lexical scope. Imported settings expressions
84    /// must resolve `self` and provider properties here, even though their
85    /// resulting values are applied to a different node.
86    pub provider_stack: Rc<RuntimePropertiesStackFrame>,
87    pub settings: Vec<SettingsBlockElement>,
88}
89
90impl fmt::Debug for RuntimeSettingsLayer {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        f.debug_struct("RuntimeSettingsLayer")
93            .field("provider_id", &self.provider_id)
94            .field("provider_type_id", &self.provider_type_id)
95            .field("provider_stack", &"<runtime scope>")
96            .field("settings", &self.settings)
97            .finish()
98    }
99}
100
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct RuntimeSettingsSignatureEntry {
103    pub provider_id: ExpandedNodeIdentifier,
104    pub provider_type_id: TypeId,
105}
106
107#[derive(Clone, Debug, PartialEq, Eq)]
108pub enum RuntimeSettingsSource {
109    ComponentSettings,
110    ImportedLayer {
111        provider_id: ExpandedNodeIdentifier,
112        provider_type_id: TypeId,
113    },
114    Inline,
115}
116
117#[derive(Clone, Debug, Default)]
118pub struct RuntimeSettingsCondition {
119    pub positive: Vec<ExpressionInfo>,
120    pub negative: Vec<ExpressionInfo>,
121}
122
123#[derive(Clone)]
124pub struct RuntimeResolvedPropertyEntry {
125    pub source: RuntimeSettingsSource,
126    pub selector: Option<SelectorExpr>,
127    pub source_location: Option<LocationInfo>,
128    /// Overrides the receiving node's scope when this value came from an
129    /// imported settings provider.
130    pub source_stack: Option<Rc<RuntimePropertiesStackFrame>>,
131    pub value: ValueDefinition,
132    pub condition: Option<RuntimeSettingsCondition>,
133    pub axis_index: Option<usize>,
134}
135
136impl fmt::Debug for RuntimeResolvedPropertyEntry {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        f.debug_struct("RuntimeResolvedPropertyEntry")
139            .field("source", &self.source)
140            .field("selector", &self.selector)
141            .field("source_location", &self.source_location)
142            .field(
143                "source_stack",
144                &self.source_stack.as_ref().map(|_| "<runtime scope>"),
145            )
146            .field("value", &self.value)
147            .field("condition", &self.condition)
148            .field("axis_index", &self.axis_index)
149            .finish()
150    }
151}
152
153pub type RuntimeResolvedPropertyColumns = BTreeMap<String, Vec<RuntimeResolvedPropertyEntry>>;
154
155#[derive(Clone, Copy)]
156enum PointerActivationSource {
157    Mouse,
158    Touch,
159}
160
161#[derive(Clone, Copy, Debug, Default)]
162pub(crate) struct FilteredRenderStats {
163    pub path_nodes_visited: usize,
164    pub dirty_nodes_rendered: usize,
165    pub skipped_subtrees: usize,
166}
167
168#[derive(Clone)]
169pub struct ExpandedNode {
170    #[allow(dead_code)]
171    /// Unique ID of this expanded node, roughly encoding an address in the tree, where the first u32 is the instance ID
172    /// and the subsequent u32s represent addresses within an expanded tree via Repeat.
173    pub id: ExpandedNodeIdentifier,
174
175    /// Pointer to the unexpanded `instance_node` underlying this ExpandedNode
176    pub instance_node: RefCell<InstanceNodePtr>,
177
178    /// Pointer (`Weak` to avoid Rc cycle memory leaks) to the ExpandedNode
179    /// rendered directly above this one.
180    pub render_parent: RefCell<Weak<ExpandedNode>>,
181
182    /// Pointer (`Weak` to avoid Rc cycle memory leaks) to the ExpandedNode
183    /// in the template directly above this one.
184    pub template_parent: Weak<ExpandedNode>,
185
186    /// Id of closest frame present in the node tree.
187    /// included as a parameter on AnyCreatePatch when
188    /// creating a native element to know what clipping context
189    /// to attach to
190    pub parent_frame: Property<Option<ExpandedNodeIdentifier>>,
191
192    /// Nearest active native liquid-glass scope inherited by descendants.
193    pub liquid_glass_scope: Property<Option<NativeLiquidGlassScope>>,
194
195    /// Reference to the _component for which this `ExpandedNode` is a template member._ Used at least for
196    /// resolving projected children for `slot`. `Option`al because the very root instance node (root component, root instance node)
197    /// has a corollary "root component expanded node."  That very root expanded node _does not have_ a containing ExpandedNode component,
198    /// thus `containing_component` is `Option`al.
199    pub containing_component: Weak<ExpandedNode>,
200
201    /// Persistent clone of the state of the `PropertiesTreeShared#runtime_properties_stack` at the time that this node was expanded (this is expected to remain immutable
202    /// through the lifetime of the program after the initial expansion; however, if that constraint changes, this should be
203    /// explicitly updated to accommodate.)
204    pub stack: Rc<RuntimePropertiesStackFrame>,
205
206    /// Pointers to the ExpandedNode beneath this one. Used for rendering
207    /// recursion and other render-tree traversals.
208    pub children: Property<Vec<Rc<ExpandedNode>>>,
209
210    /// The concrete render-tree children currently attached beneath this node.
211    ///
212    /// This may temporarily include children retained for exit transitions, so
213    /// it should not be confused with the semantic payload exposed through
214    /// `NodeContext::received_children`.
215    pub mounted_children: RefCell<Vec<Rc<ExpandedNode>>>,
216
217    /// Children that represent the currently selected control-flow/template output.
218    ///
219    /// When this node's received-child source is `Owned`, this is the internal
220    /// source for `NodeContext::received_children`.
221    pub active_children: RefCell<Vec<Rc<ExpandedNode>>>,
222    /// Reactive mirror of `active_children` for consumers that need the active
223    /// semantic set.
224    pub active_children_view: Property<Vec<Rc<ExpandedNode>>>,
225
226    /// Children retained only so exit transitions can finish before unmount.
227    ///
228    /// When this node's received-child source is `Owned`, this is the internal
229    /// source for `NodeContext::retained_received_children`.
230    pub exiting_children: RefCell<Vec<Rc<ExpandedNode>>>,
231    /// Retention token captured when each exiting child is queued. Rescuing a
232    /// child advances its token so stale cleanup work cannot unmount it.
233    exiting_child_generations: RefCell<HashMap<ExpandedNodeIdentifier, u64>>,
234    /// Reactive mirror of `exiting_children` for consumers that need retained exits.
235    pub exiting_children_view: Property<Vec<Rc<ExpandedNode>>>,
236
237    /// Auxiliary children participate in update/layout but are not mounted or rendered directly.
238    pub sidecar_children: RefCell<Vec<Rc<ExpandedNode>>>,
239
240    /// Each ExpandedNode has a unique "stamp" of computed properties
241    pub properties: RefCell<Rc<RefCell<PaxAny>>>,
242
243    /// Each ExpandedNode has unique, computed `CommonProperties`
244    pub common_properties: RefCell<Rc<RefCell<CommonProperties>>>,
245    /// Measured bounds reported by chassis/native layout or by container-owned bottom-up layout.
246    /// When width/height are omitted, these values are used as the fallback concrete size.
247    pub measured_size: Property<Option<(f64, f64)>>,
248    /// Selector-facing metadata used by runtime and designtime queries.
249    pub selector_metadata: RefCell<RuntimeSelectorMetadata>,
250
251    /// The layout information (width, height, transform) used to render this node.
252    /// computed property based on parent bounds + common properties
253    pub transform_and_bounds: Property<TransformAndBounds<NodeLocal, Window>>,
254    /// Engine-owned subtree hull used by container measurement.
255    pub subtree_layout_hull: Property<LayoutHull>,
256    /// Optional container-assigned virtual wrapper frame applied before this node's own layout.
257    pub container_frame: Property<Option<ContainerFrame>>,
258
259    /// The accumulated opacity inherited from render ancestors and this node's
260    /// own common opacity value.
261    pub computed_opacity: Property<f64>,
262
263    /// For nodes that own projection, tracks projected children in their
264    /// expanded, non-collapsed form.
265    ///
266    /// Projection is a transport detail used to deliver received payload into a
267    /// node's encapsulated implementation, typically for `slot(...)`.
268    /// Repeat/conditional descendants are still present here.
269    pub expanded_projected_children: RefCell<Option<Vec<Rc<ExpandedNode>>>>,
270    /// Flattened version of the projected children above, where repeat and
271    /// conditionals are recursively replaced by their current outputs.
272    ///
273    /// This is the raw projected family, not necessarily the same thing as the
274    /// semantic `received_children` view seen by all consumers.
275    pub expanded_and_flattened_projected_children: Property<Vec<Rc<ExpandedNode>>>,
276    /// Number of expanded and flattened projected children.
277    pub flattened_projected_children_count: Property<usize>,
278
279    /// Flag that is > 0 if this node is part of the root tree. If it is,
280    /// updates to this nodes children also marks them as attached (+1), triggering
281    /// mount and dismount on addition/removal. This is needed mainly for slot,
282    /// since an entire "shadow tree" needs to be expanded and updated for
283    /// each slot child, but only the ones that have a "connected" slot should
284    /// trigger mount/dismount updates
285    pub attached: Cell<u32>,
286
287    /// Logical render-layer assignment plus z-order for the current render tree pass.
288    ///
289    /// Layer 0 is the root surface stack, and non-root layers are reserved for scroller-owned
290    /// vector islands.
291    pub occlusion: Property<Occlusion>,
292
293    /// Hash of the last native occlusion mask emitted for this node.
294    pub native_mask_hash: Cell<u64>,
295
296    /// Last browser-owned descendant content layer published for scroller-style native hosts.
297    pub browser_content_layer_id: Cell<Option<u32>>,
298
299    /// Hash of the last presentation metadata emitted for clipping/scrolling chassis consumers.
300    pub presentation_cache_hash: Cell<u64>,
301
302    /// A map of all properties available on this expanded node.
303    /// Used by the RuntimePropertiesStackFrame to resolve symbols.
304    pub properties_scope: RefCell<HashMap<String, Variable>>,
305
306    /// The flattened index of this node in its container (if this container
307    /// cares about slot children, ex: component, path).
308    pub slot_index: Property<Option<usize>>,
309
310    /// property used to "freeze" (stop firing tick) on a node and all it's children
311    pub suspended: Property<bool>,
312
313    /// used by native elements to trigger sending of native messages
314    /// used by canvas elements to dirtify their canvas
315    pub changed_listener: Property<()>,
316    selector_classes_listener: Property<()>,
317
318    /// Tracks whether this node's occlusion-affecting inputs changed.
319    pub occlusion_listener: Property<()>,
320
321    /// Pulls the node's children property only when its upstream dependencies changed.
322    pub children_listener: Property<()>,
323    /// Rebinds `subtree_layout_hull` when the child list changes.
324    pub subtree_layout_hull_listener: Property<()>,
325    parent_binding_sources: RefCell<Option<ParentBindingSources>>,
326    layout_binding_generation: Cell<u64>,
327    #[cfg(test)]
328    pub(crate) parent_binding_rebuilds: Cell<usize>,
329    #[cfg(test)]
330    pub(crate) layout_hull_rebuilds: Cell<usize>,
331    #[cfg(test)]
332    pub(crate) structural_children_updates: Cell<usize>,
333    /// Reactive content-measurement effect used by autosize-style container features.
334    pub content_measurement_listener: Property<()>,
335    /// Rebinds `content_measurement_listener` when the content child list changes.
336    pub content_measurement_rebind_listener: Property<()>,
337    /// Guards one-time setup of the reactive content-measurement listener pair.
338    pub content_measurement_bound: Cell<bool>,
339    /// Cached update traversal state. Structural effects mark ancestors dirty when child
340    /// ownership changes; the update pass clears this for subtrees with no remaining
341    /// imperative update work.
342    subtree_requires_non_reactive_update: Cell<bool>,
343
344    /// Dirty signal emitted when the projected child set changes structurally.
345    pub projected_children_changed: Property<()>,
346
347    /// Dirty signal emitted when active slot sites or slot-index expressions
348    /// may change the component-local projection resolution.
349    pub slot_projection_changed: Property<()>,
350
351    /// subscription properties: added to this expanded node by calling ctx.subscribe in a node event handler
352    pub subscriptions: RefCell<Vec<Property<()>>>,
353
354    /// Current lifecycle transition phase for this node.
355    pub transition_phase: Property<u64>,
356    /// Monotonic identity for each lifecycle transition run.
357    pub transition_generation: Property<u64>,
358    /// Whether the current run directly reversed the opposite lifecycle phase.
359    pub transition_takeover: Property<bool>,
360    /// Class membership captured at lifecycle-transition start. Ordinary settings continue to
361    /// follow live classes, while transition tracks keep the selector membership that launched
362    /// the current run.
363    pub transition_selector_classes: RefCell<Option<Vec<String>>>,
364    /// Parent-owned exit retention generation for stale cleanup protection.
365    exit_retention_generation: Cell<u64>,
366    /// Frame at which the active lifecycle transition began.
367    pub transition_origin_frame: Property<u64>,
368    /// Millisecond clock value at which the active lifecycle transition began.
369    pub transition_origin_millis: Property<u64>,
370    /// Local playhead, in frames, for the active lifecycle transition.
371    pub transition_playhead: Property<f64>,
372    /// Local playhead, in milliseconds, for the active lifecycle transition.
373    pub transition_playhead_millis: Property<f64>,
374    /// Effect property used to stop enter-transition clock dependencies.
375    pub enter_cleanup_listener: Property<()>,
376    /// Whether enter cleanup should do work on frame ticks.
377    pub enter_cleanup_active: Cell<bool>,
378    /// Wall-clock start for exit timeout enforcement.
379    pub exit_started_millis: Cell<Option<u128>>,
380    /// Whether this node has already warned about truncating its current exit transition.
381    exit_timeout_warning_emitted: Cell<bool>,
382    /// Effect property used to release deferred exit children.
383    pub exit_cleanup_listener: Property<()>,
384    /// Whether exit cleanup should do work on frame ticks.
385    pub exit_cleanup_active: Cell<bool>,
386    /// Imported provider layers currently active for this component instance.
387    pub imported_settings_layers: RefCell<Vec<RuntimeSettingsLayer>>,
388    /// Ordered provider-node ids used to detect when the imported layer stack changed.
389    pub imported_settings_signature: RefCell<Vec<RuntimeSettingsSignatureEntry>>,
390    /// Layered property entries ordered from lowest to highest precedence.
391    pub resolved_property_columns: RefCell<RuntimeResolvedPropertyColumns>,
392    /// Winning property entry per key after layering and inline override.
393    pub resolved_property_provenance: RefCell<BTreeMap<String, RuntimeResolvedPropertyEntry>>,
394    /// Requests a materialization pass that resets properties removed from the prior columns.
395    pub reset_removed_runtime_properties: Cell<bool>,
396}
397
398#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
399pub struct Occlusion {
400    /// Logical render layer: 0 for the root surface stack, >0 for scroller-owned vector islands.
401    pub render_layer_id: usize,
402    pub z_index: i32,
403    // this is used to perform last patches logic,
404    // and is updated to reflect this nodes parent_frame
405    // when occlusion is calculated
406    pub parent_frame: Option<u32>,
407}
408
409impl Interpolatable for Occlusion {}
410
411impl ImplToFromPaxAny for ExpandedNode {}
412impl Interpolatable for ExpandedNode {}
413
414#[derive(Clone)]
415/// Live selector identity for one expanded runtime node.
416pub struct RuntimeSelectorMetadata {
417    /// Concrete element/component type used by type selectors.
418    pub type_id: TypeId,
419    /// Reactive node id used by id selectors.
420    pub id: Property<Option<String>>,
421    /// Reactive, normalized class names in left-to-right cascade order.
422    pub classes: Property<Vec<String>>,
423}
424
425impl RuntimeSelectorMetadata {
426    fn from_base(
427        base: &crate::rendering::BaseInstance,
428        common_properties: &Rc<RefCell<CommonProperties>>,
429        stack: &Rc<RuntimePropertiesStackFrame>,
430    ) -> Self {
431        let classes = base
432            .template_node_selector_info
433            .as_ref()
434            .and_then(|info| {
435                info.class_binding.as_ref().map(|binding| {
436                    build_selector_classes_property(binding, info.source_location.as_ref(), stack)
437                })
438            })
439            .unwrap_or_else(|| Property::new_with_name(Vec::new(), "selector classes"));
440
441        Self {
442            type_id: base.template_node_type_id.clone().unwrap_or_default(),
443            id: common_properties.borrow().id.clone(),
444            classes,
445        }
446    }
447
448    pub fn matches(&self, selector: &SelectorExpr) -> bool {
449        match selector {
450            SelectorExpr::Id(id) => self.id.get().as_deref() == Some(id.as_str()),
451            SelectorExpr::Class(class_name) => self
452                .classes
453                .get()
454                .into_iter()
455                .any(|node_class| node_class == *class_name),
456            SelectorExpr::Type(type_name) => {
457                self.type_id.to_string() == *type_name
458                    || self.type_id.get_pascal_identifier().as_deref() == Some(type_name.as_str())
459            }
460        }
461    }
462}
463
464fn warn_selector_class_once(
465    warnings: &Rc<RefCell<HashSet<String>>>,
466    key: String,
467    message: impl FnOnce() -> String,
468) {
469    if warnings.borrow_mut().insert(key) {
470        log::warn!("{}", message());
471    }
472}
473
474fn normalize_selector_classes(
475    value: PaxValue,
476    warnings: &Rc<RefCell<HashSet<String>>>,
477    source_location: Option<&LocationInfo>,
478) -> Vec<String> {
479    let location = source_location
480        .map(|location| {
481            format!(
482                " at {}:{}",
483                location.start_line_col.0, location.start_line_col.1
484            )
485        })
486        .unwrap_or_default();
487    let candidates = match value {
488        PaxValue::String(value) => vec![value],
489        PaxValue::Vec(values) => values
490            .into_iter()
491            .filter_map(|value| match value {
492                PaxValue::String(value) => Some(value),
493                other => {
494                    warn_selector_class_once(warnings, format!("list-type:{other}"), || {
495                        format!("ignoring non-string value in class list{location}: {other}")
496                    });
497                    None
498                }
499            })
500            .collect(),
501        other => {
502            warn_selector_class_once(warnings, format!("binding-type:{other}"), || {
503                format!(
504                    "class binding{location} must evaluate to String or Vec<String>; received {other}"
505                )
506            });
507            return Vec::new();
508        }
509    };
510
511    let mut seen = HashSet::new();
512    let mut normalized = candidates
513        .into_iter()
514        .rev()
515        .filter(|class_name| {
516            if class_name.is_empty() {
517                return false;
518            }
519            if !matches!(
520                SelectorExpr::parse(&format!(".{class_name}")),
521                Ok(SelectorExpr::Class(_))
522            ) {
523                warn_selector_class_once(warnings, format!("invalid-name:{class_name}"), || {
524                    format!("ignoring invalid class name '{class_name}'{location}")
525                });
526                return false;
527            }
528            seen.insert(class_name.clone())
529        })
530        .collect::<Vec<_>>();
531    normalized.reverse();
532    normalized
533}
534
535fn build_selector_classes_property(
536    binding: &ValueDefinition,
537    source_location: Option<&LocationInfo>,
538    stack: &Rc<RuntimePropertiesStackFrame>,
539) -> Property<Vec<String>> {
540    let warnings = Rc::new(RefCell::new(HashSet::new()));
541    let source_location = source_location.cloned();
542    match binding {
543        ValueDefinition::LiteralValue(value) => Property::new_with_name(
544            normalize_selector_classes(value.clone(), &warnings, source_location.as_ref()),
545            "selector classes",
546        ),
547        ValueDefinition::Expression(info) => {
548            let dependencies = info
549                .dependencies
550                .iter()
551                .filter_map(|dependency| {
552                    stack
553                        .resolve_symbol_as_erased_property(dependency)
554                        .or_else(|| {
555                            log::warn!("failed to resolve class binding symbol {dependency}");
556                            None
557                        })
558                })
559                .collect::<Vec<_>>();
560            let expression = info.expression.clone();
561            let expression_label = expression.to_string();
562            let stack = Rc::clone(stack);
563            let warnings = Rc::clone(&warnings);
564            Property::computed_with_name(
565                move || match expression.compute(stack.clone()) {
566                    Ok(value) => {
567                        normalize_selector_classes(value, &warnings, source_location.as_ref())
568                    }
569                    Err(error) => {
570                        warn_selector_class_once(
571                            &warnings,
572                            format!("evaluation:{expression_label}:{error:?}"),
573                            || {
574                                let location = source_location
575                                    .as_ref()
576                                    .map(|location| {
577                                        format!(
578                                            " at {}:{}",
579                                            location.start_line_col.0, location.start_line_col.1
580                                        )
581                                    })
582                                    .unwrap_or_default();
583                                format!(
584                                    "failed to evaluate class binding `{expression_label}`{location}: {error:?}"
585                                )
586                            },
587                        );
588                        Vec::new()
589                    }
590                },
591                &dependencies,
592                "selector classes",
593            )
594        }
595        other => {
596            log::warn!("unsupported class binding definition: {other}");
597            Property::new_with_name(Vec::new(), "selector classes")
598        }
599    }
600}
601
602#[cfg(test)]
603mod selector_class_tests {
604    use super::{build_selector_classes_property, normalize_selector_classes};
605    use crate::RuntimePropertiesStackFrame;
606    use pax_language::parse_pax_expression;
607    use pax_manifest::{ExpressionInfo, ValueDefinition};
608    use pax_runtime_api::{PaxValue, Property, Variable};
609    use std::cell::RefCell;
610    use std::collections::{HashMap, HashSet};
611    use std::rc::Rc;
612
613    #[test]
614    fn normalizes_order_duplicates_and_invalid_names() {
615        let warnings = Rc::new(RefCell::new(HashSet::new()));
616        let classes = normalize_selector_classes(
617            PaxValue::Vec(vec![
618                PaxValue::String("base".to_string()),
619                PaxValue::String("bad name".to_string()),
620                PaxValue::String("active".to_string()),
621                PaxValue::String("base".to_string()),
622                PaxValue::Numeric(1.into()),
623            ]),
624            &warnings,
625            None,
626        );
627
628        assert_eq!(classes, vec!["active", "base"]);
629        assert_eq!(warnings.borrow().len(), 2);
630    }
631
632    #[test]
633    fn expression_backed_classes_track_string_and_vector_properties() {
634        let constant_binding = ValueDefinition::Expression(ExpressionInfo::new(
635            parse_pax_expression(r#""temporary""#).unwrap(),
636        ));
637        let constant_classes = build_selector_classes_property(
638            &constant_binding,
639            None,
640            &RuntimePropertiesStackFrame::new(HashMap::new()),
641        );
642        assert_eq!(constant_classes.get(), vec!["temporary"]);
643
644        let scalar = Property::new("base".to_string());
645        let stack = RuntimePropertiesStackFrame::new(HashMap::from([(
646            "current_class".to_string(),
647            Variable::new_from_typed_property(scalar.clone()),
648        )]));
649        let scalar_binding = ValueDefinition::Expression(ExpressionInfo::new(
650            parse_pax_expression("current_class").unwrap(),
651        ));
652        let scalar_classes = build_selector_classes_property(&scalar_binding, None, &stack);
653        assert_eq!(scalar_classes.get(), vec!["base"]);
654        scalar.set("active".to_string());
655        assert_eq!(scalar_classes.get(), vec!["active"]);
656
657        let vector = Property::new(vec!["base".to_string(), "selected".to_string()]);
658        let stack = RuntimePropertiesStackFrame::new(HashMap::from([(
659            "current_classes".to_string(),
660            Variable::new_from_typed_property(vector.clone()),
661        )]));
662        let vector_binding = ValueDefinition::Expression(ExpressionInfo::new(
663            parse_pax_expression("current_classes").unwrap(),
664        ));
665        let vector_classes = build_selector_classes_property(&vector_binding, None, &stack);
666        assert_eq!(vector_classes.get(), vec!["base", "selected"]);
667        vector.set(Vec::new());
668        assert!(vector_classes.get().is_empty());
669    }
670}
671
672macro_rules! dispatch_event_handler {
673    ($fn_name:ident, $arg_type:ty, $handler_key:ident, $recurse:expr) => {
674        pub fn $fn_name(
675            self: &Rc<Self>,
676            event: Event<$arg_type>,
677            globals: &Globals,
678            ctx: &Rc<RuntimeContext>,
679        ) -> bool {
680            self.run_event_handlers_for_key($handler_key, &event, ctx);
681
682            if $recurse {
683                if let Some(parent) = self.template_parent.upgrade() {
684                    return parent.$fn_name(event, globals, ctx);
685                }
686            }
687            event.cancelled()
688        }
689    };
690}
691
692impl ExpandedNode {
693    pub fn initialize_root(template: Rc<ComponentInstance>, ctx: &Rc<RuntimeContext>) -> Rc<Self> {
694        let root_node = Self::new(
695            template,
696            ctx.globals().stack_frame(),
697            ctx,
698            Weak::new(),
699            Weak::new(),
700        );
701        root_node.bind_to_parent_bounds(ctx);
702        Rc::clone(&root_node).recurse_mount(ctx);
703        root_node
704    }
705
706    fn has_event_handlers(&self, handler_key: &str) -> bool {
707        borrow!(self.instance_node)
708            .base()
709            .get_handler_registry()
710            .is_some_and(|registry| {
711                borrow!(*registry)
712                    .handlers
713                    .get(handler_key)
714                    .is_some_and(|handlers| !handlers.is_empty())
715            })
716    }
717
718    fn run_event_handlers_for_key<T: Clone + 'static>(
719        self: &Rc<Self>,
720        handler_key: &str,
721        event: &Event<T>,
722        ctx: &Rc<RuntimeContext>,
723    ) {
724        if let Some(registry) = borrow!(self.instance_node).base().get_handler_registry() {
725            let borrowed_registry = &borrow!(*registry);
726            if let Some(handlers) = borrowed_registry.handlers.get(handler_key) {
727                if !handlers.is_empty() {
728                    let component_properties = if let Some(cc) = self.containing_component.upgrade()
729                    {
730                        Rc::clone(&*borrow!(cc.properties))
731                    } else {
732                        Rc::clone(&*borrow!(self.properties))
733                    };
734
735                    let context = self.get_node_context(ctx);
736                    handlers.iter().for_each(|handler| {
737                        let properties = if let HandlerLocation::Component = &handler.location {
738                            Rc::clone(&*borrow!(self.properties))
739                        } else {
740                            Rc::clone(&component_properties)
741                        };
742                        (handler.function)(
743                            Rc::clone(&properties),
744                            &context,
745                            Some(event.clone().to_pax_any()),
746                        );
747                    });
748                }
749            };
750        }
751    }
752
753    fn new(
754        template: Rc<dyn InstanceNode>,
755        env: Rc<RuntimePropertiesStackFrame>,
756        context: &Rc<RuntimeContext>,
757        containing_component: Weak<ExpandedNode>,
758        parent: Weak<ExpandedNode>,
759    ) -> Rc<Self> {
760        let transition_config = template.base().transition_config().clone();
761        let has_transition_bindings = transition_config.has_enter || transition_config.has_exit;
762        let transition_phase = Property::new_with_name(TRANSITION_PHASE_IDLE, "transition phase");
763        let transition_generation = Property::new_with_name(0, "transition generation");
764        let transition_takeover = Property::new_with_name(false, "transition takeover");
765        let transition_origin_frame =
766            Property::new_with_name(context.globals().elapsed_frames.get(), "transition origin");
767        let transition_origin_millis = Property::new_with_name(
768            context.globals().elapsed_millis.get(),
769            "transition origin millis",
770        );
771        let transition_playhead = Property::new_with_name(0.0, "transition playhead");
772        let transition_playhead_millis = Property::new_with_name(0.0, "transition playhead millis");
773
774        let env = if has_transition_bindings {
775            env.push(
776                vec![
777                    (
778                        TRANSITION_PHASE_SYMBOL.to_string(),
779                        Variable::new_from_typed_property(transition_phase.clone()),
780                    ),
781                    (
782                        TRANSITION_GENERATION_SYMBOL.to_string(),
783                        Variable::new_from_typed_property(transition_generation.clone()),
784                    ),
785                    (
786                        TRANSITION_TAKEOVER_SYMBOL.to_string(),
787                        Variable::new_from_typed_property(transition_takeover.clone()),
788                    ),
789                    (
790                        TRANSITION_PLAYHEAD_SYMBOL.to_string(),
791                        Variable::new_from_typed_property(transition_playhead.clone()),
792                    ),
793                    (
794                        TRANSITION_PLAYHEAD_MILLIS_SYMBOL.to_string(),
795                        Variable::new_from_typed_property(transition_playhead_millis.clone()),
796                    ),
797                ]
798                .into_iter()
799                .collect(),
800            )
801        } else {
802            env
803        };
804
805        // Legacy callbacks may inspect pre-bound state. Local self-timelines
806        // also need sibling double-binding aliases installed before capturing
807        // their scope. Keep their pre-node phase until alias installation is a
808        // separate descriptor operation; ordinary structured nodes just allocate.
809        let legacy_initialization = template.base().requires_pre_node_binding();
810        let properties_init = &template.base().instance_prototypical_properties;
811        let common_init = &template.base().instance_prototypical_common_properties;
812        let properties = if legacy_initialization {
813            properties_init.materialize(env.clone(), None).unwrap()
814        } else {
815            properties_init.allocate(env.clone())
816        };
817        let common_properties = if legacy_initialization {
818            common_init.materialize(env.clone(), None).unwrap()
819        } else {
820            common_init.allocate(env.clone())
821        };
822        let selector_metadata =
823            RuntimeSelectorMetadata::from_base(template.base(), &common_properties, &env);
824
825        let property_scope = if legacy_initialization {
826            let mut scope = borrow!(*common_properties).retrieve_property_scope();
827            scope.extend(template.base().properties_scope.build(properties.clone()));
828            scope
829        } else {
830            // Publish after binding so double-binding aliases are final and
831            // conversion-adapter properties are allocated only once.
832            HashMap::new()
833        };
834
835        let id = context.gen_uid();
836        let res = Rc::new(ExpandedNode {
837            id,
838            stack: env,
839            instance_node: RefCell::new(Rc::clone(&template)),
840            attached: Cell::new(0),
841            properties: RefCell::new(properties),
842            common_properties: RefCell::new(common_properties),
843            measured_size: Property::default(),
844            selector_metadata: RefCell::new(selector_metadata),
845
846            // these two refer to their rendering parent, not their
847            // template parent
848            render_parent: Default::default(),
849            parent_frame: Default::default(),
850            liquid_glass_scope: Default::default(),
851            template_parent: parent,
852
853            containing_component,
854            children: Property::new_with_name(
855                Vec::new(),
856                &format!("node children (node id: {})", id.0),
857            ),
858            mounted_children: RefCell::new(Vec::new()),
859            active_children: RefCell::new(Vec::new()),
860            active_children_view: Property::new(Vec::new()),
861            exiting_children: RefCell::new(Vec::new()),
862            exiting_child_generations: RefCell::new(HashMap::new()),
863            exiting_children_view: Property::new(Vec::new()),
864            sidecar_children: RefCell::new(Vec::new()),
865            transform_and_bounds: Property::new(TransformAndBounds::default()),
866            subtree_layout_hull: Property::new(LayoutHull::default()),
867            container_frame: Property::new(None),
868            computed_opacity: Property::new(1.0),
869            expanded_projected_children: Default::default(),
870            expanded_and_flattened_projected_children: Default::default(),
871            flattened_projected_children_count: Property::new(0),
872            occlusion: Property::new(Occlusion::default()),
873            native_mask_hash: Cell::new(0),
874            browser_content_layer_id: Cell::new(None),
875            presentation_cache_hash: Cell::new(0),
876            properties_scope: RefCell::new(property_scope),
877            slot_index: Property::default(),
878            suspended: Property::new(false),
879            changed_listener: Property::default(),
880            selector_classes_listener: Property::default(),
881            occlusion_listener: Property::default(),
882            children_listener: Property::default(),
883            subtree_layout_hull_listener: Property::default(),
884            parent_binding_sources: RefCell::new(None),
885            layout_binding_generation: Cell::new(0),
886            #[cfg(test)]
887            parent_binding_rebuilds: Cell::new(0),
888            #[cfg(test)]
889            layout_hull_rebuilds: Cell::new(0),
890            #[cfg(test)]
891            structural_children_updates: Cell::new(0),
892            content_measurement_listener: Property::default(),
893            content_measurement_rebind_listener: Property::default(),
894            content_measurement_bound: Cell::new(false),
895            subtree_requires_non_reactive_update: Cell::new(true),
896            projected_children_changed: Property::default(),
897            slot_projection_changed: Property::default(),
898            subscriptions: Default::default(),
899            transition_phase,
900            transition_generation,
901            transition_takeover,
902            transition_selector_classes: RefCell::new(None),
903            exit_retention_generation: Cell::new(0),
904            transition_origin_frame,
905            transition_origin_millis,
906            transition_playhead,
907            transition_playhead_millis,
908            enter_cleanup_listener: Property::default(),
909            enter_cleanup_active: Cell::new(false),
910            exit_started_millis: Cell::new(None),
911            exit_timeout_warning_emitted: Cell::new(false),
912            exit_cleanup_listener: Property::default(),
913            exit_cleanup_active: Cell::new(false),
914            imported_settings_layers: RefCell::new(Vec::new()),
915            imported_settings_signature: RefCell::new(Vec::new()),
916            resolved_property_columns: RefCell::new(BTreeMap::new()),
917            resolved_property_provenance: RefCell::new(BTreeMap::new()),
918            reset_removed_runtime_properties: Cell::new(false),
919        });
920        template.base().bind_properties(&res);
921        if legacy_initialization {
922            let common_properties = Rc::clone(&*borrow!(res.common_properties));
923            *res.selector_metadata.borrow_mut() =
924                RuntimeSelectorMetadata::from_base(template.base(), &common_properties, &res.stack);
925        }
926        res.refresh_properties_scope(&template);
927        res.bind_selector_classes_listener(context);
928        res.bind_occlusion_listener(context);
929        res.bind_children_listener(context);
930        res.bind_subtree_layout_hull(context);
931        res
932    }
933
934    pub fn recreate_with_new_data(
935        self: &Rc<Self>,
936        template: Rc<dyn InstanceNode>,
937        context: &Rc<RuntimeContext>,
938    ) {
939        *borrow_mut!(self.instance_node) = Rc::clone(&template);
940        template.base().bind_properties(self);
941        let common_properties = Rc::clone(&*borrow!(self.common_properties));
942        *self.selector_metadata.borrow_mut() =
943            RuntimeSelectorMetadata::from_base(template.base(), &common_properties, &self.stack);
944        self.refresh_properties_scope(&template);
945        self.bind_selector_classes_listener(context);
946        self.reapply_runtime_settings(context);
947        self.bind_to_parent_bounds(context);
948        self.bind_occlusion_listener(context);
949        self.mark_non_reactive_update_subtree_dirty();
950        context.mark_occlusion_dirty();
951        context.set_canvas_dirty(self.occlusion.get().render_layer_id);
952    }
953
954    pub fn fully_recreate_with_new_data(
955        self: &Rc<Self>,
956        template: Rc<dyn InstanceNode>,
957        context: &Rc<RuntimeContext>,
958    ) {
959        Rc::clone(self).recurse_unmount(context);
960        let new_expanded_node = Self::new(
961            template.clone(),
962            Rc::clone(&self.stack),
963            context,
964            Weak::clone(&self.containing_component),
965            Weak::clone(&self.template_parent),
966        );
967        *borrow_mut!(self.instance_node) = Rc::clone(&*borrow!(new_expanded_node.instance_node));
968        *borrow_mut!(self.properties) = Rc::clone(&*borrow!(new_expanded_node.properties));
969        *borrow_mut!(self.properties_scope) = borrow!(new_expanded_node.properties_scope).clone();
970        *borrow_mut!(self.common_properties) =
971            Rc::clone(&*borrow!(new_expanded_node.common_properties));
972        *self.selector_metadata.borrow_mut() = new_expanded_node.selector_metadata.borrow().clone();
973        self.occlusion.set(Default::default());
974
975        self.bind_to_parent_bounds(context);
976        self.bind_selector_classes_listener(context);
977        self.bind_occlusion_listener(context);
978        self.mark_non_reactive_update_subtree_dirty();
979        context.mark_occlusion_dirty();
980        Rc::clone(self).recurse_mount(context);
981        context.drain_node_effects();
982        self.recurse_emit_mount_updates();
983    }
984
985    fn recurse_emit_mount_updates(self: &Rc<Self>) {
986        // `fully_recreate_with_new_data` can remount native-backed nodes after the main
987        // update traversal has already consumed `changed_listener`. Emit the initial
988        // native updates here so the chassis does not paint a frame with skeletal defaults.
989        self.changed_listener.get();
990        self.occlusion_listener.get();
991        for child in self.children.get().iter() {
992            child.recurse_emit_mount_updates();
993        }
994        for child in borrow!(self.sidecar_children).iter() {
995            child.recurse_emit_mount_updates();
996        }
997    }
998
999    fn refresh_properties_scope(self: &Rc<Self>, template: &Rc<dyn InstanceNode>) {
1000        let common_properties = Rc::clone(&*borrow!(self.common_properties));
1001        let mut refreshed_scope = borrow!(*common_properties).retrieve_property_scope();
1002        refreshed_scope.extend(
1003            template
1004                .base()
1005                .properties_scope
1006                .build(Rc::clone(&*borrow!(self.properties))),
1007        );
1008        *borrow_mut!(self.properties_scope) = refreshed_scope;
1009    }
1010
1011    /// Returns whether this node is a descendant of the ExpandedNode described by `other_expanded_node_id` (id)
1012    /// Currently requires traversing linked list of ancestry, incurring a O(log(n)) cost for a tree of `n` elements.
1013    /// This could be mitigated with caching/memoization, perhaps by storing a HashSet on each ExpandedNode describing its ancestry chain.
1014    pub fn is_descendant_of(&self, other_expanded_node_id: &ExpandedNodeIdentifier) -> bool {
1015        if let Some(parent) = borrow!(self.render_parent).upgrade() {
1016            // We have a parent — if it matches the ID, this node is indeed an ancestor of other_expanded_node_id.  Otherwise, recurse upward.
1017            if parent.id.eq(other_expanded_node_id) {
1018                true
1019            } else {
1020                parent.is_descendant_of(other_expanded_node_id)
1021            }
1022        } else {
1023            false
1024        }
1025    }
1026
1027    pub fn create_children_detached(
1028        self: &Rc<Self>,
1029        templates: impl IntoIterator<Item = (Rc<dyn InstanceNode>, Rc<RuntimePropertiesStackFrame>)>,
1030        context: &Rc<RuntimeContext>,
1031        template_parent: &Weak<ExpandedNode>,
1032    ) -> Vec<Rc<ExpandedNode>> {
1033        let containing_component = if borrow!(self.instance_node).base().flags().is_component {
1034            Rc::downgrade(&self)
1035        } else {
1036            Weak::clone(&self.containing_component)
1037        };
1038
1039        let mut children = Vec::new();
1040
1041        for (template, env) in templates {
1042            children.push(Self::new(
1043                template,
1044                env,
1045                context,
1046                Weak::clone(&containing_component),
1047                Weak::clone(&template_parent),
1048            ));
1049        }
1050        children
1051    }
1052
1053    pub(crate) fn current_attached_children(&self) -> Vec<Rc<ExpandedNode>> {
1054        borrow!(self.mounted_children).clone()
1055    }
1056
1057    pub(crate) fn rescue_exiting_child_matching(
1058        self: &Rc<Self>,
1059        predicate: impl Fn(&Rc<ExpandedNode>) -> bool,
1060    ) -> Option<Rc<ExpandedNode>> {
1061        let position = borrow!(self.exiting_children).iter().position(predicate)?;
1062        let child = borrow_mut!(self.exiting_children).remove(position);
1063        child
1064            .exit_retention_generation
1065            .set(child.exit_retention_generation.get().wrapping_add(1));
1066        borrow_mut!(self.exiting_child_generations).remove(&child.id);
1067        if borrow!(self.exiting_children).is_empty() {
1068            self.exit_cleanup_active.set(false);
1069            self.exit_cleanup_listener
1070                .replace_with(Property::new_with_name((), "exit transition cleanup"));
1071        }
1072        Some(child)
1073    }
1074
1075    fn sync_mounted_children_from_active_and_exiting(&self) -> Vec<Rc<ExpandedNode>> {
1076        let active_children = borrow!(self.active_children).clone();
1077        let exiting_children = borrow!(self.exiting_children).clone();
1078        if !self
1079            .active_children_view
1080            .read(|old| Self::same_children(old, &active_children))
1081        {
1082            self.active_children_view.set(active_children.clone());
1083        }
1084        if !self
1085            .exiting_children_view
1086            .read(|old| Self::same_children(old, &exiting_children))
1087        {
1088            self.exiting_children_view.set(exiting_children.clone());
1089        }
1090        let mut combined = exiting_children;
1091        combined.extend(active_children);
1092        *borrow_mut!(self.mounted_children) = combined.clone();
1093        combined
1094    }
1095
1096    fn has_child(children: &[Rc<ExpandedNode>], target: &Rc<ExpandedNode>) -> bool {
1097        children.iter().any(|child| Rc::ptr_eq(child, target))
1098    }
1099
1100    fn same_children(a: &[Rc<ExpandedNode>], b: &[Rc<ExpandedNode>]) -> bool {
1101        a.len() == b.len() && a.iter().zip(b).all(|(a, b)| Rc::ptr_eq(a, b))
1102    }
1103
1104    fn detach_child_for_reparent(&self, target: &Rc<ExpandedNode>) {
1105        let mut removed = false;
1106        {
1107            let active_children = &mut *borrow_mut!(self.active_children);
1108            let old_len = active_children.len();
1109            active_children.retain(|child| !Rc::ptr_eq(child, target));
1110            removed |= active_children.len() != old_len;
1111        }
1112        {
1113            let exiting_children = &mut *borrow_mut!(self.exiting_children);
1114            let old_len = exiting_children.len();
1115            exiting_children.retain(|child| !Rc::ptr_eq(child, target));
1116            removed |= exiting_children.len() != old_len;
1117        }
1118        if removed {
1119            self.sync_mounted_children_from_active_and_exiting();
1120            self.mark_non_reactive_update_subtree_dirty();
1121        }
1122    }
1123
1124    fn template_node_identifier(&self) -> Option<UniqueTemplateNodeIdentifier> {
1125        borrow!(self.instance_node)
1126            .base()
1127            .template_node_identifier
1128            .clone()
1129    }
1130
1131    fn transition_source_matches(
1132        sources: &[UniqueTemplateNodeIdentifier],
1133        own_identifier: Option<&UniqueTemplateNodeIdentifier>,
1134        requested_source: Option<&UniqueTemplateNodeIdentifier>,
1135    ) -> bool {
1136        if let Some(source) = requested_source {
1137            return sources.contains(source);
1138        }
1139        if sources.is_empty() {
1140            return true;
1141        }
1142        own_identifier
1143            .map(|identifier| sources.contains(identifier))
1144            .unwrap_or(false)
1145    }
1146
1147    fn has_exit_transition_for_source(
1148        &self,
1149        requested_source: Option<&UniqueTemplateNodeIdentifier>,
1150    ) -> bool {
1151        let instance_node = borrow!(self.instance_node);
1152        let transition_config = instance_node.base().transition_config();
1153        transition_config.has_exit
1154            && Self::transition_source_matches(
1155                &transition_config.exit_sources,
1156                instance_node.base().template_node_identifier.as_ref(),
1157                requested_source,
1158            )
1159    }
1160
1161    fn mark_non_reactive_update_subtree_dirty(&self) {
1162        if self.subtree_requires_non_reactive_update.replace(true) {
1163            return;
1164        }
1165        if let Some(parent) = borrow!(self.render_parent).upgrade() {
1166            parent.mark_non_reactive_update_subtree_dirty();
1167        }
1168    }
1169
1170    fn start_self_enter_transition_for_source(
1171        self: &Rc<Self>,
1172        context: &Rc<RuntimeContext>,
1173        requested_source: Option<&UniqueTemplateNodeIdentifier>,
1174    ) -> bool {
1175        let instance_node = borrow!(self.instance_node);
1176        let transition_config = instance_node.base().transition_config();
1177        if !transition_config.has_enter
1178            || !Self::transition_source_matches(
1179                &transition_config.enter_sources,
1180                instance_node.base().template_node_identifier.as_ref(),
1181                requested_source,
1182            )
1183        {
1184            return false;
1185        }
1186        drop(instance_node);
1187        self.begin_transition_run(context, TRANSITION_PHASE_ENTER);
1188        self.exit_started_millis.set(None);
1189        self.exit_timeout_warning_emitted.set(false);
1190        self.enable_enter_cleanup_listener(context);
1191        true
1192    }
1193
1194    fn start_self_enter_transition(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
1195        let started = self.start_self_enter_transition_for_source(context, None);
1196        if started {
1197            if let Some(source) = self.template_node_identifier() {
1198                self.start_bound_enter_transitions(context, &source);
1199            }
1200        }
1201    }
1202
1203    fn start_self_exit_transition_for_source(
1204        self: &Rc<Self>,
1205        context: &Rc<RuntimeContext>,
1206        requested_source: Option<&UniqueTemplateNodeIdentifier>,
1207    ) -> bool {
1208        if !self.has_exit_transition_for_source(requested_source) {
1209            return false;
1210        }
1211        self.begin_transition_run(context, TRANSITION_PHASE_EXIT);
1212        self.disable_enter_cleanup_listener();
1213        self.exit_started_millis
1214            .set(Some((context.globals().get_elapsed_millis)()));
1215        self.exit_timeout_warning_emitted.set(false);
1216        true
1217    }
1218
1219    fn start_self_exit_transition(self: &Rc<Self>, context: &Rc<RuntimeContext>) -> bool {
1220        let started = self.start_self_exit_transition_for_source(context, None);
1221        if started {
1222            if let Some(source) = self.template_node_identifier() {
1223                self.start_bound_exit_transitions(context, &source);
1224            }
1225        }
1226        started
1227    }
1228
1229    fn begin_transition_run(self: &Rc<Self>, context: &Rc<RuntimeContext>, phase: u64) {
1230        let previous_phase = self.transition_phase.get();
1231        let is_takeover = matches!(
1232            (previous_phase, phase),
1233            (TRANSITION_PHASE_ENTER, TRANSITION_PHASE_EXIT)
1234                | (TRANSITION_PHASE_EXIT, TRANSITION_PHASE_ENTER)
1235        );
1236        self.transition_origin_frame
1237            .set(context.globals().elapsed_frames.get());
1238        self.transition_origin_millis
1239            .set(context.globals().elapsed_millis.get());
1240        self.transition_takeover.set(is_takeover);
1241        self.transition_generation
1242            .set(self.transition_generation.get().wrapping_add(1));
1243        self.activate_transition_clock(context);
1244        self.transition_phase.set(phase);
1245        *self.transition_selector_classes.borrow_mut() =
1246            Some(self.selector_metadata.borrow().classes.get());
1247        self.reset_removed_runtime_properties.set(true);
1248        self.reapply_runtime_settings(context);
1249    }
1250
1251    fn activate_transition_clock(&self, context: &Rc<RuntimeContext>) {
1252        let elapsed_frames = context.globals().elapsed_frames.clone();
1253        let origin_frame = self.transition_origin_frame.clone();
1254        let elapsed_frames_for_playhead = elapsed_frames.clone();
1255        let origin_frame_for_playhead = origin_frame.clone();
1256        self.transition_playhead
1257            .replace_with(Property::computed_with_name(
1258                move || {
1259                    elapsed_frames_for_playhead
1260                        .get()
1261                        .saturating_sub(origin_frame_for_playhead.get()) as f64
1262                },
1263                &[elapsed_frames.untyped(), origin_frame.untyped()],
1264                "transition playhead",
1265            ));
1266
1267        let elapsed_millis = context.globals().elapsed_millis.clone();
1268        let origin_millis = self.transition_origin_millis.clone();
1269        let elapsed_millis_for_playhead = elapsed_millis.clone();
1270        let origin_millis_for_playhead = origin_millis.clone();
1271        self.transition_playhead_millis
1272            .replace_with(Property::computed_with_name(
1273                move || {
1274                    elapsed_millis_for_playhead
1275                        .get()
1276                        .saturating_sub(origin_millis_for_playhead.get()) as f64
1277                },
1278                &[elapsed_millis.untyped(), origin_millis.untyped()],
1279                "transition playhead millis",
1280            ));
1281    }
1282
1283    fn deactivate_transition_clock(&self) {
1284        self.transition_playhead
1285            .replace_with(Property::new_with_name(0.0, "transition playhead"));
1286        self.transition_playhead_millis
1287            .replace_with(Property::new_with_name(0.0, "transition playhead millis"));
1288    }
1289
1290    fn start_bound_enter_transitions(
1291        self: &Rc<Self>,
1292        context: &Rc<RuntimeContext>,
1293        source: &UniqueTemplateNodeIdentifier,
1294    ) {
1295        if let Some(containing_component) = self.containing_component.upgrade() {
1296            containing_component.start_descendant_enter_transitions_for_source(context, source);
1297        }
1298    }
1299
1300    fn start_bound_exit_transitions(
1301        self: &Rc<Self>,
1302        context: &Rc<RuntimeContext>,
1303        source: &UniqueTemplateNodeIdentifier,
1304    ) {
1305        if let Some(containing_component) = self.containing_component.upgrade() {
1306            containing_component.start_descendant_exit_transitions_for_source(context, source);
1307        }
1308    }
1309
1310    fn start_descendant_enter_transitions_for_source(
1311        self: &Rc<Self>,
1312        context: &Rc<RuntimeContext>,
1313        source: &UniqueTemplateNodeIdentifier,
1314    ) {
1315        for child in borrow!(self.mounted_children).iter() {
1316            child.start_enter_transition_for_source_recursive(context, source);
1317        }
1318        for child in borrow!(self.sidecar_children).iter() {
1319            child.start_enter_transition_for_source_recursive(context, source);
1320        }
1321    }
1322
1323    fn start_descendant_exit_transitions_for_source(
1324        self: &Rc<Self>,
1325        context: &Rc<RuntimeContext>,
1326        source: &UniqueTemplateNodeIdentifier,
1327    ) {
1328        for child in borrow!(self.mounted_children).iter() {
1329            child.start_exit_transition_for_source_recursive(context, source);
1330        }
1331        for child in borrow!(self.sidecar_children).iter() {
1332            child.start_exit_transition_for_source_recursive(context, source);
1333        }
1334    }
1335
1336    fn start_enter_transition_for_source_recursive(
1337        self: &Rc<Self>,
1338        context: &Rc<RuntimeContext>,
1339        source: &UniqueTemplateNodeIdentifier,
1340    ) {
1341        if self.template_node_identifier().as_ref() != Some(source) {
1342            self.start_self_enter_transition_for_source(context, Some(source));
1343        }
1344        for child in borrow!(self.mounted_children).iter() {
1345            child.start_enter_transition_for_source_recursive(context, source);
1346        }
1347        for child in borrow!(self.sidecar_children).iter() {
1348            child.start_enter_transition_for_source_recursive(context, source);
1349        }
1350    }
1351
1352    fn trigger_bound_enter_transitions_for_mounted_sources_recursive(
1353        self: &Rc<Self>,
1354        context: &Rc<RuntimeContext>,
1355    ) {
1356        if let Some(source) = self.template_node_identifier() {
1357            let should_trigger = {
1358                let instance_node = borrow!(self.instance_node);
1359                instance_node
1360                    .base()
1361                    .transition_config()
1362                    .enter_sources
1363                    .contains(&source)
1364            };
1365            if should_trigger {
1366                self.start_bound_enter_transitions(context, &source);
1367            }
1368        }
1369        for child in borrow!(self.mounted_children).iter() {
1370            child.trigger_bound_enter_transitions_for_mounted_sources_recursive(context);
1371        }
1372        for child in borrow!(self.sidecar_children).iter() {
1373            child.trigger_bound_enter_transitions_for_mounted_sources_recursive(context);
1374        }
1375    }
1376
1377    fn start_exit_transition_for_source_recursive(
1378        self: &Rc<Self>,
1379        context: &Rc<RuntimeContext>,
1380        source: &UniqueTemplateNodeIdentifier,
1381    ) {
1382        if self.template_node_identifier().as_ref() != Some(source) {
1383            self.start_self_exit_transition_for_source(context, Some(source));
1384        }
1385        for child in borrow!(self.mounted_children).iter() {
1386            child.start_exit_transition_for_source_recursive(context, source);
1387        }
1388        for child in borrow!(self.sidecar_children).iter() {
1389            child.start_exit_transition_for_source_recursive(context, source);
1390        }
1391    }
1392
1393    fn start_exit_transition_tree(self: &Rc<Self>, context: &Rc<RuntimeContext>) -> bool {
1394        let mut started = self.start_self_exit_transition(context);
1395        for child in borrow!(self.mounted_children).iter() {
1396            started |= child.start_exit_transition_tree(context);
1397        }
1398        started
1399    }
1400
1401    fn start_enter_transition_tree(self: &Rc<Self>, context: &Rc<RuntimeContext>) -> bool {
1402        let was_exiting = self.transition_phase.get() == TRANSITION_PHASE_EXIT;
1403        let started_here = self.start_self_enter_transition_for_source(context, None);
1404        if was_exiting && !started_here {
1405            self.transition_phase.set(TRANSITION_PHASE_IDLE);
1406            self.transition_takeover.set(false);
1407            self.deactivate_transition_clock();
1408            self.exit_started_millis.set(None);
1409            self.transition_selector_classes.borrow_mut().take();
1410            self.reset_removed_runtime_properties.set(true);
1411            self.reapply_runtime_settings(context);
1412        }
1413        let mut started = started_here;
1414        for child in borrow!(self.mounted_children).iter() {
1415            started |= child.start_enter_transition_tree(context);
1416        }
1417        if started_here {
1418            if let Some(source) = self.template_node_identifier() {
1419                self.start_bound_enter_transitions(context, &source);
1420            }
1421        }
1422        started
1423    }
1424
1425    fn retain_exiting_child(
1426        self: &Rc<Self>,
1427        child: Rc<ExpandedNode>,
1428        context: &Rc<RuntimeContext>,
1429    ) {
1430        if Self::has_child(&borrow!(self.exiting_children), &child) {
1431            return;
1432        }
1433        let generation = child.exit_retention_generation.get().wrapping_add(1);
1434        child.exit_retention_generation.set(generation);
1435        borrow_mut!(self.exiting_child_generations).insert(child.id, generation);
1436        borrow_mut!(self.exiting_children).push(child);
1437        self.enable_exit_cleanup_listener(context);
1438    }
1439
1440    fn self_exit_transition_complete(&self, context: &Rc<RuntimeContext>) -> bool {
1441        if self.transition_phase.get() != TRANSITION_PHASE_EXIT {
1442            return true;
1443        }
1444        let instance_node = borrow!(self.instance_node);
1445        let base = instance_node.base();
1446        let transition_config = base.transition_config().clone();
1447        let template_node_identifier = base.template_node_identifier.clone();
1448        let template_node_type_id = base.template_node_type_id.clone();
1449        drop(instance_node);
1450        let duration_complete = self.transition_duration_complete(
1451            transition_config.exit_frame_count,
1452            transition_config.exit_millis_count,
1453            &transition_config.exit_dynamic_durations,
1454        );
1455        let timeout_complete = self
1456            .exit_started_millis
1457            .get()
1458            .map(|started| {
1459                (context.globals().get_elapsed_millis)().saturating_sub(started)
1460                    >= transition_config.timeout_ms as u128
1461            })
1462            .unwrap_or(false);
1463        if timeout_complete
1464            && !duration_complete
1465            && !self.exit_timeout_warning_emitted.replace(true)
1466        {
1467            log::warn!(
1468                "Exit transition timed out after {}ms and will be truncated for node {:?}{}{}. Check @out/@transition duration, percent keyframes, and dynamic duration expressions.",
1469                transition_config.timeout_ms,
1470                self.id,
1471                template_node_identifier
1472                    .as_ref()
1473                    .map(|identifier| format!(" template={}", identifier))
1474                    .unwrap_or_default(),
1475                template_node_type_id
1476                    .as_ref()
1477                    .map(|type_id| format!(" type={}", type_id))
1478                    .unwrap_or_default(),
1479            );
1480        }
1481        duration_complete || timeout_complete
1482    }
1483
1484    fn transition_duration_complete(
1485        &self,
1486        frame_count: u64,
1487        millis_count: Option<u64>,
1488        dynamic_durations: &[ValueDefinition],
1489    ) -> bool {
1490        let mut frame_count = frame_count as f64;
1491        let mut millis_count = millis_count.map(|value| value as f64).unwrap_or_default();
1492        let mut unresolved_dynamic_duration = false;
1493
1494        for duration_definition in dynamic_durations {
1495            match evaluate_timeline_duration(duration_definition, &self.stack) {
1496                Some(duration) if duration.is_frame_based() => {
1497                    frame_count = frame_count.max(duration.as_frames_f64().max(0.0).ceil());
1498                }
1499                Some(duration) => {
1500                    millis_count = millis_count.max(duration.as_milliseconds_f64().max(0.0).ceil());
1501                }
1502                None => unresolved_dynamic_duration = true,
1503            }
1504        }
1505
1506        if unresolved_dynamic_duration
1507            && frame_count <= f64::EPSILON
1508            && millis_count <= f64::EPSILON
1509        {
1510            frame_count = 100.0;
1511        }
1512
1513        let frame_complete =
1514            frame_count <= f64::EPSILON || self.transition_playhead.get() >= frame_count;
1515        let millis_complete =
1516            millis_count <= f64::EPSILON || self.transition_playhead_millis.get() >= millis_count;
1517        frame_complete && millis_complete
1518    }
1519
1520    fn self_enter_transition_complete(&self) -> bool {
1521        if self.transition_phase.get() != TRANSITION_PHASE_ENTER {
1522            return true;
1523        }
1524        let instance_node = borrow!(self.instance_node);
1525        let transition_config = instance_node.base().transition_config().clone();
1526        drop(instance_node);
1527        self.transition_duration_complete(
1528            transition_config.enter_frame_count,
1529            transition_config.enter_millis_count,
1530            &transition_config.enter_dynamic_durations,
1531        )
1532    }
1533
1534    fn enable_enter_cleanup_listener(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
1535        if self.enter_cleanup_active.get() {
1536            return;
1537        }
1538        self.enter_cleanup_active.set(true);
1539        let weak_self = Rc::downgrade(self);
1540        let runtime_context = Rc::clone(context);
1541        let elapsed_frames = context.globals().elapsed_frames.clone();
1542        let elapsed_frames_dep = elapsed_frames.untyped();
1543        self.enter_cleanup_listener
1544            .replace_with(Property::computed_with_name(
1545                move || {
1546                    let _ = elapsed_frames.get();
1547                    if let Some(node) = weak_self.upgrade() {
1548                        node.complete_self_enter_transition_if_needed(&runtime_context);
1549                    }
1550                },
1551                &[elapsed_frames_dep],
1552                "enter transition cleanup",
1553            ));
1554        context.register_expanded_node_effect_property_named(
1555            self,
1556            &self.enter_cleanup_listener,
1557            "enter transition cleanup",
1558        );
1559    }
1560
1561    fn complete_self_enter_transition_if_needed(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
1562        if !self.enter_cleanup_active.get() {
1563            return;
1564        }
1565        if self.transition_phase.get() != TRANSITION_PHASE_ENTER {
1566            self.disable_enter_cleanup_listener();
1567            return;
1568        }
1569        if self.self_enter_transition_complete() {
1570            self.transition_phase.set(TRANSITION_PHASE_IDLE);
1571            self.deactivate_transition_clock();
1572            self.disable_enter_cleanup_listener();
1573            self.transition_selector_classes.borrow_mut().take();
1574            self.reset_removed_runtime_properties.set(true);
1575            self.reapply_runtime_settings(context);
1576        }
1577    }
1578
1579    fn disable_enter_cleanup_listener(&self) {
1580        self.enter_cleanup_active.set(false);
1581        self.enter_cleanup_listener
1582            .replace_with(Property::new_with_name((), "enter transition cleanup"));
1583    }
1584
1585    fn exit_transition_tree_complete(self: &Rc<Self>, context: &Rc<RuntimeContext>) -> bool {
1586        self.self_exit_transition_complete(context)
1587            && borrow!(self.mounted_children)
1588                .iter()
1589                .all(|child| child.exit_transition_tree_complete(context))
1590    }
1591
1592    fn enable_exit_cleanup_listener(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
1593        if self.exit_cleanup_active.get() {
1594            return;
1595        }
1596        self.exit_cleanup_active.set(true);
1597        let weak_self = Rc::downgrade(self);
1598        let cloned_context = Rc::clone(context);
1599        let elapsed_frames = context.globals().elapsed_frames.clone();
1600        let elapsed_frames_dep = elapsed_frames.untyped();
1601        self.exit_cleanup_listener
1602            .replace_with(Property::computed_with_name(
1603                move || {
1604                    let _ = elapsed_frames.get();
1605                    if let Some(node) = weak_self.upgrade() {
1606                        node.prune_completed_exit_children(&cloned_context);
1607                    }
1608                },
1609                &[elapsed_frames_dep],
1610                "exit transition cleanup",
1611            ));
1612        context.register_expanded_node_effect_property_named(
1613            self,
1614            &self.exit_cleanup_listener,
1615            "exit transition cleanup",
1616        );
1617    }
1618
1619    fn prune_completed_exit_children(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
1620        let exiting = std::mem::take(&mut *borrow_mut!(self.exiting_children));
1621        if exiting.is_empty() {
1622            self.exit_cleanup_active.set(false);
1623            self.exit_cleanup_listener
1624                .replace_with(Property::new_with_name((), "exit transition cleanup"));
1625            return;
1626        }
1627
1628        let mut retained = Vec::new();
1629        let active = borrow!(self.active_children).clone();
1630        for child in exiting {
1631            let expected_generation = borrow_mut!(self.exiting_child_generations).remove(&child.id);
1632            if Self::has_child(&active, &child)
1633                || expected_generation != Some(child.exit_retention_generation.get())
1634            {
1635                continue;
1636            }
1637            if child.exit_transition_tree_complete(context) {
1638                child.recurse_unmount(context);
1639            } else {
1640                borrow_mut!(self.exiting_child_generations)
1641                    .insert(child.id, child.exit_retention_generation.get());
1642                retained.push(child);
1643            }
1644        }
1645        *borrow_mut!(self.exiting_children) = retained;
1646        self.sync_mounted_children_from_active_and_exiting();
1647        // `children` is computed for control-flow nodes. A direct write here can
1648        // mark a pending branch update clean if cleanup and selection change in
1649        // the same frame, so invalidate it and let its evaluator reconcile.
1650        self.children.invalidate();
1651        context.mark_occlusion_dirty();
1652
1653        if borrow!(self.exiting_children).is_empty() {
1654            self.exit_cleanup_active.set(false);
1655            self.exit_cleanup_listener
1656                .replace_with(Property::new_with_name((), "exit transition cleanup"));
1657        }
1658    }
1659
1660    pub fn attach_children(
1661        self: &Rc<Self>,
1662        new_children: Vec<Rc<ExpandedNode>>,
1663        context: &Rc<RuntimeContext>,
1664        parent_frame: &Property<Option<ExpandedNodeIdentifier>>,
1665    ) -> Vec<Rc<ExpandedNode>> {
1666        self.attach_children_with_liquid_glass_scope(
1667            new_children,
1668            context,
1669            parent_frame,
1670            &self.liquid_glass_scope,
1671        )
1672    }
1673
1674    pub fn attach_children_with_liquid_glass_scope(
1675        self: &Rc<Self>,
1676        new_children: Vec<Rc<ExpandedNode>>,
1677        context: &Rc<RuntimeContext>,
1678        parent_frame: &Property<Option<ExpandedNodeIdentifier>>,
1679        liquid_glass_scope: &Property<Option<NativeLiquidGlassScope>>,
1680    ) -> Vec<Rc<ExpandedNode>> {
1681        let mut bindings_changed = false;
1682        for child in new_children.iter() {
1683            let previous_parent = borrow!(child.render_parent).upgrade();
1684            let is_reparented = child.attached.get() > 0
1685                && previous_parent
1686                    .as_ref()
1687                    .is_some_and(|parent| parent.id != self.id);
1688            if is_reparented {
1689                child.mark_canvas_subtree_dirty_for_reparent(context, true);
1690                if let Some(previous_parent) = previous_parent.as_ref() {
1691                    previous_parent.detach_child_for_reparent(child);
1692                }
1693            }
1694
1695            bindings_changed |=
1696                child.ensure_parent_bindings(self, context, parent_frame, liquid_glass_scope);
1697            if is_reparented {
1698                child.rebind_parent_bounds_for_mounted_subtree(context);
1699                child.mark_canvas_subtree_dirty_for_reparent(context, false);
1700            }
1701        }
1702        // Reconciliation still evaluates item data and parent context before
1703        // this fast path. Exiting children remain managed by their cleanup lane.
1704        if Self::same_children(&borrow!(self.active_children), &new_children)
1705            && (self.attached.get() == 0
1706                || new_children.iter().all(|child| child.attached.get() > 0))
1707        {
1708            if bindings_changed {
1709                self.mark_non_reactive_update_subtree_dirty();
1710            }
1711            return self.current_attached_children();
1712        }
1713        let mut newly_mounted_children = Vec::new();
1714        if self.attached.get() > 0 {
1715            let old_active_children = borrow!(self.active_children).clone();
1716            for child in old_active_children.iter() {
1717                if Self::has_child(&new_children, child) {
1718                    continue;
1719                }
1720                if child.transition_phase.get() == TRANSITION_PHASE_EXIT {
1721                    if child.exit_transition_tree_complete(context) {
1722                        Rc::clone(child).recurse_unmount(context);
1723                    } else {
1724                        self.retain_exiting_child(Rc::clone(child), context);
1725                    }
1726                    continue;
1727                }
1728                if child.start_exit_transition_tree(context) {
1729                    self.retain_exiting_child(Rc::clone(child), context);
1730                } else {
1731                    Rc::clone(child).recurse_unmount(context);
1732                }
1733            }
1734            for child in new_children.iter() {
1735                if !Self::has_child(&old_active_children, child) || child.attached.get() == 0 {
1736                    if child.attached.get() > 0 {
1737                        child.start_enter_transition_tree(context);
1738                    } else {
1739                        Rc::clone(child).recurse_mount(context);
1740                        newly_mounted_children.push(Rc::clone(child));
1741                    }
1742                }
1743            }
1744        }
1745        *borrow_mut!(self.active_children) = new_children;
1746        let mounted = self.sync_mounted_children_from_active_and_exiting();
1747        for child in newly_mounted_children {
1748            child.trigger_bound_enter_transitions_for_mounted_sources_recursive(context);
1749        }
1750        self.mark_non_reactive_update_subtree_dirty();
1751        mounted
1752    }
1753
1754    pub fn attach_sidecar_children(
1755        self: &Rc<Self>,
1756        new_children: Vec<Rc<ExpandedNode>>,
1757        context: &Rc<RuntimeContext>,
1758        parent_frame: &Property<Option<ExpandedNodeIdentifier>>,
1759    ) -> Vec<Rc<ExpandedNode>> {
1760        for child in new_children.iter() {
1761            child.ensure_parent_bindings(self, context, parent_frame, &self.liquid_glass_scope);
1762        }
1763        *borrow_mut!(self.sidecar_children) = new_children.clone();
1764        self.mark_non_reactive_update_subtree_dirty();
1765        new_children
1766    }
1767
1768    fn ensure_parent_bindings(
1769        self: &Rc<Self>,
1770        parent: &Rc<Self>,
1771        context: &Rc<RuntimeContext>,
1772        frame: &Property<Option<ExpandedNodeIdentifier>>,
1773        glass: &Property<Option<NativeLiquidGlassScope>>,
1774    ) -> bool {
1775        let properties = {
1776            let parent_cp = parent.get_common_properties();
1777            let parent_cp = borrow!(parent_cp);
1778            let cp = self.get_common_properties();
1779            let cp = borrow!(cp);
1780            vec![
1781                frame.untyped(),
1782                glass.untyped(),
1783                parent.suspended.untyped(),
1784                parent.transform_and_bounds.untyped(),
1785                parent.computed_opacity.untyped(),
1786                parent_cp.padding_x.untyped(),
1787                parent_cp.padding_y.untyped(),
1788                self.container_frame.untyped(),
1789                self.measured_size.untyped(),
1790                cp._suspended.untyped(),
1791                cp.opacity.untyped(),
1792                cp.transform.untyped(),
1793                cp.width.untyped(),
1794                cp.height.untyped(),
1795                cp.x.untyped(),
1796                cp.y.untyped(),
1797                cp.anchor_x.untyped(),
1798                cp.anchor_y.untyped(),
1799                cp.scale_x.untyped(),
1800                cp.scale_y.untyped(),
1801                cp.skew_x.untyped(),
1802                cp.skew_y.untyped(),
1803                cp.rotate.untyped(),
1804            ]
1805        };
1806        let sources = ParentBindingSources {
1807            parent: parent.id,
1808            properties,
1809        };
1810        let same_parent = borrow!(self.render_parent)
1811            .upgrade()
1812            .is_some_and(|old| Rc::ptr_eq(&old, parent));
1813        if same_parent
1814            && borrow!(self.parent_binding_sources)
1815                .as_ref()
1816                .is_some_and(|old| old.same_as(&sources))
1817        {
1818            return false;
1819        }
1820        *borrow_mut!(self.render_parent) = Rc::downgrade(parent);
1821        let frame = frame.clone();
1822        let deps = [frame.untyped()];
1823        self.parent_frame
1824            .replace_with(Property::computed(move || frame.get(), &deps));
1825        let glass = glass.clone();
1826        let deps = [glass.untyped()];
1827        self.liquid_glass_scope
1828            .replace_with(Property::computed(move || glass.get(), &deps));
1829        self.inherit_suspend(parent);
1830        self.bind_to_parent_bounds(context);
1831        *borrow_mut!(self.parent_binding_sources) = Some(sources);
1832        true
1833    }
1834
1835    fn rebind_parent_bounds_for_mounted_subtree(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
1836        self.bind_to_parent_bounds(context);
1837        self.bind_occlusion_listener(context);
1838        let mounted_children = borrow!(self.mounted_children).clone();
1839        for child in mounted_children {
1840            child.rebind_parent_bounds_for_mounted_subtree(context);
1841        }
1842        let sidecar_children = borrow!(self.sidecar_children).clone();
1843        for child in sidecar_children {
1844            child.rebind_parent_bounds_for_mounted_subtree(context);
1845        }
1846    }
1847
1848    fn mark_canvas_subtree_dirty_for_reparent(
1849        self: &Rc<Self>,
1850        context: &Rc<RuntimeContext>,
1851        queue_retained_removal: bool,
1852    ) {
1853        let layer = self.occlusion.get().render_layer_id;
1854        context.set_canvas_dirty(layer);
1855        if borrow!(self.instance_node).base().flags().layer == Layer::Canvas {
1856            if queue_retained_removal {
1857                context.enqueue_canvas_node_removal(layer, self.id.to_u32());
1858            }
1859            context.mark_canvas_node_dirty(self.id);
1860        }
1861        context.mark_occlusion_dirty();
1862
1863        let mounted_children = borrow!(self.mounted_children).clone();
1864        for child in mounted_children {
1865            child.mark_canvas_subtree_dirty_for_reparent(context, queue_retained_removal);
1866        }
1867        let sidecar_children = borrow!(self.sidecar_children).clone();
1868        for child in sidecar_children {
1869            child.mark_canvas_subtree_dirty_for_reparent(context, queue_retained_removal);
1870        }
1871    }
1872
1873    fn bind_to_parent_bounds(self: &Rc<Self>, ctx: &Rc<RuntimeContext>) {
1874        // Explicit reload/reparent rebuilds invalidate the attachment fast path.
1875        self.parent_binding_sources.borrow_mut().take();
1876        self.layout_binding_generation
1877            .set(self.layout_binding_generation.get().wrapping_add(1));
1878        self.subtree_layout_hull_listener.invalidate();
1879        self.children_listener.invalidate();
1880        #[cfg(test)]
1881        self.parent_binding_rebuilds
1882            .set(self.parent_binding_rebuilds.get() + 1);
1883        let render_parent = borrow!(self.render_parent).upgrade();
1884        let parent_transform_and_bounds = render_parent
1885            .as_ref()
1886            .map(|n| n.transform_and_bounds.clone())
1887            .unwrap_or_else(|| ctx.globals().viewport);
1888        let (parent_padding_x, parent_padding_y) = render_parent
1889            .as_ref()
1890            .map(|n| {
1891                let common_props = n.get_common_properties();
1892                let common_props = borrow!(common_props);
1893                (
1894                    common_props.padding_x.clone(),
1895                    common_props.padding_y.clone(),
1896                )
1897            })
1898            .unwrap_or_default();
1899        let container_frame = self.container_frame.clone();
1900        let deps = [
1901            parent_transform_and_bounds.untyped(),
1902            parent_padding_x.untyped(),
1903            parent_padding_y.untyped(),
1904            container_frame.untyped(),
1905        ];
1906        let effective_parent_transform_and_bounds = Property::computed(
1907            move || {
1908                apply_container_frame(
1909                    apply_padding_frame(
1910                        parent_transform_and_bounds.get(),
1911                        parent_padding_x.get(),
1912                        parent_padding_y.get(),
1913                    ),
1914                    container_frame.get(),
1915                )
1916            },
1917            &deps,
1918        );
1919        let common_props = borrow!(self.common_properties);
1920        let extra_transform = borrow!(common_props).transform.clone();
1921
1922        let transform_and_bounds = compute_tab(
1923            self.layout_properties(),
1924            extra_transform,
1925            effective_parent_transform_and_bounds,
1926        );
1927        self.transform_and_bounds.replace_with(transform_and_bounds);
1928
1929        let parent_opacity = borrow!(self.render_parent)
1930            .upgrade()
1931            .map(|n| n.computed_opacity.clone())
1932            .unwrap_or_else(|| Property::new(1.0));
1933        let common_props = self.get_common_properties();
1934        let self_opacity = borrow!(common_props).opacity.clone();
1935        let deps = [parent_opacity.untyped(), self_opacity.untyped()];
1936        self.computed_opacity.replace_with(Property::computed(
1937            move || {
1938                let parent = parent_opacity.get().clamp(0.0, 1.0);
1939                let local = self_opacity
1940                    .get()
1941                    .unwrap_or_default()
1942                    .to_float_0_1()
1943                    .clamp(0.0, 1.0);
1944                (parent * local).clamp(0.0, 1.0)
1945            },
1946            &deps,
1947        ));
1948    }
1949
1950    fn bind_occlusion_listener(self: &Rc<Self>, ctx: &Rc<RuntimeContext>) {
1951        let instance_node = borrow!(self.instance_node);
1952        let mut deps: Vec<_> = borrow!(self.properties_scope)
1953            .iter()
1954            .filter(|(name, _)| instance_node.property_requires_occlusion_recompute(name))
1955            .map(|(_, v)| v.get_untyped_property().clone())
1956            .collect();
1957        drop(instance_node);
1958
1959        // Structural child changes are handled by children_listener after
1960        // reconciliation; data-only repeat invalidation is not an occlusion change.
1961        deps.extend([
1962            self.transform_and_bounds.untyped(),
1963            self.computed_opacity.untyped(),
1964            self.liquid_glass_scope.untyped(),
1965        ]);
1966
1967        let context = Rc::clone(ctx);
1968        self.occlusion_listener
1969            .replace_with(Property::computed_with_name(
1970                move || {
1971                    context.mark_occlusion_dirty();
1972                },
1973                &deps,
1974                "occlusion listener",
1975            ));
1976        ctx.register_expanded_node_effect_property_named(
1977            self,
1978            &self.occlusion_listener,
1979            "occlusion listener",
1980        );
1981    }
1982
1983    fn reapply_runtime_settings(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
1984        let instance = Rc::clone(&*borrow!(self.instance_node));
1985        instance.base().bind_properties(self);
1986        self.reset_removed_runtime_properties.set(false);
1987        self.refresh_properties_scope(&instance);
1988        self.mark_non_reactive_update_subtree_dirty();
1989        context.mark_occlusion_dirty();
1990        context.set_canvas_dirty(self.occlusion.get().render_layer_id);
1991    }
1992
1993    fn bind_selector_classes_listener(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
1994        let classes = self.selector_metadata.borrow().classes.clone();
1995        let previous = Rc::new(RefCell::new(classes.get()));
1996        let weak_self = Rc::downgrade(self);
1997        let runtime_context = Rc::clone(context);
1998        self.selector_classes_listener
1999            .replace_with(Property::computed_with_name(
2000                move || {
2001                    let current = classes.get();
2002                    if *previous.borrow() == current {
2003                        return;
2004                    }
2005                    *previous.borrow_mut() = current;
2006                    let Some(node) = weak_self.upgrade() else {
2007                        return;
2008                    };
2009                    node.reset_removed_runtime_properties.set(true);
2010                    node.reapply_runtime_settings(&runtime_context);
2011                },
2012                &[self.selector_metadata.borrow().classes.untyped()],
2013                "selector classes listener",
2014            ));
2015        context.register_expanded_node_effect_property_named(
2016            self,
2017            &self.selector_classes_listener,
2018            "selector classes listener",
2019        );
2020    }
2021
2022    fn bind_children_listener(self: &Rc<Self>, ctx: &Rc<RuntimeContext>) {
2023        let deps = [self.children.untyped()];
2024        let weak_self = Rc::downgrade(self);
2025        let context = Rc::clone(ctx);
2026        let previous = RefCell::new(None);
2027        self.children_listener
2028            .replace_with(Property::computed_with_name(
2029                move || {
2030                    let Some(node) = weak_self.upgrade() else {
2031                        return;
2032                    };
2033                    if !node.child_structure_changed(&previous) {
2034                        return;
2035                    }
2036                    #[cfg(test)]
2037                    node.structural_children_updates
2038                        .set(node.structural_children_updates.get() + 1);
2039                    if borrow!(node.instance_node).base().flags().is_component
2040                        || borrow!(node.expanded_projected_children).is_some()
2041                    {
2042                        node.compute_flattened_projected_children();
2043                    }
2044                    // Projected if/for nodes are flattened out of the render
2045                    // tree. Their structural changes must refresh the receiving
2046                    // component's input list, even when it was previously empty.
2047                    if borrow!(node.instance_node).base().flags().invisible_to_slot {
2048                        let mut ancestor = node.template_parent.upgrade();
2049                        while let Some(parent) = ancestor {
2050                            if borrow!(parent.expanded_projected_children).is_some() {
2051                                parent.compute_flattened_projected_children();
2052                                break;
2053                            }
2054                            if !borrow!(parent.instance_node)
2055                                .base()
2056                                .flags()
2057                                .invisible_to_slot
2058                            {
2059                                break;
2060                            }
2061                            ancestor = parent.template_parent.upgrade();
2062                        }
2063                    }
2064                    context.mark_occlusion_dirty();
2065
2066                    let (is_slot, is_component) = {
2067                        let instance = borrow!(node.instance_node);
2068                        let flags = instance.base().flags();
2069                        (flags.is_slot, flags.is_component)
2070                    };
2071                    if !is_slot {
2072                        if is_component {
2073                            node.slot_projection_changed.set(());
2074                        } else if let Some(containing_component) =
2075                            node.containing_component.upgrade()
2076                        {
2077                            containing_component.slot_projection_changed.set(());
2078                        }
2079                    }
2080                },
2081                &deps,
2082                "children listener",
2083            ));
2084        ctx.register_expanded_node_effect_property_named(
2085            self,
2086            &self.children_listener,
2087            "children listener",
2088        );
2089    }
2090
2091    fn bind_subtree_layout_hull(self: &Rc<Self>, ctx: &Rc<RuntimeContext>) {
2092        self.rebind_subtree_layout_hull();
2093        let deps = [self.children.untyped()];
2094        let weak_self = Rc::downgrade(self);
2095        let previous = RefCell::new(None);
2096        self.subtree_layout_hull_listener
2097            .replace_with(Property::computed_with_name(
2098                move || {
2099                    let Some(node) = weak_self.upgrade() else {
2100                        return;
2101                    };
2102                    if node.child_structure_changed(&previous) {
2103                        node.rebind_subtree_layout_hull();
2104                    }
2105                },
2106                &deps,
2107                "subtree layout hull listener",
2108            ));
2109        ctx.register_expanded_node_effect_property_named(
2110            self,
2111            &self.subtree_layout_hull_listener,
2112            "subtree layout hull listener",
2113        );
2114    }
2115    fn child_structure_changed(&self, previous: &RefCell<Option<ChildStructure>>) -> bool {
2116        // Always evaluate first: reconciliation may update data, rescue an exit,
2117        // or remove a completed exit even when the source keys are unchanged.
2118        let children = self.children.get();
2119        let ids = |children: &[Rc<ExpandedNode>]| {
2120            children.iter().map(|child| child.id).collect::<Vec<_>>()
2121        };
2122        // Slot projection distinguishes active from exiting children even when
2123        // their combined render order is unchanged at the start of an exit.
2124        let current = ChildStructure {
2125            binding_generation: self.layout_binding_generation.get(),
2126            rendered: ids(&children),
2127            active: ids(&borrow!(self.active_children)),
2128            exiting: ids(&borrow!(self.exiting_children)),
2129        };
2130        let mut previous = previous.borrow_mut();
2131        if previous.as_ref() == Some(&current) {
2132            return false;
2133        }
2134        *previous = Some(current);
2135        true
2136    }
2137
2138    fn rebind_subtree_layout_hull(self: &Rc<Self>) {
2139        #[cfg(test)]
2140        self.layout_hull_rebuilds
2141            .set(self.layout_hull_rebuilds.get() + 1);
2142        let self_transform_and_bounds = self.transform_and_bounds.clone();
2143        let layout_properties = self.layout_properties();
2144        let common_props = self.get_common_properties();
2145        let (width, height, x, y, padding_x, padding_y) = {
2146            let common_props = borrow!(common_props);
2147            (
2148                common_props.width.clone(),
2149                common_props.height.clone(),
2150                common_props.x.clone(),
2151                common_props.y.clone(),
2152                common_props.padding_x.clone(),
2153                common_props.padding_y.clone(),
2154            )
2155        };
2156        let children = self.children.get();
2157        let has_children = !children.is_empty();
2158        let bounds_name = format!("layout bounds (node id: {})", self.id.0);
2159        let bounds_transform_and_bounds = self_transform_and_bounds.clone();
2160        let bounds = Property::computed_with_cutoff_and_name(
2161            move || bounds_transform_and_bounds.get().bounds,
2162            &[self_transform_and_bounds.untyped()],
2163            <(f64, f64)>::eq,
2164            &bounds_name,
2165        );
2166        let mut own_deps = vec![bounds.untyped()];
2167        if has_children {
2168            own_deps.extend([width.untyped(), height.untyped(), x.untyped(), y.untyped()]);
2169        } else {
2170            own_deps.push(layout_properties.untyped());
2171        }
2172
2173        let own_hull_name = format!("own layout hull (node id: {})", self.id.0);
2174        let own_hull = Property::computed_with_cutoff_and_name(
2175            move || {
2176                let bounds = bounds.get();
2177                let (contributes_x, contributes_y) = if has_children {
2178                    (
2179                        layout_axis_can_contribute_from_parts(width.get(), x.get()),
2180                        layout_axis_can_contribute_from_parts(height.get(), y.get()),
2181                    )
2182                } else {
2183                    let layout_properties = layout_properties.get();
2184                    (
2185                        layout_axis_can_contribute(&layout_properties, Axis::X),
2186                        layout_axis_can_contribute(&layout_properties, Axis::Y),
2187                    )
2188                };
2189                LayoutHull::from_axis_ranges(
2190                    contributes_x.then_some((0.0, bounds.0)),
2191                    contributes_y.then_some((0.0, bounds.1)),
2192                )
2193            },
2194            &own_deps,
2195            crate::layout_hulls_equivalent,
2196            &own_hull_name,
2197        );
2198
2199        if !has_children {
2200            self.subtree_layout_hull.replace_with(own_hull);
2201            return;
2202        }
2203
2204        let mut projected_child_hulls = Vec::with_capacity(children.len());
2205        for child in children.iter() {
2206            let child_cp = child.get_common_properties();
2207            let child_layout_role = borrow!(child_cp).layout_role.clone();
2208            let projection_name = format!(
2209                "projected child layout hull (parent id: {}, child id: {})",
2210                self.id.0, child.id.0
2211            );
2212            projected_child_hulls.push(crate::projected_child_layout_hull_property(
2213                self_transform_and_bounds.clone(),
2214                padding_x.clone(),
2215                padding_y.clone(),
2216                child.transform_and_bounds.clone(),
2217                child.subtree_layout_hull.clone(),
2218                child_layout_role,
2219                &projection_name,
2220            ));
2221        }
2222
2223        let mut deps = vec![own_hull.untyped(), padding_x.untyped(), padding_y.untyped()];
2224        deps.extend(projected_child_hulls.iter().map(Property::untyped));
2225
2226        let property_name = format!("subtree layout hull (node id: {})", self.id.0);
2227        self.subtree_layout_hull
2228            .replace_with(Property::computed_with_cutoff_and_name(
2229                move || {
2230                    let mut hull = own_hull.get();
2231                    let mut children_hull = LayoutHull::default();
2232                    for projected_hull in &projected_child_hulls {
2233                        children_hull = children_hull.union(projected_hull.get());
2234                    }
2235                    hull = hull.union(add_symmetric_padding_to_content_layout_hull(
2236                        children_hull,
2237                        padding_x.get(),
2238                        padding_y.get(),
2239                    ));
2240
2241                    hull
2242                },
2243                &deps,
2244                crate::layout_hulls_equivalent,
2245                &property_name,
2246            ));
2247    }
2248
2249    pub fn inherit_suspend(self: &Rc<Self>, node: &Rc<Self>) {
2250        self.parent_binding_sources.borrow_mut().take();
2251        let cp = self.get_common_properties();
2252        let self_suspended = borrow!(cp)._suspended.clone();
2253        let parent_suspended = node.suspended.clone();
2254        let deps = [parent_suspended.untyped(), self_suspended.untyped()];
2255        self.suspended.replace_with(Property::computed(
2256            move || {
2257                self_suspended
2258                    .get()
2259                    .unwrap_or_else(|| parent_suspended.get())
2260            },
2261            &deps,
2262        ));
2263    }
2264
2265    pub fn generate_children(
2266        self: &Rc<Self>,
2267        templates: impl IntoIterator<Item = (Rc<dyn InstanceNode>, Rc<RuntimePropertiesStackFrame>)>,
2268        context: &Rc<RuntimeContext>,
2269        parent_frame: &Property<Option<ExpandedNodeIdentifier>>,
2270        is_mount: bool,
2271    ) -> Vec<Rc<ExpandedNode>> {
2272        self.generate_children_with_liquid_glass_scope(
2273            templates,
2274            context,
2275            parent_frame,
2276            &self.liquid_glass_scope,
2277            is_mount,
2278        )
2279    }
2280
2281    pub fn generate_children_with_liquid_glass_scope(
2282        self: &Rc<Self>,
2283        templates: impl IntoIterator<Item = (Rc<dyn InstanceNode>, Rc<RuntimePropertiesStackFrame>)>,
2284        context: &Rc<RuntimeContext>,
2285        parent_frame: &Property<Option<ExpandedNodeIdentifier>>,
2286        liquid_glass_scope: &Property<Option<NativeLiquidGlassScope>>,
2287        is_mount: bool,
2288    ) -> Vec<Rc<ExpandedNode>> {
2289        let new_children = self.create_children_detached(templates, context, &Rc::downgrade(&self));
2290        let res = if is_mount {
2291            self.attach_children_with_liquid_glass_scope(
2292                new_children,
2293                context,
2294                parent_frame,
2295                liquid_glass_scope,
2296            )
2297        } else {
2298            for child in new_children.iter() {
2299                child.recurse_control_flow_expansion(context);
2300            }
2301
2302            new_children
2303        };
2304        res
2305    }
2306
2307    /// This method recursively updates all node properties. When dirty-dag exists, this won't
2308    /// need to be here since all property dependencies can be set up and removed during mount/unmount
2309    pub fn run_lifecycle_handlers(
2310        self: &Rc<Self>,
2311        handler_key: &str,
2312        context: &Rc<RuntimeContext>,
2313    ) {
2314        if let Some(ref registry) = borrow!(self.instance_node).base().handler_registry {
2315            if !self.suspended.get() {
2316                for handler in borrow!(registry)
2317                    .handlers
2318                    .get(handler_key)
2319                    .unwrap_or(&Vec::new())
2320                {
2321                    (handler.function)(
2322                        Rc::clone(&*borrow!(self.properties)),
2323                        &self.get_node_context(context),
2324                        None,
2325                    )
2326                }
2327            }
2328        }
2329    }
2330
2331    pub fn recurse_update(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
2332        // Settle queued reactive effects before checking which subtrees still need
2333        // imperative work, then flush any effects produced by that non-reactive pass.
2334        context.drain_node_effects();
2335        self.recurse_update_mounted(context);
2336        context.drain_node_effects();
2337    }
2338
2339    fn recurse_update_mounted(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
2340        if !self.subtree_requires_non_reactive_update.get() {
2341            return;
2342        }
2343        let instance_node = Rc::clone(&*borrow!(self.instance_node));
2344        if instance_node.requires_non_reactive_update(self) {
2345            instance_node.update(&self, context);
2346        }
2347        let mut subtree_requires_non_reactive_update =
2348            Rc::clone(&*borrow!(self.instance_node)).requires_non_reactive_update(self);
2349        let children = borrow!(self.mounted_children).clone();
2350        for child in children.iter() {
2351            child.recurse_update_mounted(context);
2352            subtree_requires_non_reactive_update |=
2353                child.subtree_requires_non_reactive_update.get();
2354        }
2355        let sidecar_children = borrow!(self.sidecar_children).clone();
2356        for child in sidecar_children.iter() {
2357            child.recurse_update_mounted(context);
2358            subtree_requires_non_reactive_update |=
2359                child.subtree_requires_non_reactive_update.get();
2360        }
2361        self.subtree_requires_non_reactive_update
2362            .set(subtree_requires_non_reactive_update);
2363    }
2364
2365    pub fn recurse_sync_import_settings(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
2366        if borrow!(self.instance_node).base().flags().is_component {
2367            self.sync_imported_settings(context);
2368        }
2369        for child in self.children.get().iter() {
2370            child.recurse_sync_import_settings(context);
2371        }
2372        for child in borrow!(self.sidecar_children).iter() {
2373            child.recurse_sync_import_settings(context);
2374        }
2375    }
2376
2377    fn recurse_reapply_runtime_settings_for_component_owner(
2378        self: &Rc<Self>,
2379        owner_component_id: ExpandedNodeIdentifier,
2380        context: &Rc<RuntimeContext>,
2381    ) {
2382        if self.is_import_settings_node() {
2383            return;
2384        }
2385
2386        let owned_by_component = self
2387            .containing_component
2388            .upgrade()
2389            .map(|component| component.id == owner_component_id)
2390            .unwrap_or(false);
2391
2392        if owned_by_component {
2393            self.reset_removed_runtime_properties.set(true);
2394            let instance = borrow!(self.instance_node).clone();
2395            self.recreate_with_new_data(instance, context);
2396        }
2397
2398        let children = self.children.get();
2399        for child in children.iter() {
2400            child.recurse_reapply_runtime_settings_for_component_owner(owner_component_id, context);
2401        }
2402
2403        let sidecar_children = borrow!(self.sidecar_children).clone();
2404        for child in sidecar_children.iter() {
2405            child.recurse_reapply_runtime_settings_for_component_owner(owner_component_id, context);
2406        }
2407    }
2408
2409    pub fn recurse_control_flow_expansion(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
2410        borrow!(self.instance_node)
2411            .clone()
2412            .handle_control_flow_node_expansion(&self, context);
2413    }
2414
2415    pub fn recurse_mount(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
2416        if self.attached.get() == 0 {
2417            // Materialize projected children before mount so Slot can resolve them.
2418            borrow!(self.instance_node)
2419                .clone()
2420                .handle_setup_projected_children(&self, context);
2421
2422            // Pre-mount pass to make sure projected children are expanded and we compute the correct flattened list.
2423            if let Some(projected_children) = borrow!(self.expanded_projected_children).as_ref() {
2424                for projected_child in projected_children {
2425                    projected_child.recurse_control_flow_expansion(context);
2426                }
2427                // This is needed to resolve slot connections in a single tick.
2428                self.compute_flattened_projected_children();
2429            }
2430            self.attached.set(self.attached.get() + 1);
2431            self.start_self_enter_transition(context);
2432            context.add_to_cache(&self);
2433            if let Some(ref registry) = borrow!(self.instance_node).base().handler_registry {
2434                for handler in borrow!(registry)
2435                    .handlers
2436                    .get("mount")
2437                    .unwrap_or(&Vec::new())
2438                {
2439                    (handler.function)(
2440                        Rc::clone(&*borrow!(self.properties)),
2441                        &self.get_node_context(context),
2442                        None,
2443                    )
2444                }
2445            }
2446            borrow!(self.instance_node)
2447                .clone()
2448                .handle_mount(&self, context);
2449            context.register_expanded_node_effect_property_named(
2450                self,
2451                &self.changed_listener,
2452                "changed listener",
2453            );
2454        }
2455    }
2456
2457    pub fn recurse_unmount(self: Rc<Self>, context: &Rc<RuntimeContext>) {
2458        // WARNING: do NOT make recurse_unmount result in expr evaluation,
2459        // in this case: do not refer to self.children expression.
2460        // expr evaluation in this context can trigger get's of "old data", ie try to get
2461        // an index of a for loop source that doesn't exist anymore
2462        if self.attached.get() == 1 {
2463            self.attached.set(self.attached.get() - 1);
2464            context.remove_from_cache(&self);
2465            for child in borrow!(self.mounted_children).iter() {
2466                Rc::clone(child).recurse_unmount(context);
2467            }
2468            borrow!(self.instance_node).handle_unmount(&self, context);
2469            if let Some(ref registry) = borrow!(self.instance_node).base().handler_registry {
2470                for handler in borrow!(registry)
2471                    .handlers
2472                    .get("unmount")
2473                    .unwrap_or(&Vec::new())
2474                {
2475                    (handler.function)(
2476                        Rc::clone(&*borrow!(self.properties)),
2477                        &self.get_node_context(context),
2478                        None,
2479                    )
2480                }
2481            }
2482
2483            if self.instance_node.borrow().base().flags().layer == Layer::Canvas {
2484                context.enqueue_canvas_node_removal(
2485                    self.occlusion.get().render_layer_id,
2486                    self.id.to_u32(),
2487                );
2488                context.set_canvas_dirty(self.occlusion.get().render_layer_id);
2489            }
2490
2491            // Needed because occlusion updates are only sent on diffs so we reset it when unmounting
2492            self.occlusion.set(Default::default());
2493            self.browser_content_layer_id.set(None);
2494            self.changed_listener.replace_with(Property::default());
2495            self.selector_classes_listener
2496                .replace_with(Property::default());
2497            borrow_mut!(self.subscriptions).clear();
2498            borrow_mut!(self.active_children).clear();
2499            borrow_mut!(self.exiting_children).clear();
2500            borrow_mut!(self.exiting_child_generations).clear();
2501            borrow_mut!(self.mounted_children).clear();
2502            borrow_mut!(self.imported_settings_layers).clear();
2503            borrow_mut!(self.imported_settings_signature).clear();
2504            borrow_mut!(self.resolved_property_columns).clear();
2505            borrow_mut!(self.resolved_property_provenance).clear();
2506            self.reset_removed_runtime_properties.set(false);
2507            self.active_children_view.set(Vec::new());
2508            self.exiting_children_view.set(Vec::new());
2509            self.transition_phase.set(TRANSITION_PHASE_IDLE);
2510            self.transition_takeover.set(false);
2511            self.transition_selector_classes.borrow_mut().take();
2512            self.deactivate_transition_clock();
2513            self.enter_cleanup_active.set(false);
2514            self.enter_cleanup_listener
2515                .replace_with(Property::new_with_name((), "enter transition cleanup"));
2516            self.exit_cleanup_active.set(false);
2517            self.exit_cleanup_listener.replace_with(Property::default());
2518        }
2519    }
2520
2521    pub fn is_import_settings_node(&self) -> bool {
2522        borrow!(self.instance_node)
2523            .base()
2524            .template_node_type_id
2525            .as_ref()
2526            .and_then(|type_id| type_id.get_pascal_identifier())
2527            .as_deref()
2528            == Some("ImportSettings")
2529    }
2530
2531    pub fn is_in_import_settings_subtree(&self) -> bool {
2532        let mut current = borrow!(self.render_parent).upgrade();
2533        while let Some(node) = current {
2534            if node.is_import_settings_node() {
2535                return true;
2536            }
2537            current = borrow!(node.render_parent).upgrade();
2538        }
2539        false
2540    }
2541
2542    fn collect_imported_settings_from_node(
2543        node: &Rc<Self>,
2544        in_import_settings: bool,
2545        descend_components: bool,
2546        providers: &mut Vec<RuntimeSettingsLayer>,
2547    ) {
2548        if node.is_import_settings_node() {
2549            let sidecar_children = borrow!(node.sidecar_children).clone();
2550            for child in sidecar_children.iter() {
2551                Self::collect_imported_settings_from_node(child, true, true, providers);
2552            }
2553            return;
2554        }
2555
2556        if borrow!(node.instance_node).base().flags().is_component {
2557            if in_import_settings {
2558                let base = borrow!(node.instance_node);
2559                let component_settings = base.base().component_settings.clone();
2560                let provider_type_id = base
2561                    .base()
2562                    .template_node_type_id
2563                    .clone()
2564                    .unwrap_or_default();
2565                drop(base);
2566                if let Some(settings) = component_settings {
2567                    if !settings.is_empty() {
2568                        providers.push(RuntimeSettingsLayer {
2569                            provider_id: node.id,
2570                            provider_type_id,
2571                            provider_stack: node.stack.push(borrow!(node.properties_scope).clone()),
2572                            settings,
2573                        });
2574                    }
2575                }
2576                let children = node.children.get();
2577                for child in children.iter() {
2578                    Self::collect_imported_settings_from_node(child, false, true, providers);
2579                }
2580                let sidecar_children = borrow!(node.sidecar_children).clone();
2581                for child in sidecar_children.iter() {
2582                    Self::collect_imported_settings_from_node(child, false, true, providers);
2583                }
2584                return;
2585            }
2586            if !descend_components {
2587                return;
2588            }
2589        }
2590
2591        let children = node.children.get();
2592        for child in children.iter() {
2593            Self::collect_imported_settings_from_node(
2594                child,
2595                in_import_settings,
2596                descend_components,
2597                providers,
2598            );
2599        }
2600        let sidecar_children = borrow!(node.sidecar_children).clone();
2601        for child in sidecar_children.iter() {
2602            Self::collect_imported_settings_from_node(
2603                child,
2604                in_import_settings,
2605                descend_components,
2606                providers,
2607            );
2608        }
2609    }
2610
2611    pub fn sync_imported_settings(self: &Rc<Self>, context: &Rc<RuntimeContext>) {
2612        let mut providers = Vec::new();
2613        let children = self.children.get();
2614        for child in children.iter() {
2615            Self::collect_imported_settings_from_node(child, false, false, &mut providers);
2616        }
2617        let sidecar_children = borrow!(self.sidecar_children).clone();
2618        for child in sidecar_children.iter() {
2619            Self::collect_imported_settings_from_node(child, false, false, &mut providers);
2620        }
2621
2622        let signature = providers
2623            .iter()
2624            .map(|layer| RuntimeSettingsSignatureEntry {
2625                provider_id: layer.provider_id,
2626                provider_type_id: layer.provider_type_id.clone(),
2627            })
2628            .collect::<Vec<_>>();
2629        if *borrow!(self.imported_settings_signature) == signature {
2630            return;
2631        }
2632
2633        *borrow_mut!(self.imported_settings_signature) = signature;
2634        *borrow_mut!(self.imported_settings_layers) = providers;
2635
2636        let root_children = self.children.get();
2637        for child in root_children.iter() {
2638            if child.is_import_settings_node() {
2639                continue;
2640            }
2641            child.recurse_reapply_runtime_settings_for_component_owner(self.id, context);
2642            child.recurse_emit_mount_updates();
2643        }
2644    }
2645
2646    pub fn recurse_render_queue(
2647        self: &Rc<Self>,
2648        ctx: &Rc<RuntimeContext>,
2649        rcs: &mut dyn RenderContext,
2650    ) {
2651        let cp = self.get_common_properties();
2652        let cp = borrow!(cp);
2653        if cp.unclippable.get().unwrap_or(false) {
2654            ctx.queue_render(Rc::clone(&self));
2655        } else {
2656            self.recurse_render(ctx, rcs);
2657        }
2658    }
2659
2660    pub(crate) fn render_parent_node(&self) -> Option<Rc<ExpandedNode>> {
2661        borrow!(self.render_parent).upgrade()
2662    }
2663
2664    pub(crate) fn is_unclippable(&self) -> bool {
2665        let cp = self.get_common_properties();
2666        let cp = borrow!(cp);
2667        cp.unclippable.get().unwrap_or(false)
2668    }
2669
2670    pub(crate) fn recurse_render_filtered(
2671        self: &Rc<Self>,
2672        ctx: &Rc<RuntimeContext>,
2673        rcs: &mut dyn RenderContext,
2674        render_path_nodes: &HashSet<ExpandedNodeIdentifier>,
2675        dirty_nodes: &HashSet<ExpandedNodeIdentifier>,
2676        stats: &mut FilteredRenderStats,
2677    ) {
2678        stats.path_nodes_visited += 1;
2679        borrow!(self.instance_node).handle_pre_render(&self, ctx, rcs);
2680        for child in self.children.get().iter().rev() {
2681            if render_path_nodes.contains(&child.id) {
2682                child.recurse_render_filtered(ctx, rcs, render_path_nodes, dirty_nodes, stats);
2683            } else {
2684                stats.skipped_subtrees += 1;
2685            }
2686        }
2687        if dirty_nodes.contains(&self.id) {
2688            borrow!(self.instance_node).render(&self, ctx, rcs);
2689            stats.dirty_nodes_rendered += 1;
2690        }
2691        borrow!(self.instance_node).handle_post_render(&self, ctx, rcs);
2692    }
2693
2694    pub fn recurse_render(self: &Rc<Self>, ctx: &Rc<RuntimeContext>, rcs: &mut dyn RenderContext) {
2695        borrow!(self.instance_node).handle_pre_render(&self, ctx, rcs);
2696        for child in self.children.get().iter().rev() {
2697            child.recurse_render_queue(ctx, rcs);
2698        }
2699        borrow!(self.instance_node).render(&self, ctx, rcs);
2700        borrow!(self.instance_node).handle_post_render(&self, ctx, rcs);
2701    }
2702
2703    /// Manages unpacking an `Rc<RefCell<PaxValue>>`, downcasting into
2704    /// the parameterized `target_type`, and executing a provided closure `body` in the
2705    /// context of that unwrapped variant (including support for mutable operations),
2706    /// the closure is executed.  Used at least by calculating properties in `expand_node` and
2707    /// passing `&mut self` into event handlers (where the typed `self` is retrieved from an instance of `PaxValue`)
2708    pub fn with_properties_unwrapped<T: ToFromPaxAny, R>(
2709        &self,
2710        callback: impl FnOnce(&mut T) -> R,
2711    ) -> R {
2712        self.try_with_properties_unwrapped(callback)
2713            .expect("properties not of expected type")
2714    }
2715
2716    pub fn try_with_properties_unwrapped<T: ToFromPaxAny, R>(
2717        &self,
2718        callback: impl FnOnce(&mut T) -> R,
2719    ) -> Option<R> {
2720        // Borrow the contents of the RefCell mutably.
2721        let properties = borrow_mut!(self.properties);
2722        let mut borrowed = borrow_mut!(properties);
2723        // Downcast the unwrapped value to the specified `target_type` (or panic)
2724        let Ok(mut val) = T::mut_from_pax_any(&mut *borrowed) else {
2725            return None;
2726        };
2727        Some(callback(&mut val))
2728    }
2729
2730    pub fn recurse_visit_postorder(self: &Rc<Self>, func: &mut impl FnMut(&Rc<Self>)) {
2731        // NOTE: This is to make sure the projected children list is updated before trying to access children,
2732        // to make stacker/scroller behave correctly when the number of children is dynamic.
2733        self.compute_flattened_projected_children();
2734        for child in self.children.get().iter().rev() {
2735            child.recurse_visit_postorder(func)
2736        }
2737        func(self);
2738    }
2739
2740    pub fn get_node_context(self: &Rc<Self>, ctx: &Rc<RuntimeContext>) -> NodeContext {
2741        let globals = ctx.globals();
2742        let viewport = super::viewport_info_property(&globals.viewport);
2743        let target = Property::new(globals.target);
2744        let t_and_b = self.transform_and_bounds.clone();
2745        let deps = [t_and_b.untyped()];
2746        let bounds_name = format!("node context bounds (node id: {})", self.id.0);
2747        let bounds_self = Property::computed_with_cutoff_and_name(
2748            move || t_and_b.get().bounds,
2749            &deps,
2750            <(f64, f64)>::eq,
2751            &bounds_name,
2752        );
2753        let render_parent = borrow!(self.render_parent).upgrade();
2754        let t_and_b_parent = render_parent
2755            .as_ref()
2756            .map(|parent| parent.transform_and_bounds.clone())
2757            .unwrap_or_else(|| globals.viewport.clone());
2758        let (parent_padding_x, parent_padding_y) = render_parent
2759            .as_ref()
2760            .map(|parent| {
2761                let common_props = parent.get_common_properties();
2762                let common_props = borrow!(common_props);
2763                (
2764                    common_props.padding_x.clone(),
2765                    common_props.padding_y.clone(),
2766                )
2767            })
2768            .unwrap_or_default();
2769        let deps = [
2770            t_and_b_parent.untyped(),
2771            parent_padding_x.untyped(),
2772            parent_padding_y.untyped(),
2773        ];
2774        let bounds_parent = Property::computed(
2775            move || {
2776                apply_padding_frame(
2777                    t_and_b_parent.get(),
2778                    parent_padding_x.get(),
2779                    parent_padding_y.get(),
2780                )
2781                .bounds
2782            },
2783            &deps,
2784        );
2785
2786        let owns_projected_children = borrow!(self.instance_node).base().flags().is_component
2787            || borrow!(self.expanded_projected_children).is_some();
2788
2789        let projected_children_count = if owns_projected_children {
2790            self.flattened_projected_children_count.clone()
2791        } else {
2792            self.containing_component
2793                .upgrade()
2794                .map(|v| v.flattened_projected_children_count.clone())
2795                .unwrap_or_default()
2796        };
2797
2798        let projected_children = if owns_projected_children {
2799            self.expanded_and_flattened_projected_children.clone()
2800        } else {
2801            self.containing_component
2802                .upgrade()
2803                .map(|v| v.expanded_and_flattened_projected_children.clone())
2804                .unwrap_or_default()
2805        };
2806
2807        let projected_children_changed = if owns_projected_children {
2808            self.projected_children_changed.clone()
2809        } else {
2810            self.containing_component
2811                .upgrade()
2812                .map(|v| v.projected_children_changed.clone())
2813                .unwrap_or_default()
2814        };
2815        // Normalize semantic payload for container consumers. This hides the
2816        // engine transport distinction (`Owned` vs `Projected`) behind the
2817        // single concept "received children".
2818        let received_children = match borrow!(self.instance_node).received_children_source() {
2819            ReceivedChildrenSource::Owned => self.active_children_view.clone(),
2820            ReceivedChildrenSource::Projected => {
2821                if owns_projected_children {
2822                    self.expanded_and_flattened_projected_children.clone()
2823                } else {
2824                    self.containing_component
2825                        .upgrade()
2826                        .map(|v| v.expanded_and_flattened_projected_children.clone())
2827                        .unwrap_or_default()
2828                }
2829            }
2830        };
2831        let received_children_count_source = received_children.clone();
2832        let received_children_count = Property::computed(
2833            move || received_children_count_source.get().len(),
2834            &[received_children.untyped()],
2835        );
2836        let received_children_signal = received_children.clone();
2837        let received_children_changed = Property::computed(
2838            move || {
2839                let _ = received_children_signal.get();
2840            },
2841            &[received_children.untyped()],
2842        );
2843        // Normalize the corresponding exit-retained payload set. These nodes
2844        // are no longer active content, but containers may still need them for
2845        // ghosting or overlay placement while `@out` transitions finish.
2846        let retained_received_children =
2847            match borrow!(self.instance_node).received_children_source() {
2848                ReceivedChildrenSource::Owned => self.exiting_children_view.clone(),
2849                ReceivedChildrenSource::Projected
2850                    if borrow!(self.expanded_projected_children).is_some() =>
2851                {
2852                    self.exiting_children_view.clone()
2853                }
2854                ReceivedChildrenSource::Projected => Property::default(),
2855            };
2856        let retained_received_children_signal = retained_received_children.clone();
2857        let retained_received_children_changed = Property::computed(
2858            move || {
2859                let _ = retained_received_children_signal.get();
2860            },
2861            &[retained_received_children.untyped()],
2862        );
2863
2864        let last_frame = Rc::new(RefCell::new(globals.elapsed_frames.get()));
2865        let suspended = self.suspended.clone();
2866        let elapsed_frames = globals.elapsed_frames.clone();
2867        let elapsed_millis = globals.elapsed_millis.clone();
2868        let deps = [elapsed_frames.untyped(), suspended.untyped()];
2869        // TODO: this still triggers the dirty dag dependencies of elapsed
2870        // frames even if the value is the same. Try to make it not trigger
2871        // dependencides when frozen
2872        let elapsed_frames_frozen_if_suspended = Property::computed(
2873            move || {
2874                if suspended.get() {
2875                    *borrow!(last_frame)
2876                } else {
2877                    let val = elapsed_frames.get();
2878                    *borrow_mut!(last_frame) = val;
2879                    val
2880                }
2881            },
2882            &deps,
2883        );
2884        // PREF: possibly change this to just take a reference to expanded node, and
2885        // expose methods to retrieve these values from the expanded noe when
2886        // needed, instead of re-creating/copying
2887        NodeContext {
2888            slot_index: self.slot_index.clone(),
2889            local_stack_frame: Rc::clone(&self.stack),
2890            expanded_node: Rc::downgrade(&self),
2891            containing_component: Weak::clone(&self.containing_component),
2892            elapsed_frames: elapsed_frames_frozen_if_suspended,
2893            elapsed_millis,
2894            gyro: globals.gyro.clone(),
2895            accel: globals.accel.clone(),
2896            bounds_self,
2897            bounds_parent,
2898            measured_size: self.measured_size.clone(),
2899            subtree_layout_hull: self.subtree_layout_hull.clone(),
2900            runtime_context: ctx.clone(),
2901            platform: globals.platform.clone(),
2902            os: globals.os.clone(),
2903            target,
2904            viewport,
2905            get_elapsed_millis: globals.get_elapsed_millis,
2906            projected_children_count,
2907            projected_children,
2908            node_transform_and_bounds: self.transform_and_bounds.get(),
2909            projected_children_changed,
2910            received_children,
2911            received_children_count,
2912            received_children_changed,
2913            retained_received_children,
2914            retained_received_children_changed,
2915            #[cfg(feature = "designtime")]
2916            designtime: globals.designtime.clone(),
2917        }
2918    }
2919
2920    pub fn get_common_properties(&self) -> Rc<RefCell<CommonProperties>> {
2921        Rc::clone(&*borrow!(self.common_properties))
2922    }
2923
2924    pub fn is_layout_breakout(&self) -> bool {
2925        let common_props = self.get_common_properties();
2926        let is_breakout = borrow!(common_props).layout_role.get() == Some(LayoutRole::Breakout);
2927        is_breakout
2928    }
2929
2930    /// Determines whether the provided ray, orthogonal to the view plane,
2931    /// intersects this `ExpandedNode`.
2932    pub fn ray_cast_test(&self, ray: Point2<Window>) -> bool {
2933        let cp = borrow!(self.common_properties);
2934
2935        // skip raycast if false
2936        if !borrow!(&*cp)._raycastable.get().unwrap_or(true) {
2937            return false;
2938        }
2939        drop(cp);
2940        borrow!(self.instance_node).ray_cast_test(self, ray)
2941    }
2942
2943    pub fn compute_flattened_projected_children(&self) {
2944        // All of this should ideally be reactively updated,
2945        // but currently doesn't exist a way to "listen to"
2946        // an entire node tree, and generate the flattened list
2947        // only when changed.
2948        if let Some(projected_children) = borrow!(self.expanded_projected_children).as_ref() {
2949            let new_flattened = flatten_expanded_nodes_for_projection(projected_children);
2950            let old_and_new_filtered_same =
2951                self.expanded_and_flattened_projected_children
2952                    .read(|flattened| {
2953                        flattened
2954                            .iter()
2955                            .map(|n| n.id)
2956                            .eq(new_flattened.iter().map(|n| n.id))
2957                    });
2958
2959            if !old_and_new_filtered_same {
2960                self.flattened_projected_children_count
2961                    .set(new_flattened.len());
2962                self.expanded_and_flattened_projected_children
2963                    .set(new_flattened);
2964                for (i, projected_child) in self
2965                    .expanded_and_flattened_projected_children
2966                    .get()
2967                    .iter()
2968                    .enumerate()
2969                {
2970                    if projected_child.slot_index.get() != Some(i) {
2971                        projected_child.slot_index.set(Some(i));
2972                    };
2973                }
2974            }
2975        }
2976    }
2977
2978    dispatch_event_handler!(dispatch_scroll, Scroll, SCROLL_HANDLERS, true);
2979    dispatch_event_handler!(dispatch_touch_start, TouchStart, TOUCH_START_HANDLERS, true);
2980
2981    dispatch_event_handler!(dispatch_touch_move, TouchMove, TOUCH_MOVE_HANDLERS, true);
2982    dispatch_event_handler!(dispatch_touch_end, TouchEnd, TOUCH_END_HANDLERS, true);
2983    dispatch_event_handler!(
2984        dispatch_touch_cancel,
2985        TouchCancel,
2986        TOUCH_CANCEL_HANDLERS,
2987        true
2988    );
2989    dispatch_event_handler!(dispatch_key_down, KeyDown, KEY_DOWN_HANDLERS, false);
2990    dispatch_event_handler!(dispatch_key_up, KeyUp, KEY_UP_HANDLERS, false);
2991    dispatch_event_handler!(dispatch_key_press, KeyPress, KEY_PRESS_HANDLERS, false);
2992    dispatch_event_handler!(
2993        dispatch_checkbox_change,
2994        CheckboxChange,
2995        CHECKBOX_CHANGE_HANDLERS,
2996        true
2997    );
2998    dispatch_event_handler!(
2999        dispatch_slider_change,
3000        SliderChange,
3001        SLIDER_CHANGE_HANDLERS,
3002        true
3003    );
3004    dispatch_event_handler!(
3005        dispatch_textbox_change,
3006        TextboxChange,
3007        TEXTBOX_CHANGE_HANDLERS,
3008        true
3009    );
3010    dispatch_event_handler!(dispatch_text_input, TextInput, TEXT_INPUT_HANDLERS, true);
3011    dispatch_event_handler!(
3012        dispatch_textbox_input,
3013        TextboxInput,
3014        TEXTBOX_INPUT_HANDLERS,
3015        true
3016    );
3017    dispatch_event_handler!(
3018        dispatch_button_click,
3019        ButtonClick,
3020        BUTTON_CLICK_HANDLERS,
3021        true
3022    );
3023    dispatch_event_handler!(
3024        dispatch_photo_picker_change,
3025        PhotoPickerChange,
3026        PHOTO_PICKER_CHANGE_HANDLERS,
3027        true
3028    );
3029    dispatch_event_handler!(dispatch_mouse_down, MouseDown, MOUSE_DOWN_HANDLERS, true);
3030    dispatch_event_handler!(dispatch_mouse_up, MouseUp, MOUSE_UP_HANDLERS, true);
3031    dispatch_event_handler!(dispatch_mouse_move, MouseMove, MOUSE_MOVE_HANDLERS, true);
3032    dispatch_event_handler!(dispatch_mouse_over, MouseOver, MOUSE_OVER_HANDLERS, false);
3033    dispatch_event_handler!(dispatch_mouse_out, MouseOut, MOUSE_OUT_HANDLERS, false);
3034    dispatch_event_handler!(
3035        dispatch_double_click,
3036        DoubleClick,
3037        DOUBLE_CLICK_HANDLERS,
3038        true
3039    );
3040    dispatch_event_handler!(
3041        dispatch_context_menu,
3042        ContextMenu,
3043        CONTEXT_MENU_HANDLERS,
3044        true
3045    );
3046    fn dispatch_pointer_activation(
3047        self: &Rc<Self>,
3048        event: Event<Click>,
3049        source: PointerActivationSource,
3050        ctx: &Rc<RuntimeContext>,
3051    ) -> bool {
3052        let has_click = self.has_event_handlers(CLICK_HANDLERS);
3053        let has_tap = self.has_event_handlers(TAP_HANDLERS);
3054        let handler_key = match (has_click, has_tap, source) {
3055            (true, true, PointerActivationSource::Mouse) => Some(CLICK_HANDLERS),
3056            (true, true, PointerActivationSource::Touch) => Some(TAP_HANDLERS),
3057            (true, false, _) => Some(CLICK_HANDLERS),
3058            (false, true, _) => Some(TAP_HANDLERS),
3059            (false, false, _) => None,
3060        };
3061
3062        if let Some(handler_key) = handler_key {
3063            self.run_event_handlers_for_key(handler_key, &event, ctx);
3064        }
3065
3066        if let Some(parent) = self.template_parent.upgrade() {
3067            return parent.dispatch_pointer_activation(event, source, ctx);
3068        }
3069        event.cancelled()
3070    }
3071
3072    pub fn dispatch_click(
3073        self: &Rc<Self>,
3074        event: Event<Click>,
3075        _globals: &Globals,
3076        ctx: &Rc<RuntimeContext>,
3077    ) -> bool {
3078        self.dispatch_pointer_activation(event, PointerActivationSource::Mouse, ctx)
3079    }
3080
3081    pub fn dispatch_tap(
3082        self: &Rc<Self>,
3083        event: Event<Click>,
3084        _globals: &Globals,
3085        ctx: &Rc<RuntimeContext>,
3086    ) -> bool {
3087        self.dispatch_pointer_activation(event, PointerActivationSource::Touch, ctx)
3088    }
3089
3090    dispatch_event_handler!(dispatch_wheel, Wheel, WHEEL_HANDLERS, true);
3091    dispatch_event_handler!(dispatch_drop, Drop, DROP_HANDLERS, true);
3092    dispatch_event_handler!(dispatch_gyro, Gyro, GYRO_HANDLERS, false);
3093    dispatch_event_handler!(dispatch_accel, Accel, ACCEL_HANDLERS, false);
3094    dispatch_event_handler!(dispatch_focus, Focus, FOCUSED_HANDLERS, false);
3095    dispatch_event_handler!(
3096        dispatch_select_start,
3097        SelectStart,
3098        SELECT_START_HANDLERS,
3099        false
3100    );
3101
3102    pub fn dispatch_custom_event(
3103        self: &Rc<Self>,
3104        identifier: &str,
3105        ctx: &Rc<RuntimeContext>,
3106    ) -> Result<(), String> {
3107        let component_origin_instance = borrow!(self.instance_node);
3108        let registry = component_origin_instance
3109            .base()
3110            .handler_registry
3111            .as_ref()
3112            .ok_or_else(|| "no registry present".to_owned())?;
3113
3114        let parent_component = self
3115            .containing_component
3116            .upgrade()
3117            .ok_or_else(|| "can't dispatch from root (has no parent)".to_owned())?;
3118        let properties = borrow!(parent_component.properties);
3119
3120        for handler in borrow!(registry)
3121            .handlers
3122            .get(identifier)
3123            .expect("presence should have been checked when added to custom_event_queue")
3124        {
3125            (handler.function)(Rc::clone(&*properties), &self.get_node_context(ctx), None)
3126        }
3127        Ok(())
3128    }
3129
3130    // Sets measured bounds when the chassis or container layout resolves them empirically.
3131    pub fn set_measured_size(self: &Rc<ExpandedNode>, width: f64, height: f64) {
3132        self.measured_size.set(Some((width, height)));
3133    }
3134
3135    // Backward-compatible alias used by chassis interrupt plumbing.
3136    pub fn chassis_resize_request(self: &Rc<ExpandedNode>, width: f64, height: f64) {
3137        self.set_measured_size(width, height);
3138    }
3139
3140    /// Helper method that returns a collection of common properties
3141    /// related to layout (position, size, scale, anchor, etc),
3142    pub fn layout_properties(self: &Rc<ExpandedNode>) -> Property<LayoutProperties> {
3143        let common_props = self.get_common_properties();
3144        let common_props = borrow!(common_props);
3145        let cp_width = common_props.width.clone();
3146        let cp_height = common_props.height.clone();
3147        let cp_transform = common_props.transform.clone();
3148        let cp_anchor_x = common_props.anchor_x.clone();
3149        let cp_anchor_y = common_props.anchor_y.clone();
3150        let cp_scale_x = common_props.scale_x.clone();
3151        let cp_scale_y = common_props.scale_y.clone();
3152        let cp_skew_x = common_props.skew_x.clone();
3153        let cp_skew_y = common_props.skew_y.clone();
3154        let cp_rotate = common_props.rotate.clone();
3155        let cp_x = common_props.x.clone();
3156        let cp_y = common_props.y.clone();
3157        let measured_size = self.measured_size.clone();
3158        let deps = [
3159            cp_width.untyped(),
3160            cp_height.untyped(),
3161            cp_transform.untyped(),
3162            cp_anchor_x.untyped(),
3163            cp_anchor_y.untyped(),
3164            cp_scale_x.untyped(),
3165            cp_scale_y.untyped(),
3166            cp_skew_x.untyped(),
3167            cp_skew_y.untyped(),
3168            cp_rotate.untyped(),
3169            cp_x.untyped(),
3170            cp_y.untyped(),
3171            measured_size.untyped(),
3172        ];
3173
3174        Property::computed(
3175            move || {
3176                // Used for auto sized text, might be used for other things later
3177                let fallback = measured_size.get();
3178                let (w_fallback, h_fallback) = match fallback {
3179                    Some((wf, hf)) => (Some(wf), Some(hf)),
3180                    None => (None, None),
3181                };
3182
3183                LayoutProperties {
3184                    x: cp_x.get(),
3185                    y: cp_y.get(),
3186                    width: cp_width
3187                        .get()
3188                        .or(w_fallback.map(|v| Size::Pixels(v.into()))),
3189                    height: cp_height
3190                        .get()
3191                        .or(h_fallback.map(|v| Size::Pixels(v.into()))),
3192                    rotate: cp_rotate.get(),
3193                    // TODO make the common prop only accept percent
3194                    scale_x: cp_scale_x
3195                        .get()
3196                        .map(|v| Percent((100.0 * v.expect_percent()).into())),
3197                    scale_y: cp_scale_y
3198                        .get()
3199                        .map(|v| Percent((100.0 * v.expect_percent()).into())),
3200                    anchor_x: cp_anchor_x.get(),
3201                    anchor_y: cp_anchor_y.get(),
3202                    skew_x: cp_skew_x.get(),
3203                    skew_y: cp_skew_y.get(),
3204                }
3205            },
3206            &deps,
3207        )
3208    }
3209}
3210
3211fn layout_axis_can_contribute(layout: &LayoutProperties, axis: Axis) -> bool {
3212    let size = match axis {
3213        Axis::X => layout.width,
3214        Axis::Y => layout.height,
3215    };
3216    let position = match axis {
3217        Axis::X => layout.x,
3218        Axis::Y => layout.y,
3219    };
3220
3221    size.is_some_and(|size| !size_depends_on_parent(size))
3222        && !position.is_some_and(size_depends_on_parent)
3223}
3224
3225fn layout_axis_can_contribute_from_parts(size: Option<Size>, position: Option<Size>) -> bool {
3226    size.is_some_and(|size| !size_depends_on_parent(size))
3227        && !position.is_some_and(size_depends_on_parent)
3228}
3229
3230fn size_depends_on_parent(size: Size) -> bool {
3231    match size {
3232        Size::Pixels(_) => false,
3233        Size::Percent(percent) => percent.to_float().abs() > f64::EPSILON,
3234        Size::Combined(_, percent) => percent.to_float().abs() > f64::EPSILON,
3235    }
3236}
3237
3238/// Given a projected child list, flatten away "slot-invisible" nodes (namely, `if` and `for`)
3239/// and return a top-level list of renderable projected children.
3240fn flatten_expanded_nodes_for_projection(nodes: &[Rc<ExpandedNode>]) -> Vec<Rc<ExpandedNode>> {
3241    let mut result: Vec<Rc<ExpandedNode>> = vec![];
3242    for node in nodes {
3243        if borrow!(node.instance_node).base().flags().invisible_to_slot {
3244            result.extend(flatten_expanded_nodes_for_projection(
3245                node.children
3246                    .get()
3247                    .clone()
3248                    .into_iter()
3249                    .collect::<Vec<_>>()
3250                    .as_slice(),
3251            ));
3252        } else {
3253            result.push(Rc::clone(&node))
3254        }
3255    }
3256    result
3257}
3258
3259impl std::fmt::Debug for ExpandedNode {
3260    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3261        //see: https://users.rust-lang.org/t/reusing-an-fmt-formatter/8531/4
3262        //maybe this utility should be moved to a more accessible place?
3263        pub struct Fmt<F>(pub F)
3264        where
3265            F: Fn(&mut fmt::Formatter) -> fmt::Result;
3266
3267        impl<F> fmt::Debug for Fmt<F>
3268        where
3269            F: Fn(&mut fmt::Formatter) -> fmt::Result,
3270        {
3271            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3272                (self.0)(f)
3273            }
3274        }
3275
3276        f.debug_struct("ExpandedNode")
3277            .field(
3278                "instance_node",
3279                &Fmt(|f| borrow!(self.instance_node).resolve_debug(f, Some(self))),
3280            )
3281            .field("id", &self.id)
3282            .field("common_properties", &borrow!(self.common_properties))
3283            .field("transform_and_bounds", &self.transform_and_bounds)
3284            .field("children", &self.children.get().iter().collect::<Vec<_>>())
3285            .field(
3286                "projected_children",
3287                &self
3288                    .expanded_and_flattened_projected_children
3289                    .get()
3290                    .iter()
3291                    .map(|v| v.id)
3292                    .collect::<Vec<_>>(),
3293            )
3294            .field("render_placement", &self.occlusion.get())
3295            .field(
3296                "containing_component",
3297                &self.containing_component.upgrade().map(|v| v.id.clone()),
3298            )
3299            .finish()
3300    }
3301}