Skip to main content

pax_runtime/
rendering.rs

1use std::collections::{BTreeMap, HashMap};
2
3use std::iter;
4use std::rc::Rc;
5use_RefCell!();
6use crate::api::{math::Point2, CommonProperties, RenderContext, SceneAmbientLight, SceneLight};
7use pax_manifest::cartridge_generation::ComponentTransitionConfig;
8use pax_manifest::{
9    SettingsBlockElement, TemplateNodeSelectorInfo, TypeId, UniqueTemplateNodeIdentifier,
10};
11use pax_message::NativeInterrupt;
12use pax_runtime_api::pax_value::PaxAny;
13use pax_runtime_api::{borrow, use_RefCell, Variable};
14use piet::{Color, StrokeStyle};
15
16use crate::api::{Layer, Scroll, Window};
17
18use crate::{
19    create_new_common_properties, create_new_common_properties_from_columns,
20    property_columns_from_defined_properties, update_existing_common_properties,
21    update_existing_common_properties_from_columns, ErasedComponentDescriptor, ExpandedNode,
22    HandlerRegistry, ReceivedChildrenSource, RuntimeContext, RuntimePropertiesStackFrame,
23    TemplatePropertyPlan,
24};
25use pax_manifest::ValueDefinition;
26
27/// Type aliases to make it easier to work with nested Rcs and
28/// RefCells for instance nodes.
29pub type InstanceNodePtr = Rc<dyn InstanceNode>;
30pub type InstanceNodePtrList = RefCell<Vec<InstanceNodePtr>>;
31pub type CommonPropertiesFactory = Box<
32    dyn Fn(
33        Rc<RuntimePropertiesStackFrame>,
34        Option<Rc<ExpandedNode>>,
35    ) -> Option<Rc<RefCell<CommonProperties>>>,
36>;
37pub type PropertiesFactory = Box<
38    dyn Fn(
39        Rc<RuntimePropertiesStackFrame>,
40        Option<Rc<ExpandedNode>>,
41    ) -> Option<Rc<RefCell<PaxAny>>>,
42>;
43pub type PropertiesScopeFactory = Box<dyn Fn(Rc<RefCell<PaxAny>>) -> HashMap<String, Variable>>;
44
45/// Structured initialization for node-local common properties.
46pub enum CommonPropertiesInit {
47    Default,
48    Inline {
49        defined_properties: BTreeMap<String, ValueDefinition>,
50    },
51    Template(Rc<TemplatePropertyPlan>),
52    Factory(CommonPropertiesFactory),
53}
54
55impl CommonPropertiesInit {
56    fn requires_pre_node_binding(&self) -> bool {
57        match self {
58            Self::Factory(_) => true,
59            Self::Inline { defined_properties } => defined_properties
60                .values()
61                .any(crate::value_uses_local_property_scope),
62            Self::Template(plan) => plan.requires_pre_node_binding,
63            Self::Default => false,
64        }
65    }
66
67    /// Allocate stable slots without binding structured template expressions.
68    /// Legacy factories retain their existing pre-node callback contract.
69    pub(crate) fn allocate(
70        &self,
71        stack: Rc<RuntimePropertiesStackFrame>,
72    ) -> Rc<RefCell<CommonProperties>> {
73        match self {
74            Self::Factory(factory) => {
75                factory(stack, None).expect("common properties factory must allocate properties")
76            }
77            _ => Rc::new(RefCell::new(CommonProperties::default())),
78        }
79    }
80
81    pub fn materialize(
82        &self,
83        stack_frame: Rc<RuntimePropertiesStackFrame>,
84        expanded_node: Option<Rc<ExpandedNode>>,
85    ) -> Option<Rc<RefCell<CommonProperties>>> {
86        match self {
87            CommonPropertiesInit::Default => expanded_node
88                .is_none()
89                .then(|| Rc::new(RefCell::new(CommonProperties::default()))),
90            CommonPropertiesInit::Inline { defined_properties } => {
91                if let Some(expanded_node) = expanded_node {
92                    update_existing_common_properties(
93                        &expanded_node,
94                        defined_properties,
95                        &stack_frame,
96                    );
97                    None
98                } else {
99                    Some(create_new_common_properties(
100                        defined_properties,
101                        &stack_frame,
102                    ))
103                }
104            }
105            CommonPropertiesInit::Template(plan) => {
106                let columns = plan.resolve(expanded_node.as_ref());
107                if let Some(node) = expanded_node {
108                    update_existing_common_properties_from_columns(&node, &columns, &stack_frame);
109                    None
110                } else {
111                    Some(create_new_common_properties_from_columns(
112                        &columns,
113                        &stack_frame,
114                    ))
115                }
116            }
117            CommonPropertiesInit::Factory(factory) => factory(stack_frame, expanded_node),
118        }
119    }
120}
121
122/// Structured initialization for node-local typed properties.
123pub enum PropertiesInit {
124    DescriptorDefault(&'static ErasedComponentDescriptor),
125    DescriptorInline {
126        descriptor: &'static ErasedComponentDescriptor,
127        defined_properties: BTreeMap<String, ValueDefinition>,
128    },
129    Template {
130        descriptor: &'static ErasedComponentDescriptor,
131        plan: Rc<TemplatePropertyPlan>,
132    },
133    Factory(PropertiesFactory),
134}
135
136impl PropertiesInit {
137    /// Use an already constructed root while preserving its settings bindings.
138    /// The allocation is shared directly; no descriptor default or value
139    /// conversion may replace its property identities or Rust dependencies.
140    pub fn adopt(self, properties: Rc<RefCell<PaxAny>>) -> Self {
141        Self::Factory(Box::new(move |stack, node| {
142            if node.is_none() {
143                Some(properties.clone())
144            } else {
145                self.materialize(stack, node)
146            }
147        }))
148    }
149
150    fn requires_pre_node_binding(&self) -> bool {
151        match self {
152            Self::Factory(_) => true,
153            Self::DescriptorInline {
154                defined_properties, ..
155            } => defined_properties
156                .values()
157                .any(crate::value_uses_local_property_scope),
158            Self::Template { plan, .. } => plan.requires_pre_node_binding,
159            Self::DescriptorDefault(_) => false,
160        }
161    }
162
163    /// Allocate the component's own defaults once before node-local binding.
164    pub(crate) fn allocate(&self, stack: Rc<RuntimePropertiesStackFrame>) -> Rc<RefCell<PaxAny>> {
165        match self {
166            Self::DescriptorDefault(descriptor)
167            | Self::DescriptorInline { descriptor, .. }
168            | Self::Template { descriptor, .. } => {
169                Rc::new(RefCell::new((descriptor.create_properties)()))
170            }
171            Self::Factory(factory) => {
172                factory(stack, None).expect("properties factory must allocate properties")
173            }
174        }
175    }
176
177    pub fn materialize(
178        &self,
179        stack_frame: Rc<RuntimePropertiesStackFrame>,
180        expanded_node: Option<Rc<ExpandedNode>>,
181    ) -> Option<Rc<RefCell<PaxAny>>> {
182        match self {
183            PropertiesInit::DescriptorDefault(descriptor) => expanded_node
184                .is_none()
185                .then(|| Rc::new(RefCell::new((descriptor.create_properties)()))),
186            PropertiesInit::DescriptorInline {
187                descriptor,
188                defined_properties,
189            } => {
190                if let Some(expanded_node) = expanded_node {
191                    let outer_ref = expanded_node.properties.borrow();
192                    let rc = Rc::clone(&outer_ref);
193                    let mut inner_ref = (*rc).borrow_mut();
194                    (descriptor.apply_defined_properties)(
195                        descriptor.typed_descriptor,
196                        &mut inner_ref,
197                        &property_columns_from_defined_properties(defined_properties),
198                        &stack_frame,
199                        Some(&expanded_node),
200                    );
201                    None
202                } else {
203                    let mut properties = (descriptor.create_properties)();
204                    (descriptor.apply_defined_properties)(
205                        descriptor.typed_descriptor,
206                        &mut properties,
207                        &property_columns_from_defined_properties(defined_properties),
208                        &stack_frame,
209                        None,
210                    );
211                    Some(Rc::new(RefCell::new(properties)))
212                }
213            }
214            PropertiesInit::Template { descriptor, plan } => {
215                let columns = plan.resolve(expanded_node.as_ref());
216                if let Some(node) = expanded_node {
217                    let properties = Rc::clone(&node.properties.borrow());
218                    (descriptor.apply_defined_properties)(
219                        descriptor.typed_descriptor,
220                        &mut properties.borrow_mut(),
221                        &columns,
222                        &stack_frame,
223                        Some(&node),
224                    );
225                    None
226                } else {
227                    let mut properties = (descriptor.create_properties)();
228                    (descriptor.apply_defined_properties)(
229                        descriptor.typed_descriptor,
230                        &mut properties,
231                        &columns,
232                        &stack_frame,
233                        None,
234                    );
235                    Some(Rc::new(RefCell::new(properties)))
236                }
237            }
238            PropertiesInit::Factory(factory) => factory(stack_frame, expanded_node),
239        }
240    }
241}
242
243/// How an expanded node should expose component-local symbols into scope.
244pub enum PropertiesScopeInit {
245    None,
246    Descriptor(&'static ErasedComponentDescriptor),
247    Factory(PropertiesScopeFactory),
248}
249
250impl PropertiesScopeInit {
251    pub fn build(&self, properties: Rc<RefCell<PaxAny>>) -> HashMap<String, Variable> {
252        match self {
253            PropertiesScopeInit::None => HashMap::new(),
254            PropertiesScopeInit::Descriptor(descriptor) => {
255                let props_ref = borrow!(properties.as_ref());
256                (descriptor.build_property_scope)(descriptor.typed_descriptor, &props_ref)
257            }
258            PropertiesScopeInit::Factory(factory) => factory(properties),
259        }
260    }
261}
262
263/// Construction payload used when compiler-generated code instantiates an `InstanceNode`.
264pub struct InstantiationArgs {
265    pub prototypical_common_properties: CommonPropertiesInit,
266    pub prototypical_properties: PropertiesInit,
267    pub handler_registry: Option<Rc<RefCell<HandlerRegistry>>>,
268    pub children: Option<InstanceNodePtrList>,
269    pub component_template: Option<InstanceNodePtrList>,
270    pub component_settings: Option<Vec<SettingsBlockElement>>,
271
272    pub template_node_identifier: Option<UniqueTemplateNodeIdentifier>,
273    pub template_node_type_id: Option<TypeId>,
274    pub template_node_selector_info: Option<TemplateNodeSelectorInfo>,
275    pub transition_config: ComponentTransitionConfig,
276    // Used by RuntimePropertyStackFrame to pull out struct's properties based on their names
277    pub properties_scope: PropertiesScopeInit,
278}
279
280/// Lightweight clone of reusable base-node data for helper constructors.
281pub struct ReusableInstanceNodeArgs {
282    pub handler_registry: Option<Rc<RefCell<HandlerRegistry>>>,
283    pub children: InstanceNodePtrList,
284    pub template_node_identifier: Option<UniqueTemplateNodeIdentifier>,
285    pub template_node_type_id: Option<TypeId>,
286    pub template_node_selector_info: Option<TemplateNodeSelectorInfo>,
287}
288
289impl ReusableInstanceNodeArgs {
290    /// Capture the reusable portions of a `BaseInstance`.
291    pub fn new(base: &BaseInstance) -> Self {
292        ReusableInstanceNodeArgs {
293            handler_registry: base.handler_registry.clone(),
294            children: base.instance_children.clone(),
295            template_node_identifier: base.template_node_identifier.clone(),
296            template_node_type_id: base.template_node_type_id.clone(),
297            template_node_selector_info: base.template_node_selector_info.clone(),
298        }
299    }
300}
301
302#[derive(Clone)]
303/// Coarse runtime category for an instance node.
304pub enum NodeType {
305    Component,
306    Primitive,
307}
308
309impl std::fmt::Debug for dyn InstanceNode {
310    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311        self.resolve_debug(f, None)
312    }
313}
314
315/// Central runtime representation of a properties-computable and renderable node.
316/// `InstanceNode`s are conceptually stateless, and rely on [`ExpandedNode`]s for stateful representations.
317///
318/// An `InstanceNode` sits in between a `pax_compiler::TemplateNodeDefinition`, the
319/// compile-time `definition` analogue to this `instance`, and [`ExpandedNode`].
320///
321/// There is a 1:1 relationship between `pax_compiler::TemplateNodeDefinition`s and `InstanceNode`s.
322/// There is a one-to-many relationship between one `InstanceNode` and possibly many variant [`ExpandedNode`]s,
323/// due to duplication via `for`.
324///
325/// `InstanceNode`s are architecturally "type-aware" — they can perform type-specific operations e.g. on the state stored in [`ExpandedNode`], while
326/// [`ExpandedNode`]s are "type-blind".  The latter store polymorphic data but cannot operate on it without the type-aware assistance of their linked `InstanceNode`.
327///
328/// (See `RepeatInstance::expand_node` where we visit a singular `InstanceNode` several times, producing multiple [`ExpandedNode`]s.)
329pub trait InstanceNode {
330    /// Resolve a userland subtree filter from ordinary reactive properties. Returning `Some`
331    /// replaces the subtree's GPU presentation, preserving original native coverage/hit testing.
332    /// Effect primitives must be `Layer::Canvas`, subscribe their inputs to canvas dirtification,
333    /// and exclude shader-only properties from `property_requires_occlusion_recompute`.
334    /// A shader primitive returns `Some` throughout its mounted lifetime; use an identity
335    /// recipe (or an identity parameter value) to disable filtering.
336    /// Available with the opt-in `shaders` Cargo feature.
337    #[cfg(feature = "shaders")]
338    fn shader_effect(&self, _node: &ExpandedNode) -> Option<pax_runtime_api::ShaderEffect> {
339        None
340    }
341    /// Whether ordinary render hooks are valid inside a detached vector alpha source.
342    /// Native surfaces and cross-surface containers require separate capture support.
343    fn supports_alpha_source_render(&self) -> bool {
344        false
345    }
346    ///Retrieves the base instance, containing common functionality that all instances share
347    fn base(&self) -> &BaseInstance;
348
349    fn instantiate(args: InstantiationArgs) -> Rc<Self>
350    where
351        Self: Sized;
352
353    fn resolve_debug(
354        &self,
355        f: &mut std::fmt::Formatter,
356        expanded_node: Option<&ExpandedNode>,
357    ) -> std::fmt::Result;
358
359    /// Whether this instance has per-frame update work that cannot be driven entirely through
360    /// reactive property effects.
361    fn requires_non_reactive_update(&self, _expanded_node: &ExpandedNode) -> bool {
362        false
363    }
364
365    /// Whether this primitive can contribute direct or ambient scene lighting.
366    ///
367    /// Lighting primitives must return `true` even while disabled. The runtime uses
368    /// this stable capability at mount/replacement to register contributors without
369    /// polling every scene node during rendering.
370    fn has_scene_lighting(&self) -> bool {
371        false
372    }
373
374    /// Returns a resolved light contribution for a primitive with `has_scene_lighting()`.
375    fn resolve_scene_light(
376        &self,
377        _expanded_node: &ExpandedNode,
378        _context: &RuntimeContext,
379    ) -> Option<SceneLight> {
380        None
381    }
382
383    /// Returns a resolved ambient contribution for a primitive with `has_scene_lighting()`.
384    fn resolve_scene_ambient_light(
385        &self,
386        _expanded_node: &ExpandedNode,
387        _context: &RuntimeContext,
388    ) -> Option<SceneAmbientLight> {
389        None
390    }
391
392    /// Whether this node establishes a lexical boundary for descendant lights.
393    fn establishes_light_frame(&self) -> bool {
394        false
395    }
396
397    /// Updates the expanded node, recomputing its properties and possibly updating its children.
398    fn update(self: Rc<Self>, _expanded_node: &Rc<ExpandedNode>, _context: &Rc<RuntimeContext>) {}
399
400    /// Second lifecycle method during each render loop, occurs after
401    /// properties have been computed, but before rendering
402    /// Example use-case: perform side-effects to the drawing contexts.
403    /// This is how `Frame` performs clipping, for example.
404    /// Occurs in a pre-order traversal of the render tree.
405    #[allow(unused_variables)]
406    fn handle_pre_render(
407        &self,
408        expanded_node: &ExpandedNode,
409        context: &Rc<RuntimeContext>,
410        rcs: &mut dyn RenderContext,
411    ) {
412        //no-op default implementation
413    }
414
415    /// Prepare primitive-local paint geometry independently of drawing or surface readiness.
416    ///
417    /// The default is conservatively unbounded. Bounded primitives should return their
418    /// actual local coverage; the runtime shares its transformed result with replay selection.
419    /// This must be a side-effect-free read of settled properties. Results are retained
420    /// until `RuntimeContext::mark_canvas_node_dirty` invalidates the node, so a primitive's
421    /// reactive drawing dependencies must include every input used here. Surface replay
422    /// reuses the prepared record without invoking this method again.
423    fn prepare_canvas_geometry(
424        &self,
425        _node: &ExpandedNode,
426    ) -> crate::scene_geometry::CanvasGeometry {
427        Default::default()
428    }
429
430    /// Third lifecycle method during each render loop, occurs
431    /// after all descendents have been rendered.
432    /// Occurs in a post-order traversal of the render tree. Most primitives
433    /// are expected to draw their contents to the rendering context during this event.
434    #[allow(unused_variables)]
435    fn render(
436        &self,
437        expanded_node: &ExpandedNode,
438        context: &Rc<RuntimeContext>,
439        rcs: &mut dyn RenderContext,
440    ) {
441    }
442
443    /// Fourth and final lifecycle method during each render loop, occurs
444    /// after all descendents have been rendered AND the current node has been rendered.
445    /// Useful for clean-up, e.g. this is where `Frame` cleans up the drawing contexts
446    /// to stop clipping.
447    /// Occurs in a post-order traversal of the render tree.
448    #[allow(unused_variables)]
449    fn handle_post_render(
450        &self,
451        expanded_node: &ExpandedNode,
452        context: &Rc<RuntimeContext>,
453        rcs: &mut dyn RenderContext,
454    ) {
455        //no-op default implementation
456    }
457
458    /// Fires during the tick when a node is first attached to the render tree.  For example,
459    /// this event fires by all nodes on the global first tick, and by all nodes in a subtree
460    /// when a `Conditional` subsequently turns on a subtree (i.e. when the `Conditional`s criterion becomes `true` after being `false` through the end of at least 1 frame.)
461    /// A use-case: send a message to native renderers that a `Text` element should be rendered and tracked
462    #[allow(unused_variables)]
463    fn handle_mount(
464        self: Rc<Self>,
465        expanded_node: &Rc<ExpandedNode>,
466        context: &Rc<RuntimeContext>,
467    ) {
468        let env = Rc::clone(&expanded_node.stack);
469        let children = borrow!(self.base().get_instance_children());
470        let children_with_envs = children.iter().cloned().zip(iter::repeat(env));
471
472        let new_children = expanded_node.generate_children(
473            children_with_envs,
474            context,
475            &expanded_node.parent_frame,
476            true,
477        );
478        expanded_node.children.set(new_children);
479        #[cfg(feature = "shaders")]
480        if self.shader_effect(expanded_node).is_some() {
481            crate::bind_shader_effect(expanded_node, context);
482        }
483    }
484
485    /// Fires during element unmount, when an element is about to be removed from the render tree (e.g. by a `Conditional`)
486    /// A use-case: send a message to native renderers that a `Text` element should be removed
487    #[allow(unused_variables)]
488    fn handle_unmount(&self, expanded_node: &Rc<ExpandedNode>, context: &Rc<RuntimeContext>) {
489        // let new_children =
490        //     expanded_node.generate_children(vec![], context, &expanded_node.parent_frame);
491        // expanded_node.children.set(new_children);
492    }
493
494    /// Invoked by event interrupts to pass scroll information to render node
495    #[allow(unused_variables)]
496    fn handle_scroll(&self, args_scroll: Scroll) {
497        //no-op default implementation
498    }
499
500    fn get_template(&self) -> Option<&InstanceNodePtrList> {
501        None
502    }
503
504    /// Used by frame to control content clipping
505    fn clips_content(&self, _expanded_node: &ExpandedNode) -> bool {
506        false
507    }
508
509    /// Override when a primitive needs a more precise hit region than its layout bounds.
510    fn ray_cast_test(&self, expanded_node: &ExpandedNode, ray: Point2<Window>) -> bool {
511        let t_and_b = expanded_node.transform_and_bounds.get();
512        let inverted_transform = t_and_b.transform.inverse();
513        let transformed_ray = inverted_transform * ray;
514        let (width, height) = t_and_b.bounds;
515        transformed_ray.x > 0.0
516            && transformed_ray.y > 0.0
517            && transformed_ray.x < width
518            && transformed_ray.y < height
519    }
520
521    /// Used by scroller-like primitives to allocate a browser-owned presentation layer.
522    fn scrolls_content(&self, _expanded_node: &ExpandedNode) -> bool {
523        false
524    }
525
526    /// Returns whether changes to a named typed property invalidate native compositing.
527    /// Leaf changes update indexed coverage; changes to clipping or layer ownership
528    /// reconcile the inherited structure.
529    ///
530    /// Common geometry, transform, opacity, and child-list dependencies are tracked separately
531    /// for every node. Override this for typed properties that only affect local content or
532    /// native patches and cannot change occlusion coverage, clipping, z-order, or layer
533    /// ownership.
534    fn property_requires_occlusion_recompute(&self, _property_name: &str) -> bool {
535        true
536    }
537
538    /// True when an otherwise layout-only primitive currently materializes a native surface.
539    fn materializes_native_surface(&self, _expanded_node: &ExpandedNode) -> bool {
540        false
541    }
542
543    /// True when a materialized native surface should be projected before descendants.
544    ///
545    /// Most renderables are post-order so their own pixels sit above descendants in logical
546    /// tree order. Native backing surfaces, such as liquid-glass Groups, need the opposite:
547    /// the chassis should create/project the native surface before descendant content while
548    /// still including the surface in native occlusion and vector-mask computation.
549    fn materializes_native_surface_before_children(&self, _expanded_node: &ExpandedNode) -> bool {
550        false
551    }
552
553    /// Returns the current browser-owned scroll offset in local scroller coordinates.
554    fn resolve_scroll_offset(&self, _expanded_node: &ExpandedNode) -> Option<(f64, f64)> {
555        None
556    }
557
558    /// Selects which engine child family should be normalized into
559    /// `received_children` for semantic container-style consumers.
560    ///
561    /// This hook is about provenance, not semantics:
562    /// - `Owned` means the node's active child tree is already its received
563    ///   payload
564    /// - `Projected` means the node receives payload from its caller and the
565    ///   runtime delivers that payload through projection so `slot(...)` can
566    ///   consume it
567    ///
568    /// The default remains heuristic for now: components normalize projected
569    /// children, while other nodes normalize owned children.
570    fn received_children_source(&self) -> ReceivedChildrenSource {
571        if self.base().flags().is_component {
572            ReceivedChildrenSource::Projected
573        } else {
574            ReceivedChildrenSource::Owned
575        }
576    }
577
578    fn handle_native_interrupt(
579        &self,
580        _expanded_node: &Rc<ExpandedNode>,
581        _interrupt: &NativeInterrupt,
582    ) {
583        // no-op for many
584    }
585
586    fn handle_control_flow_node_expansion(
587        self: Rc<Self>,
588        _expanded_node: &Rc<ExpandedNode>,
589        _context: &Rc<RuntimeContext>,
590    ) {
591    }
592
593    fn handle_setup_projected_children(
594        self: Rc<Self>,
595        _expanded_node: &Rc<ExpandedNode>,
596        _context: &Rc<RuntimeContext>,
597    ) {
598    }
599
600    fn resolve_coverage_path(&self, _expanded_node: &ExpandedNode) -> Option<kurbo::BezPath> {
601        None
602    }
603
604    /// Resolves vector paint for an alpha-mask source without rendering or
605    /// mutating the source's retained canvas state.
606    /// Paint-local coverage without common node/ancestor opacity. The Mask consumer
607    /// applies the source subtree's opacity factors relative to its own boundary.
608    fn resolve_alpha_mask_paints(
609        &self,
610        _expanded_node: &ExpandedNode,
611    ) -> Vec<pax_runtime_api::AlphaMaskPaint> {
612        Vec::new()
613    }
614
615    /// Returns coverage used by the occlusion/native-mask/layer-partition pass.
616    ///
617    /// The default uses the same coverage path as masks and hit-style geometry, but primitives
618    /// with render-side animation may return a conservative stable path here so animation-only
619    /// properties do not force the occlusion pass to rebuild expensive geometry every frame.
620    fn resolve_occlusion_path(&self, expanded_node: &ExpandedNode) -> Option<kurbo::BezPath> {
621        self.resolve_coverage_path(expanded_node)
622    }
623
624    fn resolve_coverage_opacity(&self, expanded_node: &ExpandedNode) -> f64 {
625        expanded_node.computed_opacity.get()
626    }
627
628    fn resolve_effect_clip_path(&self, _expanded_node: &ExpandedNode) -> Option<kurbo::BezPath> {
629        None
630    }
631}
632
633/// Shared storage carried by every concrete `InstanceNode`.
634pub struct BaseInstance {
635    pub handler_registry: Option<Rc<RefCell<HandlerRegistry>>>,
636    pub instance_prototypical_properties: PropertiesInit,
637    pub instance_prototypical_common_properties: CommonPropertiesInit,
638    pub component_settings: Option<Vec<SettingsBlockElement>>,
639    pub template_node_identifier: Option<UniqueTemplateNodeIdentifier>,
640    pub template_node_type_id: Option<TypeId>,
641    pub template_node_selector_info: Option<TemplateNodeSelectorInfo>,
642    pub transition_config: ComponentTransitionConfig,
643    pub properties_scope: PropertiesScopeInit,
644    instance_children: InstanceNodePtrList,
645    flags: InstanceFlags,
646}
647
648#[derive(Clone)]
649/// Static traversal/rendering flags for a concrete instance node.
650pub struct InstanceFlags {
651    /// Used for exotic tree traversals for `Slot`, e.g. for `Stacker` > `Repeat` > `Rectangle`
652    /// where the repeated `Rectangle`s need to be be considered direct children of `Stacker`.
653    /// `Repeat` and `Conditional` set this true so their active children can be
654    /// considered direct projected children by slot-driven containers.
655    pub invisible_to_slot: bool,
656    /// Certain elements, such as Groups and Components, are invisible to ray-casting.
657    /// Since these container elements are on top of the elements they contain,
658    /// this is needed otherwise the containers would intercept rays that should hit their contents.
659    pub invisible_to_raycasting: bool,
660    /// The layer type (`Layer::Native`, `Layer::NativeNonOccluding`, or `Layer::Canvas`)
661    /// for this RenderNode.
662    /// Default is `Layer::Canvas`, and must be overwritten for `InstanceNode`s that manage native
663    /// content.
664    pub layer: Layer,
665
666    /// Only true for ComponentInstance
667    pub is_component: bool,
668
669    /// Is this node a `Slot`?
670    pub is_slot: bool,
671}
672
673impl BaseInstance {
674    pub(crate) fn requires_pre_node_binding(&self) -> bool {
675        self.instance_prototypical_common_properties
676            .requires_pre_node_binding()
677            || self
678                .instance_prototypical_properties
679                .requires_pre_node_binding()
680            || matches!(self.properties_scope, PropertiesScopeInit::Factory(_))
681    }
682
683    /// Bind after stable slots and selector identity exist, before publishing
684    /// the final scope and activating listeners/lifecycle handlers.
685    /// Paired template initializers share one fresh resolution per bind, including
686    /// removal resets on reload. Custom factories keep their ordered callbacks.
687    pub(crate) fn bind_properties(&self, node: &Rc<ExpandedNode>) {
688        if let (
689            CommonPropertiesInit::Template(common_plan),
690            PropertiesInit::Template { descriptor, plan },
691        ) = (
692            &self.instance_prototypical_common_properties,
693            &self.instance_prototypical_properties,
694        ) {
695            if Rc::ptr_eq(common_plan, plan) {
696                let columns = plan.resolve(Some(node));
697                update_existing_common_properties_from_columns(node, &columns, &node.stack);
698                let properties = Rc::clone(&node.properties.borrow());
699                (descriptor.apply_defined_properties)(
700                    descriptor.typed_descriptor,
701                    &mut properties.borrow_mut(),
702                    &columns,
703                    &node.stack,
704                    Some(node),
705                );
706                return;
707            }
708        }
709        self.instance_prototypical_common_properties
710            .materialize(Rc::clone(&node.stack), Some(Rc::clone(node)));
711        self.instance_prototypical_properties
712            .materialize(Rc::clone(&node.stack), Some(Rc::clone(node)));
713    }
714
715    /// Build shared instance state from compiler-generated instantiation args.
716    pub fn new(args: InstantiationArgs, flags: InstanceFlags) -> Self {
717        BaseInstance {
718            handler_registry: args.handler_registry,
719            instance_prototypical_common_properties: args.prototypical_common_properties,
720            instance_prototypical_properties: args.prototypical_properties,
721            component_settings: args.component_settings,
722            instance_children: args.children.unwrap_or_default(),
723            flags,
724            template_node_identifier: args.template_node_identifier,
725            template_node_type_id: args.template_node_type_id,
726            template_node_selector_info: args.template_node_selector_info,
727            transition_config: args.transition_config,
728            properties_scope: args.properties_scope,
729        }
730    }
731
732    /// Returns a handle to a node-managed HandlerRegistry, a mapping between event types and handlers.
733    /// Each node that can handle events is responsible for implementing this; Component instances generate
734    /// the necessary code to wire up userland events like `<SomeNode @click=self.handler>`. Primitives must handle
735    /// this explicitly, see e.g. `[pax_std::drawing::rectangle::RectangleInstance#get_handler_registry]`.
736    pub fn get_handler_registry(&self) -> Option<Rc<RefCell<HandlerRegistry>>> {
737        match &self.handler_registry {
738            Some(registry) => Some(Rc::clone(registry)),
739            _ => None,
740        }
741    }
742
743    /// Return the list of instance nodes that are children of this one. Intuitively, this returns
744    /// the nodes owned directly by this instance's definition.
745    ///
746    /// For `Component`s, this returns the root(s) of the component template, not the
747    /// projected children supplied by the containing component.
748    pub fn get_instance_children(&self) -> &InstanceNodePtrList {
749        &self.instance_children
750    }
751
752    /// Static behavior flags for this instance.
753    pub fn flags(&self) -> &InstanceFlags {
754        &self.flags
755    }
756
757    pub fn transition_config(&self) -> &ComponentTransitionConfig {
758        &self.transition_config
759    }
760}
761
762/// Resolved stroke style used by canvas drawing primitives.
763pub struct StrokeInstance {
764    pub color: Color,
765    pub width: f64,
766    pub style: StrokeStyle,
767}