pax_std_primitives/
image.rs

1use pax_runtime::{api::Property, api::RenderContext, ExpandedNodeIdentifier};
2use pax_runtime_api::{borrow, borrow_mut, use_RefCell};
3use pax_std::{primitives::Image, types::ImageFit};
4use std::collections::HashMap;
5
6use_RefCell!();
7use pax_message::ImagePatch;
8use pax_runtime::{
9    BaseInstance, ExpandedNode, InstanceFlags, InstanceNode, InstantiationArgs, RuntimeContext,
10};
11use std::rc::Rc;
12
13use crate::patch_if_needed;
14/// An Image (decoded by chassis), drawn to the bounds specified
15/// by `size`, transformed by `transform`
16pub struct ImageInstance {
17    base: BaseInstance,
18    // Properties that listen to Image property changes, and computes
19    // a patch in the case that they have changed + sends it as a native
20    // message to the chassi. Since InstanceNode -> ExpandedNode has a one
21    // to many relationship, needs to be a hashmap
22    native_message_props: RefCell<HashMap<ExpandedNodeIdentifier, Property<()>>>,
23}
24
25impl InstanceNode for ImageInstance {
26    fn instantiate(args: InstantiationArgs) -> Rc<Self>
27    where
28        Self: Sized,
29    {
30        Rc::new(Self {
31            base: BaseInstance::new(
32                args,
33                InstanceFlags {
34                    invisible_to_slot: false,
35                    invisible_to_raycasting: false,
36                    layer: pax_runtime::api::Layer::Canvas,
37                    is_component: false,
38                },
39            ),
40            native_message_props: Default::default(),
41        })
42    }
43
44    fn update(self: Rc<Self>, expanded_node: &Rc<ExpandedNode>, _context: &Rc<RuntimeContext>) {
45        //trigger computation of property that computes + sends native message update
46        borrow!(self.native_message_props)
47            .get(&expanded_node.id)
48            .unwrap()
49            .get();
50    }
51
52    fn handle_mount(
53        self: Rc<Self>,
54        expanded_node: &Rc<ExpandedNode>,
55        context: &Rc<RuntimeContext>,
56    ) {
57        let id = expanded_node.id.to_u32();
58
59        // send update message when relevant properties change
60        let weak_self_ref = Rc::downgrade(&expanded_node);
61        let context = Rc::clone(context);
62        let last_patch = Rc::new(RefCell::new(ImagePatch {
63            id,
64            ..Default::default()
65        }));
66
67        let deps: Vec<_> = borrow!(expanded_node.properties_scope)
68            .values()
69            .cloned()
70            .chain([expanded_node.transform_and_bounds.untyped()])
71            .collect();
72        borrow_mut!(self.native_message_props).insert(
73            expanded_node.id,
74            Property::computed(
75                move || {
76                    let Some(expanded_node) = weak_self_ref.upgrade() else {
77                        unreachable!()
78                    };
79                    let mut old_state = borrow_mut!(last_patch);
80
81                    let mut patch = ImagePatch {
82                        id,
83                        ..Default::default()
84                    };
85                    let path_val = expanded_node
86                        .with_properties_unwrapped(|props: &mut Image| props.path.get().clone());
87
88                    let update = patch_if_needed(&mut old_state.path, &mut patch.path, path_val);
89
90                    if update {
91                        context
92                            .enqueue_native_message(pax_message::NativeMessage::ImageLoad(patch));
93                    }
94                    ()
95                },
96                &deps,
97            ),
98        );
99    }
100
101    fn handle_unmount(&self, expanded_node: &Rc<ExpandedNode>, _context: &Rc<RuntimeContext>) {
102        let id = expanded_node.id.clone();
103        // Reset so that native_message stops sending updates while unmounted
104        borrow_mut!(self.native_message_props).remove(&id);
105    }
106
107    fn render(
108        &self,
109        expanded_node: &ExpandedNode,
110        _rtc: &Rc<RuntimeContext>,
111        rc: &mut dyn RenderContext,
112    ) {
113        let t_and_b = expanded_node.transform_and_bounds.get();
114        let (container_width, container_height) = t_and_b.bounds;
115
116        expanded_node.with_properties_unwrapped(|props: &mut Image| {
117            let path = props.path.get();
118            let Some((image_width, image_height)) = rc.get_image_size(&path) else {
119                // image not loaded yet
120                return;
121            };
122            let (image_width, image_height) = (image_width as f64, image_height as f64);
123            let stretch_w = container_width / image_width;
124            let stretch_h = container_height / image_height;
125            let (width, height) = match props.fit.get() {
126                ImageFit::FillVertical => (image_width * stretch_h, image_height * stretch_h),
127                ImageFit::FillHorizontal => (image_width * stretch_w, image_height * stretch_w),
128                ImageFit::Fill => {
129                    let stretch = stretch_h.max(stretch_w);
130                    (image_width * stretch, image_height * stretch)
131                }
132                ImageFit::Fit => {
133                    let stretch = stretch_h.min(stretch_w);
134                    (image_width * stretch, image_height * stretch)
135                }
136                ImageFit::Stretch => (container_width, container_height),
137            };
138            let x = (container_width - width) / 2.0;
139            let y = (container_height - height) / 2.0;
140            let transformed_bounds = kurbo::Rect::new(x, y, x + width, y + height);
141            let layer_id = format!("{}", borrow!(expanded_node.occlusion_id));
142            rc.save(&layer_id);
143            rc.transform(&layer_id, t_and_b.transform.into());
144            rc.draw_image(&layer_id, &path, transformed_bounds);
145            rc.restore(&layer_id);
146        });
147    }
148
149    fn base(&self) -> &BaseInstance {
150        &self.base
151    }
152
153    fn resolve_debug(
154        &self,
155        f: &mut std::fmt::Formatter,
156        _expanded_node: Option<&pax_runtime::ExpandedNode>,
157    ) -> std::fmt::Result {
158        f.debug_struct("Image").finish_non_exhaustive()
159    }
160}