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