Skip to main content

pax_runtime/
component.rs

1use std::cell::Ref;
2use std::collections::HashMap;
3use std::iter;
4use std::rc::Rc;
5
6use pax_runtime_api::{
7    borrow, borrow_mut, use_RefCell, Interpolatable, LocalProperty, PaxValue, ToPaxValue, Variable,
8};
9
10use_RefCell!();
11use crate::api::{Layer, Timeline};
12use crate::{
13    BaseInstance, ExpandedNode, InstanceFlags, InstanceNode, InstanceNodePtrList,
14    InstantiationArgs, RuntimeContext,
15};
16
17/// A render node with its own runtime context. Will push a frame
18/// to the runtime stack including the specified `projected_children` and
19/// a `PaxType` properties object.  `Component` is used at the root of
20/// applications, at the root of reusable components like `Stacker`, and
21/// in special applications like `Repeat` where it houses the `RepeatItem`
22/// properties attached to each of Repeat's virtual nodes.
23pub struct ComponentInstance {
24    pub template: InstanceNodePtrList,
25    pub timelines: Vec<Rc<RefCell<Timeline>>>,
26    base: BaseInstance,
27}
28
29// #[derive(Default)]
30// pub struct ComponentProperties {
31//     pub projected_children: BTreeSet<Rc<ExpandedNode>>,
32// }
33
34impl InstanceNode for ComponentInstance {
35    fn instantiate(mut args: InstantiationArgs) -> Rc<Self> {
36        let component_template = args.component_template.take();
37        let template = component_template.unwrap_or_default();
38        let base = BaseInstance::new(
39            args,
40            InstanceFlags {
41                invisible_to_slot: false,
42                invisible_to_raycasting: true,
43                layer: Layer::DontCare,
44                is_component: true,
45                is_slot: false,
46            },
47        );
48        Rc::new(ComponentInstance {
49            base,
50            template,
51            timelines: vec![],
52        })
53    }
54
55    fn handle_setup_projected_children(
56        self: Rc<Self>,
57        expanded_node: &Rc<ExpandedNode>,
58        context: &Rc<RuntimeContext>,
59    ) {
60        if let Some(containing_component) = expanded_node.containing_component.upgrade() {
61            let env = if let Some(stack_frame) =
62                ScrollPosition::create_builtin_if_exists(borrow!(expanded_node.properties_scope))
63            {
64                expanded_node.stack.push(stack_frame)
65            } else {
66                Rc::clone(&expanded_node.stack)
67            };
68            let children = borrow!(self.base().get_instance_children());
69            let children_with_env = children.iter().cloned().zip(iter::repeat(env));
70            let new_projected_children = containing_component.create_children_detached(
71                children_with_env,
72                context,
73                &Rc::downgrade(expanded_node),
74            );
75            *borrow_mut!(expanded_node.expanded_projected_children) = Some(new_projected_children);
76        }
77    }
78
79    fn handle_mount(
80        self: Rc<Self>,
81        expanded_node: &Rc<ExpandedNode>,
82        context: &Rc<RuntimeContext>,
83    ) {
84        let new_env = {
85            let mut properties_scope = borrow_mut!(expanded_node.properties_scope);
86            properties_scope.insert(
87                "$suspended".to_string(),
88                Variable::new_from_typed_property(expanded_node.suspended.clone()),
89            );
90            expanded_node.stack.push(properties_scope.clone())
91        };
92        let children = borrow!(self.template);
93        let children_with_envs = children.iter().cloned().zip(iter::repeat(new_env));
94        expanded_node
95            .children
96            .replace_with(LocalProperty::new_with_name(
97                expanded_node.generate_children(
98                    children_with_envs,
99                    context,
100                    &expanded_node.parent_frame,
101                    true,
102                ),
103                &format!("component (node id: {})", expanded_node.id.0),
104            ));
105        drop(children);
106        // A component can mount after the tick's settings pass (for example in
107        // @pre_render). Bind its initial theme before it can be presented or the
108        // frame clock advances, which would turn defaults into animation endpoints.
109        expanded_node.sync_imported_settings(context);
110    }
111
112    fn handle_unmount(&self, expanded_node: &Rc<ExpandedNode>, context: &Rc<RuntimeContext>) {
113        if let Some(projected_children) =
114            borrow_mut!(expanded_node.expanded_projected_children).take()
115        {
116            for projected_child in projected_children {
117                projected_child.recurse_unmount(context);
118            }
119        }
120    }
121
122    fn resolve_debug(
123        &self,
124        f: &mut std::fmt::Formatter,
125        _expanded_node: Option<&ExpandedNode>,
126    ) -> std::fmt::Result {
127        f.debug_struct("Component").finish()
128    }
129
130    fn base(&self) -> &BaseInstance {
131        &self.base
132    }
133
134    fn property_requires_occlusion_recompute(&self, _property_name: &str) -> bool {
135        // Components do not draw; state-driven occlusion changes surface through concrete children.
136        false
137    }
138
139    fn get_template(&self) -> Option<&InstanceNodePtrList> {
140        Some(&self.template)
141    }
142}
143
144#[derive(Debug, Clone, Default)]
145/// Built-in `$scroll_position` value synthesized for components inside scrollers.
146pub struct ScrollPosition {
147    pub x: f64,
148    pub y: f64,
149}
150
151impl Interpolatable for ScrollPosition {
152    fn interpolate(&self, other: &Self, t: f64) -> Self {
153        ScrollPosition {
154            x: self.x + (other.x - self.x) * t,
155            y: self.y + (other.y - self.y) * t,
156        }
157    }
158}
159
160impl ToPaxValue for ScrollPosition {
161    fn to_pax_value(self) -> PaxValue {
162        PaxValue::Object(
163            vec![
164                ("x".to_string(), self.x.to_pax_value()),
165                ("y".to_string(), self.y.to_pax_value()),
166            ]
167            .into_iter()
168            .collect(),
169        )
170    }
171}
172
173impl ScrollPosition {
174    /// Create a stack frame containing `$scroll_position` when scroll properties are present.
175    pub fn create_builtin_if_exists(
176        property_scope: Ref<HashMap<String, Variable>>,
177    ) -> Option<HashMap<String, Variable>> {
178        let scroll_pos_x: LocalProperty<f64> = LocalProperty::new_from_untyped(
179            property_scope
180                .get("scroll_pos_x")?
181                .get_untyped_property()
182                .clone(),
183        );
184        let scroll_pos_y: LocalProperty<f64> = LocalProperty::new_from_untyped(
185            property_scope
186                .get("scroll_pos_y")?
187                .get_untyped_property()
188                .clone(),
189        );
190        let deps = [scroll_pos_x.untyped(), scroll_pos_y.untyped()];
191        let scroll_position = LocalProperty::computed(
192            move || ScrollPosition {
193                x: scroll_pos_x.get(),
194                y: scroll_pos_y.get(),
195            },
196            &deps,
197        );
198
199        let scroll_position_var = Variable::new_from_typed_property(scroll_position);
200        let stack_frame = vec![("$scroll_position".to_string(), scroll_position_var)]
201            .into_iter()
202            .collect();
203        Some(stack_frame)
204    }
205}