freya_core/dom/
doms.rs

1use std::sync::{
2    Arc,
3    Mutex,
4    MutexGuard,
5};
6#[cfg(feature = "rc-dom")]
7use std::{
8    cell::{
9        Ref,
10        RefCell,
11        RefMut,
12    },
13    rc::Rc,
14};
15
16use dioxus_core::VirtualDom;
17use freya_native_core::{
18    prelude::{
19        DioxusState,
20        State,
21    },
22    real_dom::{
23        NodeRef,
24        RealDom,
25    },
26    NodeId,
27    SendAnyMap,
28};
29use torin::prelude::*;
30
31use super::{
32    mutations_writer::MutationsWriter,
33    CompositorDirtyNodes,
34    ImagesCache,
35    ParagraphElements,
36};
37use crate::{
38    accessibility::{
39        AccessibilityDirtyNodes,
40        AccessibilityGenerator,
41    },
42    custom_attributes::CustomAttributeValues,
43    elements::ParagraphElement,
44    event_loop_messages::TextGroupMeasurement,
45    layers::Layers,
46    render::{
47        CompositorCache,
48        CompositorDirtyArea,
49    },
50    states::{
51        AccessibilityNodeState,
52        CanvasState,
53        CursorState,
54        FontStyleState,
55        ImageState,
56        LayerState,
57        LayoutState,
58        StyleState,
59        SvgState,
60        TransformState,
61        ViewportState,
62    },
63};
64
65pub type DioxusDOM = RealDom<CustomAttributeValues>;
66pub type DioxusNode<'a> = NodeRef<'a, CustomAttributeValues>;
67
68/// Tiny wrapper over [FreyaDOM] to make it thread-safe if desired.
69/// This is primarily used by the Devtools and Testing renderer.
70pub struct SafeDOM {
71    #[cfg(not(feature = "rc-dom"))]
72    pub fdom: FreyaDOM,
73
74    #[cfg(feature = "rc-dom")]
75    pub fdom: Rc<RefCell<FreyaDOM>>,
76}
77
78#[cfg(feature = "rc-dom")]
79impl Clone for SafeDOM {
80    fn clone(&self) -> Self {
81        Self {
82            fdom: self.fdom.clone(),
83        }
84    }
85}
86
87impl SafeDOM {
88    #[cfg(not(feature = "rc-dom"))]
89    pub fn new(fdom: FreyaDOM) -> Self {
90        Self { fdom }
91    }
92
93    #[cfg(feature = "rc-dom")]
94    pub fn new(fdom: FreyaDOM) -> Self {
95        Self {
96            fdom: Rc::new(RefCell::new(fdom)),
97        }
98    }
99
100    /// Get a reference to the DOM.
101    #[cfg(not(feature = "rc-dom"))]
102    pub fn get(&self) -> &FreyaDOM {
103        &self.fdom
104    }
105
106    /// Get a reference to the DOM.
107    #[cfg(not(feature = "rc-dom"))]
108    pub fn try_get(&self) -> Option<&FreyaDOM> {
109        Some(&self.fdom)
110    }
111
112    /// Get a mutable reference to the DOM.
113    #[cfg(not(feature = "rc-dom"))]
114    pub fn get_mut(&mut self) -> &mut FreyaDOM {
115        &mut self.fdom
116    }
117
118    /// Get a reference to the DOM.
119    #[cfg(feature = "rc-dom")]
120    pub fn get(&self) -> Ref<FreyaDOM> {
121        return self.fdom.borrow();
122    }
123
124    /// Get a mutable reference to the dom.
125    #[cfg(feature = "rc-dom")]
126    pub fn get_mut(&self) -> RefMut<FreyaDOM> {
127        return self.fdom.borrow_mut();
128    }
129}
130
131/// Manages the application DOM.
132pub struct FreyaDOM {
133    rdom: DioxusDOM,
134    dioxus_integration_state: DioxusState,
135    torin: Arc<Mutex<Torin<NodeId>>>,
136    paragraphs: Arc<Mutex<ParagraphElements>>,
137    layers: Arc<Mutex<Layers>>,
138    compositor_dirty_nodes: Arc<Mutex<CompositorDirtyNodes>>,
139    compositor_dirty_area: Arc<Mutex<CompositorDirtyArea>>,
140    compositor_cache: Arc<Mutex<CompositorCache>>,
141    accessibility_dirty_nodes: Arc<Mutex<AccessibilityDirtyNodes>>,
142    accessibility_generator: Arc<AccessibilityGenerator>,
143    images_cache: Arc<Mutex<ImagesCache>>,
144}
145
146impl Default for FreyaDOM {
147    fn default() -> Self {
148        let mut rdom = RealDom::<CustomAttributeValues>::new([
149            CursorState::to_type_erased(),
150            FontStyleState::to_type_erased(),
151            CanvasState::to_type_erased(),
152            LayoutState::to_type_erased(),
153            StyleState::to_type_erased(),
154            TransformState::to_type_erased(),
155            AccessibilityNodeState::to_type_erased(),
156            ViewportState::to_type_erased(),
157            LayerState::to_type_erased(),
158            SvgState::to_type_erased(),
159            ImageState::to_type_erased(),
160        ]);
161        let dioxus_integration_state = DioxusState::create(&mut rdom);
162        Self {
163            rdom,
164            dioxus_integration_state,
165            torin: Arc::new(Mutex::new(Torin::new())),
166            paragraphs: Arc::default(),
167            layers: Arc::default(),
168            compositor_dirty_nodes: Arc::default(),
169            compositor_dirty_area: Arc::default(),
170            compositor_cache: Arc::default(),
171            accessibility_dirty_nodes: Arc::default(),
172            accessibility_generator: Arc::default(),
173            images_cache: Arc::default(),
174        }
175    }
176}
177
178impl FreyaDOM {
179    pub fn layout(&self) -> MutexGuard<Torin<NodeId>> {
180        self.torin.lock().unwrap()
181    }
182
183    pub fn layers(&self) -> MutexGuard<Layers> {
184        self.layers.lock().unwrap()
185    }
186
187    pub fn paragraphs(&self) -> MutexGuard<ParagraphElements> {
188        self.paragraphs.lock().unwrap()
189    }
190
191    pub fn compositor_dirty_nodes(&self) -> MutexGuard<CompositorDirtyNodes> {
192        self.compositor_dirty_nodes.lock().unwrap()
193    }
194
195    pub fn compositor_dirty_area(&self) -> MutexGuard<CompositorDirtyArea> {
196        self.compositor_dirty_area.lock().unwrap()
197    }
198
199    pub fn compositor_cache(&self) -> MutexGuard<CompositorCache> {
200        self.compositor_cache.lock().unwrap()
201    }
202
203    pub fn accessibility_dirty_nodes(&self) -> MutexGuard<AccessibilityDirtyNodes> {
204        self.accessibility_dirty_nodes.lock().unwrap()
205    }
206
207    pub fn accessibility_generator(&self) -> &Arc<AccessibilityGenerator> {
208        &self.accessibility_generator
209    }
210
211    pub fn images_cache(&self) -> MutexGuard<ImagesCache> {
212        self.images_cache.lock().unwrap()
213    }
214
215    /// Create the initial DOM from the given Mutations
216    pub fn init_dom(&mut self, vdom: &mut VirtualDom, scale_factor: f32) {
217        // Build the RealDOM
218        vdom.rebuild(&mut MutationsWriter {
219            native_writer: self
220                .dioxus_integration_state
221                .create_mutation_writer(&mut self.rdom),
222            layout: &mut self.torin.lock().unwrap(),
223            layers: &mut self.layers.lock().unwrap(),
224            paragraphs: &mut self.paragraphs.lock().unwrap(),
225            scale_factor,
226            compositor_dirty_nodes: &mut self.compositor_dirty_nodes.lock().unwrap(),
227            compositor_dirty_area: &mut self.compositor_dirty_area.lock().unwrap(),
228            compositor_cache: &mut self.compositor_cache.lock().unwrap(),
229            accessibility_dirty_nodes: &mut self.accessibility_dirty_nodes.lock().unwrap(),
230            images_cache: &mut self.images_cache.lock().unwrap(),
231        });
232
233        let mut ctx = SendAnyMap::new();
234        ctx.insert(self.torin.clone());
235        ctx.insert(self.layers.clone());
236        ctx.insert(self.paragraphs.clone());
237        ctx.insert(self.compositor_dirty_nodes.clone());
238        ctx.insert(self.accessibility_dirty_nodes.clone());
239        ctx.insert(self.rdom.root_id());
240        ctx.insert(self.accessibility_generator.clone());
241        ctx.insert(self.images_cache.clone());
242
243        self.rdom.update_state(ctx);
244    }
245
246    /// Process the given mutations from the [`VirtualDOM`](dioxus_core::VirtualDom).
247    pub fn render_mutations(&mut self, vdom: &mut VirtualDom, scale_factor: f32) -> (bool, bool) {
248        // Update the RealDOM
249        vdom.render_immediate(&mut MutationsWriter {
250            native_writer: self
251                .dioxus_integration_state
252                .create_mutation_writer(&mut self.rdom),
253            layout: &mut self.torin.lock().unwrap(),
254            layers: &mut self.layers.lock().unwrap(),
255            paragraphs: &mut self.paragraphs.lock().unwrap(),
256            scale_factor,
257            compositor_dirty_nodes: &mut self.compositor_dirty_nodes.lock().unwrap(),
258            compositor_dirty_area: &mut self.compositor_dirty_area.lock().unwrap(),
259            compositor_cache: &mut self.compositor_cache.lock().unwrap(),
260            accessibility_dirty_nodes: &mut self.accessibility_dirty_nodes.lock().unwrap(),
261            images_cache: &mut self.images_cache.lock().unwrap(),
262        });
263
264        // Update the Nodes states
265        let mut ctx = SendAnyMap::new();
266        ctx.insert(self.torin.clone());
267        ctx.insert(self.layers.clone());
268        ctx.insert(self.paragraphs.clone());
269        ctx.insert(self.compositor_dirty_nodes.clone());
270        ctx.insert(self.accessibility_dirty_nodes.clone());
271        ctx.insert(self.rdom.root_id());
272        ctx.insert(self.accessibility_generator.clone());
273        ctx.insert(self.images_cache.clone());
274
275        // Update the Node's states
276        let diff = self.rdom.update_state(ctx);
277
278        let must_repaint = !diff.is_empty();
279        let must_relayout = !self.layout().get_dirty_nodes().is_empty();
280
281        #[cfg(debug_assertions)]
282        if !diff.is_empty() {
283            tracing::info!(
284                "Updated {} nodes in RealDOM, now of size {}",
285                diff.len(),
286                self.rdom().tree_ref().len()
287            );
288        }
289
290        (must_repaint, must_relayout)
291    }
292
293    /// Get a reference to the [`DioxusDOM`].
294    pub fn rdom(&self) -> &DioxusDOM {
295        &self.rdom
296    }
297
298    /// Get a mutable reference to the [`DioxusDOM`].
299    pub fn rdom_mut(&mut self) -> &mut DioxusDOM {
300        &mut self.rdom
301    }
302
303    pub fn state_mut(&mut self) -> &mut DioxusState {
304        &mut self.dioxus_integration_state
305    }
306
307    /// Measure all the paragraphs registered under the given TextId
308    pub fn measure_paragraphs(&self, text_measurement: TextGroupMeasurement, scale_factor: f64) {
309        let paragraphs = self.paragraphs.lock().unwrap();
310        let group = paragraphs.get(&text_measurement.text_id);
311        let layout = self.layout();
312        if let Some(group) = group {
313            for node_id in group {
314                let node = self.rdom().get(*node_id);
315                let layout_node = layout.get(*node_id);
316
317                if let Some((node, layout_node)) = node.zip(layout_node) {
318                    ParagraphElement::measure_paragraph(
319                        &node,
320                        layout_node,
321                        &text_measurement,
322                        scale_factor,
323                    );
324                }
325            }
326        }
327    }
328}