torin/
torin.rs

1use std::{
2    collections::HashMap,
3    mem,
4};
5
6pub use euclid::Rect;
7use itertools::Itertools;
8use rustc_hash::FxHashMap;
9
10use crate::{
11    custom_measurer::LayoutMeasurer,
12    geometry::Area,
13    measure::{
14        MeasureContext,
15        Phase,
16    },
17    prelude::{
18        AreaConverter,
19        AreaModel,
20        AvailableAreaModel,
21        Gaps,
22        Length,
23    },
24    tree_adapter::{
25        LayoutNode,
26        NodeKey,
27        TreeAdapter,
28    },
29};
30
31pub struct LayoutMetadata {
32    pub root_area: Area,
33}
34
35/// Contains the best Root node candidate from where to start measuring
36#[derive(PartialEq, Debug, Clone)]
37pub enum RootNodeCandidate<Key: NodeKey> {
38    /// A valid Node ID
39    Valid(Key),
40
41    /// None
42    None,
43}
44
45impl<Key: NodeKey> RootNodeCandidate<Key> {
46    #[must_use]
47    pub fn take(&mut self) -> Self {
48        mem::replace(self, Self::None)
49    }
50
51    /// Propose a new root candidate
52    pub fn propose_new_candidate(
53        &mut self,
54        proposed_candidate: &Key,
55        tree_adapter: &mut impl TreeAdapter<Key>,
56        dirty: &mut FxHashMap<Key, DirtyReason>,
57    ) {
58        if let RootNodeCandidate::Valid(current_candidate) = self {
59            if current_candidate != proposed_candidate {
60                let mut continue_waking = true;
61                let closest_parent = tree_adapter.closest_common_parent(
62                    proposed_candidate,
63                    current_candidate,
64                    |id| {
65                        if !continue_waking {
66                            return;
67                        }
68                        let reason = dirty.get(&id);
69                        match reason {
70                            Some(DirtyReason::InnerLayout) => {
71                                // Replace [DirtyReason::InnerLayout] with [DirtyReason::None]
72                                // for all the nodes between the proposed candidate and the current candidate
73                                dirty.insert(id, DirtyReason::None);
74                            }
75                            Some(DirtyReason::None | DirtyReason::Reorder)
76                                if id != *proposed_candidate =>
77                            {
78                                // No need to continue checking if we encountered an ascendant
79                                // that is dirty but not with [DirtyReason::InnerLayout]
80                                continue_waking = false;
81                            }
82                            _ => {}
83                        }
84                    },
85                );
86
87                if let Some(closest_parent) = closest_parent {
88                    *self = RootNodeCandidate::Valid(closest_parent);
89                }
90            }
91        } else {
92            *self = RootNodeCandidate::Valid(*proposed_candidate);
93        }
94    }
95}
96
97#[derive(Clone, Copy, Debug, PartialEq)]
98pub enum DirtyReason {
99    None,
100    /// Node was moved from one position to another in its parent' children list.
101    Reorder,
102    /// The inner layout of the Node changed, e.g the offsets.
103    InnerLayout,
104}
105
106pub struct Torin<Key: NodeKey> {
107    /// Layout results of the registered Nodes
108    pub results: FxHashMap<Key, LayoutNode>,
109
110    /// Invalid registered nodes since previous layout measurement
111    pub dirty: FxHashMap<Key, DirtyReason>,
112
113    /// Best Root node candidate from where to start measuring
114    pub root_node_candidate: RootNodeCandidate<Key>,
115}
116
117impl<Key: NodeKey> Default for Torin<Key> {
118    fn default() -> Self {
119        Self::new()
120    }
121}
122
123impl<Key: NodeKey> Torin<Key> {
124    /// Create a new Layout
125    pub fn new() -> Self {
126        Self {
127            results: HashMap::default(),
128            dirty: FxHashMap::default(),
129            root_node_candidate: RootNodeCandidate::None,
130        }
131    }
132
133    pub fn size(&self) -> usize {
134        self.results.len()
135    }
136
137    /// Clear the dirty nodes buffer
138    pub fn clear_dirty(&mut self) {
139        self.dirty.clear();
140    }
141
142    /// Reset the layout
143    pub fn reset(&mut self) {
144        self.root_node_candidate = RootNodeCandidate::None;
145        self.results.clear();
146        self.dirty.clear();
147    }
148
149    /// Read the HashSet of dirty nodes
150    pub fn get_dirty_nodes(&self) -> &FxHashMap<Key, DirtyReason> {
151        &self.dirty
152    }
153
154    /// Remove a Node's result and data
155    pub fn raw_remove(&mut self, node_id: Key) {
156        self.results.remove(&node_id);
157        self.dirty.remove(&node_id);
158        if let RootNodeCandidate::Valid(id) = self.root_node_candidate
159            && id == node_id
160        {
161            self.root_node_candidate = RootNodeCandidate::None;
162        }
163    }
164
165    /// Remove a Node from the layout
166    /// # Panics
167    /// Might panic if the parent is not found.
168    pub fn remove(
169        &mut self,
170        node_id: Key,
171        tree_adapter: &mut impl TreeAdapter<Key>,
172        invalidate_parent: bool,
173    ) {
174        // Remove itself
175        self.raw_remove(node_id);
176
177        // Mark as dirty the Node's parent
178        if invalidate_parent && let Some(parent) = tree_adapter.parent_of(&node_id) {
179            self.invalidate(parent);
180        }
181
182        // Remove all it's children
183        for child_id in tree_adapter.children_of(&node_id) {
184            self.remove(child_id, tree_adapter, false);
185        }
186    }
187
188    /// Safely mark as dirty a Node, with no reason.
189    pub fn safe_invalidate(&mut self, node_id: Key) {
190        self.dirty.insert(node_id, DirtyReason::None);
191    }
192
193    /// Mark as dirty a Node, with no reason.
194    pub fn invalidate(&mut self, node_id: Key) {
195        self.dirty.insert(node_id, DirtyReason::None);
196    }
197
198    /// Mark as dirty a Node, with a reason.
199    pub fn invalidate_with_reason(&mut self, node_id: Key, reason: DirtyReason) {
200        self.dirty.entry(node_id).or_insert(reason);
201    }
202
203    // Mark as dirty the given Node and all the nodes that depend on it
204    pub fn check_dirty_dependants(
205        &mut self,
206        node_id: Key,
207        reason: DirtyReason,
208        tree_adapter: &mut impl TreeAdapter<Key>,
209        ignore: bool,
210    ) {
211        if self.dirty.contains_key(&node_id) && ignore {
212            return;
213        }
214
215        // Mark this node as dirty
216        self.invalidate_with_reason(node_id, reason);
217
218        self.root_node_candidate
219            .propose_new_candidate(&node_id, tree_adapter, &mut self.dirty);
220
221        // Mark this Node's parent if it is affected
222        let parent_id = tree_adapter.parent_of(&node_id);
223
224        if let Some(parent_id) = parent_id {
225            if reason == DirtyReason::InnerLayout {
226                self.root_node_candidate.propose_new_candidate(
227                    &parent_id,
228                    tree_adapter,
229                    &mut self.dirty,
230                );
231                return;
232            }
233
234            let parent = tree_adapter.get_node(&parent_id);
235
236            if let Some(parent) = parent {
237                if parent.does_depend_on_inner() {
238                    // Mark parent if it depends on it's inner children
239                    self.check_dirty_dependants(parent_id, DirtyReason::None, tree_adapter, true);
240                } else {
241                    let parent_children = tree_adapter.children_of(&parent_id);
242                    let multiple_children = parent_children.len() > 1;
243
244                    let mut found_node = match reason {
245                        DirtyReason::None | DirtyReason::InnerLayout => false,
246                        // Invalidate all siblings if the node was reordered
247                        DirtyReason::Reorder => true,
248                    };
249                    for child_id in parent_children {
250                        if found_node {
251                            self.safe_invalidate(child_id);
252                        }
253                        if child_id == node_id {
254                            found_node = true;
255                        }
256                    }
257
258                    // Try using the node's parent as root candidate if it has multiple children
259                    if multiple_children || parent.do_inner_depend_on_parent() {
260                        self.root_node_candidate.propose_new_candidate(
261                            &parent_id,
262                            tree_adapter,
263                            &mut self.dirty,
264                        );
265                    }
266                }
267            }
268        }
269    }
270
271    /// Get the Root Node candidate
272    pub fn get_root_candidate(&self) -> RootNodeCandidate<Key> {
273        self.root_node_candidate.clone()
274    }
275
276    /// Find the best root Node from where to start measuring
277    pub fn find_best_root(&mut self, tree_adapter: &mut impl TreeAdapter<Key>) {
278        if self.results.is_empty() {
279            return;
280        }
281        for (id, reason) in self
282            .dirty
283            .clone()
284            .into_iter()
285            .sorted_by_key(|e| tree_adapter.height(&e.0))
286        {
287            self.check_dirty_dependants(id, reason, tree_adapter, false);
288        }
289    }
290
291    /// Measure dirty Nodes
292    /// # Panics
293    /// Might panic if the final root node is not found.
294    pub fn measure(
295        &mut self,
296        suggested_root_id: Key,
297        root_area: Area,
298        measurer: &mut Option<impl LayoutMeasurer<Key>>,
299        tree_adapter: &mut impl TreeAdapter<Key>,
300    ) {
301        // If there are previosuly cached results
302        // But no dirty nodes, we can simply skip the measurement
303        // as this means no changes has been made to the layout
304        if self.dirty.is_empty() && !self.results.is_empty() {
305            return;
306        }
307
308        // Try the Root candidate otherwise use the provided Root
309        let root_id = if let RootNodeCandidate::Valid(id) = self.root_node_candidate.take() {
310            id
311        } else {
312            suggested_root_id
313        };
314        let root_parent_id = tree_adapter.parent_of(&root_id);
315        let layout_node = root_parent_id
316            .and_then(|root_parent_id| self.get(&root_parent_id).cloned())
317            .unwrap_or(LayoutNode {
318                area: root_area,
319                inner_area: root_area.as_inner(),
320                margin: Gaps::default(),
321                offset_x: Length::default(),
322                offset_y: Length::default(),
323                data: None,
324            });
325        let root = tree_adapter.get_node(&root_id).unwrap();
326
327        #[cfg(debug_assertions)]
328        {
329            let root_height = tree_adapter.height(&root_id).unwrap();
330            tracing::info!(
331                "Processing {} dirty nodes and {} cached nodes from a height of {}",
332                self.dirty.len(),
333                self.results.len(),
334                root_height
335            );
336        }
337
338        let layout_metadata = LayoutMetadata { root_area };
339
340        let mut available_area = layout_node.inner_area.as_available();
341        if let Some(root_parent_id) = root_parent_id {
342            let root_parent = tree_adapter.get_node(&root_parent_id).unwrap();
343            available_area.move_with_offsets(&root_parent.offset_x, &root_parent.offset_y);
344        }
345
346        let mut measure_context = MeasureContext {
347            layout: self,
348            layout_metadata,
349            tree_adapter,
350            measurer,
351        };
352
353        let (root_revalidated, mut root_layout_node) = measure_context.measure_node(
354            root_id,
355            &root,
356            layout_node.inner_area.as_parent(),
357            available_area,
358            true,
359            false,
360            Phase::Final,
361        );
362
363        // Cache the root Node results if it was modified
364        if root_revalidated {
365            // Adjust the size of the area if needed
366            root_layout_node.area.adjust_size(&root);
367
368            self.cache_node(root_id, root_layout_node);
369        }
370
371        self.dirty.clear();
372        self.root_node_candidate = RootNodeCandidate::None;
373    }
374
375    /// Get a reference to [LayoutNode] of a Node
376    pub fn get(&self, node_id: &Key) -> Option<&LayoutNode> {
377        self.results.get(node_id)
378    }
379
380    /// Get a mutable reference to [LayoutNode] of a Node
381    pub fn get_mut(&mut self, node_id: &Key) -> Option<&mut LayoutNode> {
382        self.results.get_mut(node_id)
383    }
384
385    /// Cache a Node's [LayoutNode]
386    pub fn cache_node(&mut self, node_id: Key, layout_node: LayoutNode) {
387        self.results.insert(node_id, layout_node);
388    }
389}