pax_std_primitives/
text.rs

1use pax_message::{AnyCreatePatch, TextPatch};
2use pax_runtime::api::{Layer, Property, RenderContext};
3use pax_runtime::{
4    BaseInstance, ExpandedNode, ExpandedNodeIdentifier, InstanceFlags, InstanceNode,
5    InstantiationArgs, RuntimeContext,
6};
7use pax_runtime_api::{borrow, borrow_mut, use_RefCell};
8use pax_std::primitives::Text;
9use_RefCell!();
10use std::collections::HashMap;
11use std::rc::Rc;
12#[cfg(feature = "designtime")]
13use {
14    kurbo::{RoundedRect, Shape},
15    pax_runtime::DEBUG_TEXT_GREEN_BACKGROUND,
16    piet::Color,
17};
18
19use crate::patch_if_needed;
20
21pub struct TextInstance {
22    base: BaseInstance,
23    // Properties that listen to Text property changes, and computes
24    // a patch in the case that they have changed + sends it as a native
25    // message to the chassi. Since InstanceNode -> ExpandedNode has a one
26    // to many relationship, needs to be a hashmap
27    native_message_props: RefCell<HashMap<ExpandedNodeIdentifier, Property<()>>>,
28}
29
30impl InstanceNode for TextInstance {
31    fn instantiate(args: InstantiationArgs) -> Rc<Self>
32    where
33        Self: Sized,
34    {
35        Rc::new(Self {
36            base: BaseInstance::new(
37                args,
38                InstanceFlags {
39                    invisible_to_slot: false,
40                    invisible_to_raycasting: false,
41                    layer: Layer::Native,
42                    is_component: false,
43                },
44            ),
45            native_message_props: Default::default(),
46        })
47    }
48
49    fn update(self: Rc<Self>, expanded_node: &Rc<ExpandedNode>, _context: &Rc<RuntimeContext>) {
50        //trigger computation of property that computes + sends native message update
51        borrow!(self.native_message_props)
52            .get(&expanded_node.id)
53            .unwrap()
54            .get();
55    }
56
57    fn render(
58        &self,
59        _expanded_node: &ExpandedNode,
60        _context: &Rc<RuntimeContext>,
61        _rc: &mut dyn RenderContext,
62    ) {
63        //no-op -- only native rendering for Text (unless/until we support rasterizing text, which Piet should be able to handle!)
64
65        #[cfg(feature = "designtime")]
66        if DEBUG_TEXT_GREEN_BACKGROUND {
67            let computed_props = borrow!(expanded_node.layout_properties);
68            let tab = &computed_props.as_ref().unwrap().computed_tab;
69            let layer_id = format!("{}", borrow!(expanded_node.occlusion_id));
70            let width: f64 = tab.bounds.0;
71            let height: f64 = tab.bounds.1;
72            let rect = RoundedRect::new(0.0, 0.0, width, height, 0.0);
73            let bez_path = rect.to_path(0.1);
74            let transformed_bez_path = Into::<kurbo::Affine>::into(tab.transform) * bez_path;
75            rc.fill(
76                &layer_id,
77                transformed_bez_path,
78                &piet::PaintBrush::Color(Color::rgba8(0, 255, 0, 100)),
79            );
80        }
81    }
82
83    fn handle_mount(
84        self: Rc<Self>,
85        expanded_node: &Rc<ExpandedNode>,
86        context: &Rc<RuntimeContext>,
87    ) {
88        // Send creation message
89        let id = expanded_node.id.to_u32();
90        context.enqueue_native_message(pax_message::NativeMessage::TextCreate(AnyCreatePatch {
91            id,
92            parent_frame: expanded_node.parent_frame.get().map(|v| v.to_u32()),
93            occlusion_layer_id: 0,
94        }));
95
96        // send update message when relevant properties change
97        let weak_self_ref = Rc::downgrade(&expanded_node);
98        let context = Rc::clone(context);
99        let last_patch = Rc::new(RefCell::new(TextPatch {
100            id,
101            ..Default::default()
102        }));
103
104        let deps: Vec<_> = borrow!(expanded_node.properties_scope)
105            .values()
106            .cloned()
107            .chain([expanded_node.transform_and_bounds.untyped()])
108            .collect();
109
110        borrow_mut!(self.native_message_props).insert(
111            expanded_node.id,
112            Property::computed(
113                move || {
114                    let Some(expanded_node) = weak_self_ref.upgrade() else {
115                        unreachable!()
116                    };
117                    let mut old_state = borrow_mut!(last_patch);
118
119                    let mut patch = TextPatch {
120                        id,
121                        ..Default::default()
122                    };
123                    expanded_node.with_properties_unwrapped(|properties: &mut Text| {
124                        let computed_tab = expanded_node.transform_and_bounds.get();
125                        let (width, height) = computed_tab.bounds;
126                        let cp = expanded_node.get_common_properties();
127                        let cp = borrow!(cp);
128                        // send width/height only if common props exist, otherwise we are in "listening mode"
129                        // trying to infer width and height from the engine. To signal this we
130                        // send width/height = -1.0, telling chassis that "you tell me!".
131                        let (width, height) = (
132                            cp.width.get().is_some().then_some(width).unwrap_or(-1.0),
133                            cp.height.get().is_some().then_some(height).unwrap_or(-1.0),
134                        );
135
136                        let updates = [
137                            // Content
138                            patch_if_needed(
139                                &mut old_state.content,
140                                &mut patch.content,
141                                properties.text.get(),
142                            ),
143                            // Styles
144                            patch_if_needed(
145                                &mut old_state.style,
146                                &mut patch.style,
147                                (&properties.style.get()).into(),
148                            ),
149                            patch_if_needed(
150                                &mut old_state.style_link,
151                                &mut patch.style_link,
152                                (&properties.style_link.get()).into(),
153                            ),
154                            patch_if_needed(
155                                &mut old_state.editable,
156                                &mut patch.editable,
157                                properties.editable.get(),
158                            ),
159                            // Transform and bounds
160                            patch_if_needed(&mut old_state.size_x, &mut patch.size_x, width),
161                            patch_if_needed(&mut old_state.size_y, &mut patch.size_y, height),
162                            patch_if_needed(
163                                &mut old_state.transform,
164                                &mut patch.transform,
165                                computed_tab.transform.coeffs().to_vec(),
166                            ),
167                        ];
168                        if updates.into_iter().any(|v| v == true) {
169                            context.enqueue_native_message(pax_message::NativeMessage::TextUpdate(
170                                patch,
171                            ));
172                        }
173                    });
174                    ()
175                },
176                &deps,
177            ),
178        );
179    }
180
181    fn handle_unmount(&self, expanded_node: &Rc<ExpandedNode>, context: &Rc<RuntimeContext>) {
182        let id = expanded_node.id.to_u32();
183        context.enqueue_native_message(pax_message::NativeMessage::TextDelete(id));
184        // Reset so that native_message sending updates while unmounted
185        borrow_mut!(self.native_message_props).remove(&expanded_node.id);
186    }
187
188    fn resolve_debug(
189        &self,
190        f: &mut std::fmt::Formatter,
191        expanded_node: Option<&ExpandedNode>,
192    ) -> std::fmt::Result {
193        match expanded_node {
194            Some(expanded_node) => expanded_node.with_properties_unwrapped(|r: &mut Text| {
195                f.debug_struct("Text").field("text", &r.text.get()).finish()
196            }),
197            None => f.debug_struct("Text").finish_non_exhaustive(),
198        }
199    }
200
201    fn base(&self) -> &BaseInstance {
202        &self.base
203    }
204
205    fn handle_text_change(&self, expanded_node: &Rc<ExpandedNode>, text: String) {
206        expanded_node.with_properties_unwrapped(|properties: &mut Text| properties.text.set(text));
207    }
208}