torin/
torin.rs

1use std::{
2    collections::HashMap,
3    mem,
4};
5
6use itertools::Itertools;
7use rustc_hash::FxHashMap;
8
9use crate::{
10    custom_measurer::LayoutMeasurer,
11    geometry::Area,
12    measure::{
13        MeasureContext,
14        Phase,
15    },
16    prelude::{
17        AreaConverter,
18        AreaModel,
19        AvailableAreaModel,
20        Gaps,
21        Length,
22        Size2D,
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 measuringg
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_walking = true;
61                let closest_parent = tree_adapter.closest_common_parent(
62                    proposed_candidate,
63                    current_candidate,
64                    |id| {
65                        if !continue_walking {
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_walking = 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 measuringg
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 measuringg
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 previously 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                inner_sizes: Size2D::default(),
321                margin: Gaps::default(),
322                offset_x: Length::default(),
323                offset_y: Length::default(),
324                data: None,
325            });
326        let root = tree_adapter.get_node(&root_id).unwrap();
327
328        #[cfg(debug_assertions)]
329        {
330            let root_height = tree_adapter.height(&root_id).unwrap();
331            tracing::info!(
332                "Processing {} dirty nodes and {} cached nodes from a height of {}",
333                self.dirty.len(),
334                self.results.len(),
335                root_height
336            );
337        }
338
339        let layout_metadata = LayoutMetadata { root_area };
340
341        let mut available_area = layout_node.inner_area.as_available();
342        if let Some(root_parent_id) = root_parent_id {
343            let root_parent = tree_adapter.get_node(&root_parent_id).unwrap();
344            available_area.move_with_offsets(&root_parent.offset_x, &root_parent.offset_y);
345        }
346
347        let mut measure_context = MeasureContext {
348            layout: self,
349            layout_metadata,
350            tree_adapter,
351            measurer,
352        };
353
354        let (root_revalidated, mut root_layout_node) = measure_context.measure_node(
355            root_id,
356            &root,
357            layout_node.inner_area.as_parent(),
358            layout_node.inner_area.as_parent(),
359            available_area,
360            true,
361            false,
362            Phase::Final,
363        );
364
365        // Cache the root Node results if it was modified
366        if root_revalidated {
367            // Adjust the size of the area if needed
368            root_layout_node.area.adjust_size(&root);
369
370            self.cache_node(root_id, root_layout_node);
371        }
372
373        self.dirty.clear();
374        self.root_node_candidate = RootNodeCandidate::None;
375    }
376
377    /// Get a reference to [LayoutNode] of a Node
378    pub fn get(&self, node_id: &Key) -> Option<&LayoutNode> {
379        self.results.get(node_id)
380    }
381
382    /// Get a mutable reference to [LayoutNode] of a Node
383    pub fn get_mut(&mut self, node_id: &Key) -> Option<&mut LayoutNode> {
384        self.results.get_mut(node_id)
385    }
386
387    /// Cache a Node's [LayoutNode]
388    pub fn cache_node(&mut self, node_id: Key, layout_node: LayoutNode) {
389        self.results.insert(node_id, layout_node);
390    }
391}