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    fn requires_pre_node_binding(&self) -> bool {
138        match self {
139            Self::Factory(_) => true,
140            Self::DescriptorInline {
141                defined_properties, ..
142            } => defined_properties
143                .values()
144                .any(crate::value_uses_local_property_scope),
145            Self::Template { plan, .. } => plan.requires_pre_node_binding,
146            Self::DescriptorDefault(_) => false,
147        }
148    }
149
150    /// Allocate the component's own defaults once before node-local binding.
151    pub(crate) fn allocate(&self, stack: Rc<RuntimePropertiesStackFrame>) -> Rc<RefCell<PaxAny>> {
152        match self {
153            Self::DescriptorDefault(descriptor)
154            | Self::DescriptorInline { descriptor, .. }
155            | Self::Template { descriptor, .. } => {
156                Rc::new(RefCell::new((descriptor.create_properties)()))
157            }
158            Self::Factory(factory) => {
159                factory(stack, None).expect("properties factory must allocate properties")
160            }
161        }
162    }
163
164    pub fn materialize(
165        &self,
166        stack_frame: Rc<RuntimePropertiesStackFrame>,
167        expanded_node: Option<Rc<ExpandedNode>>,
168    ) -> Option<Rc<RefCell<PaxAny>>> {
169        match self {
170            PropertiesInit::DescriptorDefault(descriptor) => expanded_node
171                .is_none()
172                .then(|| Rc::new(RefCell::new((descriptor.create_properties)()))),
173            PropertiesInit::DescriptorInline {
174                descriptor,
175                defined_properties,
176            } => {
177                if let Some(expanded_node) = expanded_node {
178                    let outer_ref = expanded_node.properties.borrow();
179                    let rc = Rc::clone(&outer_ref);
180                    let mut inner_ref = (*rc).borrow_mut();
181                    (descriptor.apply_defined_properties)(
182                        descriptor.typed_descriptor,
183                        &mut inner_ref,
184                        &property_columns_from_defined_properties(defined_properties),
185                        &stack_frame,
186                    );
187                    None
188                } else {
189                    let mut properties = (descriptor.create_properties)();
190                    (descriptor.apply_defined_properties)(
191                        descriptor.typed_descriptor,
192                        &mut properties,
193                        &property_columns_from_defined_properties(defined_properties),
194                        &stack_frame,
195                    );
196                    Some(Rc::new(RefCell::new(properties)))
197                }
198            }
199            PropertiesInit::Template { descriptor, plan } => {
200                let columns = plan.resolve(expanded_node.as_ref());
201                if let Some(node) = expanded_node {
202                    let properties = Rc::clone(&node.properties.borrow());
203                    (descriptor.apply_defined_properties)(
204                        descriptor.typed_descriptor,
205                        &mut properties.borrow_mut(),
206                        &columns,
207                        &stack_frame,
208                    );
209                    None
210                } else {
211                    let mut properties = (descriptor.create_properties)();
212                    (descriptor.apply_defined_properties)(
213                        descriptor.typed_descriptor,
214                        &mut properties,
215                        &columns,
216                        &stack_frame,
217                    );
218                    Some(Rc::new(RefCell::new(properties)))
219                }
220            }
221            PropertiesInit::Factory(factory) => factory(stack_frame, expanded_node),
222        }
223    }
224}
225
226/// How an expanded node should expose component-local symbols into scope.
227pub enum PropertiesScopeInit {
228    None,
229    Descriptor(&'static ErasedComponentDescriptor),
230    Factory(PropertiesScopeFactory),
231}
232
233impl PropertiesScopeInit {
234    pub fn build(&self, properties: Rc<RefCell<PaxAny>>) -> HashMap<String, Variable> {
235        match self {
236            PropertiesScopeInit::None => HashMap::new(),
237            PropertiesScopeInit::Descriptor(descriptor) => {
238                let props_ref = borrow!(properties.as_ref());
239                (descriptor.build_property_scope)(descriptor.typed_descriptor, &props_ref)
240            }
241            PropertiesScopeInit::Factory(factory) => factory(properties),
242        }
243    }
244}
245
246/// Construction payload used when compiler-generated code instantiates an `InstanceNode`.
247pub struct InstantiationArgs {
248    pub prototypical_common_properties: CommonPropertiesInit,
249    pub prototypical_properties: PropertiesInit,
250    pub handler_registry: Option<Rc<RefCell<HandlerRegistry>>>,
251    pub children: Option<InstanceNodePtrList>,
252    pub component_template: Option<InstanceNodePtrList>,
253    pub component_settings: Option<Vec<SettingsBlockElement>>,
254
255    pub template_node_identifier: Option<UniqueTemplateNodeIdentifier>,
256    pub template_node_type_id: Option<TypeId>,
257    pub template_node_selector_info: Option<TemplateNodeSelectorInfo>,
258    pub transition_config: ComponentTransitionConfig,
259    // Used by RuntimePropertyStackFrame to pull out struct's properties based on their names
260    pub properties_scope: PropertiesScopeInit,
261}
262
263/// Lightweight clone of reusable base-node data for helper constructors.
264pub struct ReusableInstanceNodeArgs {
265    pub handler_registry: Option<Rc<RefCell<HandlerRegistry>>>,
266    pub children: InstanceNodePtrList,
267    pub template_node_identifier: Option<UniqueTemplateNodeIdentifier>,
268    pub template_node_type_id: Option<TypeId>,
269    pub template_node_selector_info: Option<TemplateNodeSelectorInfo>,
270}
271
272impl ReusableInstanceNodeArgs {
273    /// Capture the reusable portions of a `BaseInstance`.
274    pub fn new(base: &BaseInstance) -> Self {
275        ReusableInstanceNodeArgs {
276            handler_registry: base.handler_registry.clone(),
277            children: base.instance_children.clone(),
278            template_node_identifier: base.template_node_identifier.clone(),
279            template_node_type_id: base.template_node_type_id.clone(),
280            template_node_selector_info: base.template_node_selector_info.clone(),
281        }
282    }
283}
284
285#[derive(Clone)]
286/// Coarse runtime category for an instance node.
287pub enum NodeType {
288    Component,
289    Primitive,
290}
291
292impl std::fmt::Debug for dyn InstanceNode {
293    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294        self.resolve_debug(f, None)
295    }
296}
297
298/// Central runtime representation of a properties-computable and renderable node.
299/// `InstanceNode`s are conceptually stateless, and rely on [`ExpandedNode`]s for stateful representations.
300///
301/// An `InstanceNode` sits in between a `pax_compiler::TemplateNodeDefinition`, the
302/// compile-time `definition` analogue to this `instance`, and [`ExpandedNode`].
303///
304/// There is a 1:1 relationship between `pax_compiler::TemplateNodeDefinition`s and `InstanceNode`s.
305/// There is a one-to-many relationship between one `InstanceNode` and possibly many variant [`ExpandedNode`]s,
306/// due to duplication via `for`.
307///
308/// `InstanceNode`s are architecturally "type-aware" — they can perform type-specific operations e.g. on the state stored in [`ExpandedNode`], while
309/// [`ExpandedNode`]s are "type-blind".  The latter store polymorphic data but cannot operate on it without the type-aware assistance of their linked `InstanceNode`.
310///
311/// (See `RepeatInstance::expand_node` where we visit a singular `InstanceNode` several times, producing multiple [`ExpandedNode`]s.)
312pub trait InstanceNode {
313    ///Retrieves the base instance, containing common functionality that all instances share
314    fn base(&self) -> &BaseInstance;
315
316    fn instantiate(args: InstantiationArgs) -> Rc<Self>
317    where
318        Self: Sized;
319
320    fn resolve_debug(
321        &self,
322        f: &mut std::fmt::Formatter,
323        expanded_node: Option<&ExpandedNode>,
324    ) -> std::fmt::Result;
325
326    /// Whether this instance has per-frame update work that cannot be driven entirely through
327    /// reactive property effects.
328    fn requires_non_reactive_update(&self, _expanded_node: &ExpandedNode) -> bool {
329        false
330    }
331
332    /// Returns a resolved light contribution for non-rendering lighting primitives.
333    fn resolve_scene_light(
334        &self,
335        _expanded_node: &ExpandedNode,
336        _context: &RuntimeContext,
337    ) -> Option<SceneLight> {
338        None
339    }
340
341    /// Returns a resolved ambient contribution for non-rendering lighting primitives.
342    fn resolve_scene_ambient_light(
343        &self,
344        _expanded_node: &ExpandedNode,
345        _context: &RuntimeContext,
346    ) -> Option<SceneAmbientLight> {
347        None
348    }
349
350    /// Whether this node establishes a lexical boundary for descendant lights.
351    fn establishes_light_frame(&self) -> bool {
352        false
353    }
354
355    /// Updates the expanded node, recomputing its properties and possibly updating its children.
356    fn update(self: Rc<Self>, _expanded_node: &Rc<ExpandedNode>, _context: &Rc<RuntimeContext>) {}
357
358    /// Second lifecycle method during each render loop, occurs after
359    /// properties have been computed, but before rendering
360    /// Example use-case: perform side-effects to the drawing contexts.
361    /// This is how `Frame` performs clipping, for example.
362    /// Occurs in a pre-order traversal of the render tree.
363    #[allow(unused_variables)]
364    fn handle_pre_render(
365        &self,
366        expanded_node: &ExpandedNode,
367        context: &Rc<RuntimeContext>,
368        rcs: &mut dyn RenderContext,
369    ) {
370        //no-op default implementation
371    }
372
373    /// Third lifecycle method during each render loop, occurs
374    /// after all descendents have been rendered.
375    /// Occurs in a post-order traversal of the render tree. Most primitives
376    /// are expected to draw their contents to the rendering context during this event.
377    #[allow(unused_variables)]
378    fn render(
379        &self,
380        expanded_node: &ExpandedNode,
381        context: &Rc<RuntimeContext>,
382        rcs: &mut dyn RenderContext,
383    ) {
384    }
385
386    /// Fourth and final lifecycle method during each render loop, occurs
387    /// after all descendents have been rendered AND the current node has been rendered.
388    /// Useful for clean-up, e.g. this is where `Frame` cleans up the drawing contexts
389    /// to stop clipping.
390    /// Occurs in a post-order traversal of the render tree.
391    #[allow(unused_variables)]
392    fn handle_post_render(
393        &self,
394        expanded_node: &ExpandedNode,
395        context: &Rc<RuntimeContext>,
396        rcs: &mut dyn RenderContext,
397    ) {
398        //no-op default implementation
399    }
400
401    /// Fires during the tick when a node is first attached to the render tree.  For example,
402    /// this event fires by all nodes on the global first tick, and by all nodes in a subtree
403    /// 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.)
404    /// A use-case: send a message to native renderers that a `Text` element should be rendered and tracked
405    #[allow(unused_variables)]
406    fn handle_mount(
407        self: Rc<Self>,
408        expanded_node: &Rc<ExpandedNode>,
409        context: &Rc<RuntimeContext>,
410    ) {
411        let env = Rc::clone(&expanded_node.stack);
412        let children = borrow!(self.base().get_instance_children());
413        let children_with_envs = children.iter().cloned().zip(iter::repeat(env));
414
415        let new_children = expanded_node.generate_children(
416            children_with_envs,
417            context,
418            &expanded_node.parent_frame,
419            true,
420        );
421        expanded_node.children.set(new_children);
422    }
423
424    /// Fires during element unmount, when an element is about to be removed from the render tree (e.g. by a `Conditional`)
425    /// A use-case: send a message to native renderers that a `Text` element should be removed
426    #[allow(unused_variables)]
427    fn handle_unmount(&self, expanded_node: &Rc<ExpandedNode>, context: &Rc<RuntimeContext>) {
428        // let new_children =
429        //     expanded_node.generate_children(vec![], context, &expanded_node.parent_frame);
430        // expanded_node.children.set(new_children);
431    }
432
433    /// Invoked by event interrupts to pass scroll information to render node
434    #[allow(unused_variables)]
435    fn handle_scroll(&self, args_scroll: Scroll) {
436        //no-op default implementation
437    }
438
439    fn get_template(&self) -> Option<&InstanceNodePtrList> {
440        None
441    }
442
443    /// Used by frame to control content clipping
444    fn clips_content(&self, _expanded_node: &ExpandedNode) -> bool {
445        false
446    }
447
448    /// Override when a primitive needs a more precise hit region than its layout bounds.
449    fn ray_cast_test(&self, expanded_node: &ExpandedNode, ray: Point2<Window>) -> bool {
450        let t_and_b = expanded_node.transform_and_bounds.get();
451        let inverted_transform = t_and_b.transform.inverse();
452        let transformed_ray = inverted_transform * ray;
453        let (width, height) = t_and_b.bounds;
454        transformed_ray.x > 0.0
455            && transformed_ray.y > 0.0
456            && transformed_ray.x < width
457            && transformed_ray.y < height
458    }
459
460    /// Used by scroller-like primitives to allocate a browser-owned presentation layer.
461    fn scrolls_content(&self, _expanded_node: &ExpandedNode) -> bool {
462        false
463    }
464
465    /// Returns whether changes to a named typed property require recomputing the full
466    /// occlusion/native-mask/layer-partition pass.
467    ///
468    /// Common geometry, transform, opacity, and child-list dependencies are tracked separately
469    /// for every node. Override this for typed properties that only affect local content or
470    /// native patches and cannot change occlusion coverage, clipping, z-order, or layer
471    /// ownership.
472    fn property_requires_occlusion_recompute(&self, _property_name: &str) -> bool {
473        true
474    }
475
476    /// True when an otherwise layout-only primitive currently materializes a native surface.
477    fn materializes_native_surface(&self, _expanded_node: &ExpandedNode) -> bool {
478        false
479    }
480
481    /// True when a materialized native surface should be projected before descendants.
482    ///
483    /// Most renderables are post-order so their own pixels sit above descendants in logical
484    /// tree order. Native backing surfaces, such as liquid-glass Groups, need the opposite:
485    /// the chassis should create/project the native surface before descendant content while
486    /// still including the surface in native occlusion and vector-mask computation.
487    fn materializes_native_surface_before_children(&self, _expanded_node: &ExpandedNode) -> bool {
488        false
489    }
490
491    /// Returns the current browser-owned scroll offset in local scroller coordinates.
492    fn resolve_scroll_offset(&self, _expanded_node: &ExpandedNode) -> Option<(f64, f64)> {
493        None
494    }
495
496    /// Selects which engine child family should be normalized into
497    /// `received_children` for semantic container-style consumers.
498    ///
499    /// This hook is about provenance, not semantics:
500    /// - `Owned` means the node's active child tree is already its received
501    ///   payload
502    /// - `Projected` means the node receives payload from its caller and the
503    ///   runtime delivers that payload through projection so `slot(...)` can
504    ///   consume it
505    ///
506    /// The default remains heuristic for now: components normalize projected
507    /// children, while other nodes normalize owned children.
508    fn received_children_source(&self) -> ReceivedChildrenSource {
509        if self.base().flags().is_component {
510            ReceivedChildrenSource::Projected
511        } else {
512            ReceivedChildrenSource::Owned
513        }
514    }
515
516    fn handle_native_interrupt(
517        &self,
518        _expanded_node: &Rc<ExpandedNode>,
519        _interrupt: &NativeInterrupt,
520    ) {
521        // no-op for many
522    }
523
524    fn handle_control_flow_node_expansion(
525        self: Rc<Self>,
526        _expanded_node: &Rc<ExpandedNode>,
527        _context: &Rc<RuntimeContext>,
528    ) {
529    }
530
531    fn handle_setup_projected_children(
532        self: Rc<Self>,
533        _expanded_node: &Rc<ExpandedNode>,
534        _context: &Rc<RuntimeContext>,
535    ) {
536    }
537
538    fn resolve_coverage_path(&self, _expanded_node: &ExpandedNode) -> Option<kurbo::BezPath> {
539        None
540    }
541
542    /// Resolves vector paint for an alpha-mask source without rendering or
543    /// mutating the source's retained canvas state.
544    fn resolve_alpha_mask_paints(
545        &self,
546        _expanded_node: &ExpandedNode,
547    ) -> Vec<pax_runtime_api::AlphaMaskPaint> {
548        Vec::new()
549    }
550
551    /// Returns coverage used by the occlusion/native-mask/layer-partition pass.
552    ///
553    /// The default uses the same coverage path as masks and hit-style geometry, but primitives
554    /// with render-side animation may return a conservative stable path here so animation-only
555    /// properties do not force the occlusion pass to rebuild expensive geometry every frame.
556    fn resolve_occlusion_path(&self, expanded_node: &ExpandedNode) -> Option<kurbo::BezPath> {
557        self.resolve_coverage_path(expanded_node)
558    }
559
560    fn resolve_coverage_opacity(&self, expanded_node: &ExpandedNode) -> f64 {
561        expanded_node.computed_opacity.get()
562    }
563
564    fn resolve_effect_clip_path(&self, _expanded_node: &ExpandedNode) -> Option<kurbo::BezPath> {
565        None
566    }
567}
568
569/// Shared storage carried by every concrete `InstanceNode`.
570pub struct BaseInstance {
571    pub handler_registry: Option<Rc<RefCell<HandlerRegistry>>>,
572    pub instance_prototypical_properties: PropertiesInit,
573    pub instance_prototypical_common_properties: CommonPropertiesInit,
574    pub component_settings: Option<Vec<SettingsBlockElement>>,
575    pub template_node_identifier: Option<UniqueTemplateNodeIdentifier>,
576    pub template_node_type_id: Option<TypeId>,
577    pub template_node_selector_info: Option<TemplateNodeSelectorInfo>,
578    pub transition_config: ComponentTransitionConfig,
579    pub properties_scope: PropertiesScopeInit,
580    instance_children: InstanceNodePtrList,
581    flags: InstanceFlags,
582}
583
584#[derive(Clone)]
585/// Static traversal/rendering flags for a concrete instance node.
586pub struct InstanceFlags {
587    /// Used for exotic tree traversals for `Slot`, e.g. for `Stacker` > `Repeat` > `Rectangle`
588    /// where the repeated `Rectangle`s need to be be considered direct children of `Stacker`.
589    /// `Repeat` and `Conditional` set this true so their active children can be
590    /// considered direct projected children by slot-driven containers.
591    pub invisible_to_slot: bool,
592    /// Certain elements, such as Groups and Components, are invisible to ray-casting.
593    /// Since these container elements are on top of the elements they contain,
594    /// this is needed otherwise the containers would intercept rays that should hit their contents.
595    pub invisible_to_raycasting: bool,
596    /// The layer type (`Layer::Native`, `Layer::NativeNonOccluding`, or `Layer::Canvas`)
597    /// for this RenderNode.
598    /// Default is `Layer::Canvas`, and must be overwritten for `InstanceNode`s that manage native
599    /// content.
600    pub layer: Layer,
601
602    /// Only true for ComponentInstance
603    pub is_component: bool,
604
605    /// Is this node a `Slot`?
606    pub is_slot: bool,
607}
608
609impl BaseInstance {
610    pub(crate) fn requires_pre_node_binding(&self) -> bool {
611        self.instance_prototypical_common_properties
612            .requires_pre_node_binding()
613            || self
614                .instance_prototypical_properties
615                .requires_pre_node_binding()
616            || matches!(self.properties_scope, PropertiesScopeInit::Factory(_))
617    }
618
619    /// Bind after stable slots and selector identity exist, before publishing
620    /// the final scope and activating listeners/lifecycle handlers.
621    /// Paired template initializers share one fresh resolution per bind, including
622    /// removal resets on reload. Custom factories keep their ordered callbacks.
623    pub(crate) fn bind_properties(&self, node: &Rc<ExpandedNode>) {
624        if let (
625            CommonPropertiesInit::Template(common_plan),
626            PropertiesInit::Template { descriptor, plan },
627        ) = (
628            &self.instance_prototypical_common_properties,
629            &self.instance_prototypical_properties,
630        ) {
631            if Rc::ptr_eq(common_plan, plan) {
632                let columns = plan.resolve(Some(node));
633                update_existing_common_properties_from_columns(node, &columns, &node.stack);
634                let properties = Rc::clone(&node.properties.borrow());
635                (descriptor.apply_defined_properties)(
636                    descriptor.typed_descriptor,
637                    &mut properties.borrow_mut(),
638                    &columns,
639                    &node.stack,
640                );
641                return;
642            }
643        }
644        self.instance_prototypical_common_properties
645            .materialize(Rc::clone(&node.stack), Some(Rc::clone(node)));
646        self.instance_prototypical_properties
647            .materialize(Rc::clone(&node.stack), Some(Rc::clone(node)));
648    }
649
650    /// Build shared instance state from compiler-generated instantiation args.
651    pub fn new(args: InstantiationArgs, flags: InstanceFlags) -> Self {
652        BaseInstance {
653            handler_registry: args.handler_registry,
654            instance_prototypical_common_properties: args.prototypical_common_properties,
655            instance_prototypical_properties: args.prototypical_properties,
656            component_settings: args.component_settings,
657            instance_children: args.children.unwrap_or_default(),
658            flags,
659            template_node_identifier: args.template_node_identifier,
660            template_node_type_id: args.template_node_type_id,
661            template_node_selector_info: args.template_node_selector_info,
662            transition_config: args.transition_config,
663            properties_scope: args.properties_scope,
664        }
665    }
666
667    /// Returns a handle to a node-managed HandlerRegistry, a mapping between event types and handlers.
668    /// Each node that can handle events is responsible for implementing this; Component instances generate
669    /// the necessary code to wire up userland events like `<SomeNode @click=self.handler>`. Primitives must handle
670    /// this explicitly, see e.g. `[pax_std::drawing::rectangle::RectangleInstance#get_handler_registry]`.
671    pub fn get_handler_registry(&self) -> Option<Rc<RefCell<HandlerRegistry>>> {
672        match &self.handler_registry {
673            Some(registry) => Some(Rc::clone(registry)),
674            _ => None,
675        }
676    }
677
678    /// Return the list of instance nodes that are children of this one. Intuitively, this returns
679    /// the nodes owned directly by this instance's definition.
680    ///
681    /// For `Component`s, this returns the root(s) of the component template, not the
682    /// projected children supplied by the containing component.
683    pub fn get_instance_children(&self) -> &InstanceNodePtrList {
684        &self.instance_children
685    }
686
687    /// Static behavior flags for this instance.
688    pub fn flags(&self) -> &InstanceFlags {
689        &self.flags
690    }
691
692    pub fn transition_config(&self) -> &ComponentTransitionConfig {
693        &self.transition_config
694    }
695}
696
697/// Resolved stroke style used by canvas drawing primitives.
698pub struct StrokeInstance {
699    pub color: Color,
700    pub width: f64,
701    pub style: StrokeStyle,
702}