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 dom_adapter::{
13 LayoutNode,
14 NodeKey,
15 TreeAdapter,
16 },
17 geometry::Area,
18 measure::{
19 MeasureContext,
20 Phase,
21 },
22 prelude::{
23 AreaModel,
24 Gaps,
25 Length,
26 },
27};
28
29pub struct LayoutMetadata {
30 pub root_area: Area,
31}
32
33#[derive(PartialEq, Debug, Clone)]
35pub enum RootNodeCandidate<Key: NodeKey> {
36 Valid(Key),
38
39 None,
41}
42
43impl<Key: NodeKey> RootNodeCandidate<Key> {
44 #[must_use]
45 pub fn take(&mut self) -> Self {
46 mem::replace(self, Self::None)
47 }
48
49 pub fn propose_new_candidate(
51 &mut self,
52 proposed_candidate: &Key,
53 dom_adapter: &mut impl TreeAdapter<Key>,
54 dirty: &mut FxHashMap<Key, DirtyReason>,
55 ) {
56 if let RootNodeCandidate::Valid(current_candidate) = self {
57 if current_candidate != proposed_candidate {
58 let mut continue_waking = true;
59 let closest_parent = dom_adapter.closest_common_parent(
60 proposed_candidate,
61 current_candidate,
62 |id| {
63 if !continue_waking {
64 return;
65 }
66 let reason = dirty.get(&id);
67 match reason {
68 Some(DirtyReason::InnerLayout) => {
69 dirty.insert(id, DirtyReason::None);
72 }
73 Some(DirtyReason::None | DirtyReason::Reorder)
74 if id != *proposed_candidate =>
75 {
76 continue_waking = false;
79 }
80 _ => {}
81 }
82 },
83 );
84
85 if let Some(closest_parent) = closest_parent {
86 *self = RootNodeCandidate::Valid(closest_parent);
87 }
88 }
89 } else {
90 *self = RootNodeCandidate::Valid(*proposed_candidate);
91 }
92 }
93}
94
95#[derive(Clone, Copy, Debug, PartialEq)]
96pub enum DirtyReason {
97 None,
98 Reorder,
100 InnerLayout,
102}
103
104pub struct Torin<Key: NodeKey> {
105 pub results: FxHashMap<Key, LayoutNode>,
107
108 pub dirty: FxHashMap<Key, DirtyReason>,
110
111 pub root_node_candidate: RootNodeCandidate<Key>,
113}
114
115impl<Key: NodeKey> Default for Torin<Key> {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121impl<Key: NodeKey> Torin<Key> {
122 pub fn new() -> Self {
124 Self {
125 results: HashMap::default(),
126 dirty: FxHashMap::default(),
127 root_node_candidate: RootNodeCandidate::None,
128 }
129 }
130
131 pub fn size(&self) -> usize {
132 self.results.len()
133 }
134
135 pub fn clear_dirty(&mut self) {
137 self.dirty.clear();
138 }
139
140 pub fn reset(&mut self) {
142 self.root_node_candidate = RootNodeCandidate::None;
143 self.results.clear();
144 self.dirty.clear();
145 }
146
147 pub fn get_dirty_nodes(&self) -> &FxHashMap<Key, DirtyReason> {
149 &self.dirty
150 }
151
152 pub fn raw_remove(&mut self, node_id: Key) {
154 self.results.remove(&node_id);
155 self.dirty.remove(&node_id);
156 if let RootNodeCandidate::Valid(id) = self.root_node_candidate
157 && id == node_id
158 {
159 self.root_node_candidate = RootNodeCandidate::None;
160 }
161 }
162
163 pub fn remove(
167 &mut self,
168 node_id: Key,
169 dom_adapter: &mut impl TreeAdapter<Key>,
170 invalidate_parent: bool,
171 ) {
172 self.raw_remove(node_id);
174
175 if invalidate_parent && let Some(parent) = dom_adapter.parent_of(&node_id) {
177 self.invalidate(parent);
178 }
179
180 for child_id in dom_adapter.children_of(&node_id) {
182 self.remove(child_id, dom_adapter, false);
183 }
184 }
185
186 pub fn safe_invalidate(&mut self, node_id: Key) {
188 self.dirty.insert(node_id, DirtyReason::None);
189 }
190
191 pub fn invalidate(&mut self, node_id: Key) {
193 self.dirty.insert(node_id, DirtyReason::None);
194 }
195
196 pub fn invalidate_with_reason(&mut self, node_id: Key, reason: DirtyReason) {
198 self.dirty.entry(node_id).or_insert(reason);
199 }
200
201 pub fn check_dirty_dependants(
203 &mut self,
204 node_id: Key,
205 reason: DirtyReason,
206 dom_adapter: &mut impl TreeAdapter<Key>,
207 ignore: bool,
208 ) {
209 if self.dirty.contains_key(&node_id) && ignore {
210 return;
211 }
212
213 self.invalidate_with_reason(node_id, reason);
215
216 self.root_node_candidate
217 .propose_new_candidate(&node_id, dom_adapter, &mut self.dirty);
218
219 let parent_id = dom_adapter.parent_of(&node_id);
221
222 if let Some(parent_id) = parent_id {
223 if reason == DirtyReason::InnerLayout {
224 self.root_node_candidate.propose_new_candidate(
225 &parent_id,
226 dom_adapter,
227 &mut self.dirty,
228 );
229 return;
230 }
231
232 let parent = dom_adapter.get_node(&parent_id);
233
234 if let Some(parent) = parent {
235 if parent.does_depend_on_inner() {
236 self.check_dirty_dependants(parent_id, DirtyReason::None, dom_adapter, true);
238 } else {
239 let parent_children = dom_adapter.children_of(&parent_id);
240 let multiple_children = parent_children.len() > 1;
241
242 let mut found_node = match reason {
243 DirtyReason::None | DirtyReason::InnerLayout => false,
244 DirtyReason::Reorder => true,
246 };
247 for child_id in parent_children {
248 if found_node {
249 self.safe_invalidate(child_id);
250 }
251 if child_id == node_id {
252 found_node = true;
253 }
254 }
255
256 if multiple_children || parent.do_inner_depend_on_parent() {
258 self.root_node_candidate.propose_new_candidate(
259 &parent_id,
260 dom_adapter,
261 &mut self.dirty,
262 );
263 }
264 }
265 }
266 }
267 }
268
269 pub fn get_root_candidate(&self) -> RootNodeCandidate<Key> {
271 self.root_node_candidate.clone()
272 }
273
274 pub fn find_best_root(&mut self, dom_adapter: &mut impl TreeAdapter<Key>) {
276 if self.results.is_empty() {
277 return;
278 }
279 for (id, reason) in self
280 .dirty
281 .clone()
282 .into_iter()
283 .sorted_by_key(|e| dom_adapter.height(&e.0))
284 {
285 self.check_dirty_dependants(id, reason, dom_adapter, false);
286 }
287 }
288
289 pub fn measure(
293 &mut self,
294 suggested_root_id: Key,
295 root_area: Area,
296 measurer: &mut Option<impl LayoutMeasurer<Key>>,
297 dom_adapter: &mut impl TreeAdapter<Key>,
298 ) {
299 if self.dirty.is_empty() && !self.results.is_empty() {
303 return;
304 }
305
306 let root_id = if let RootNodeCandidate::Valid(id) = self.root_node_candidate.take() {
308 id
309 } else {
310 suggested_root_id
311 };
312 let root_parent_id = dom_adapter.parent_of(&root_id);
313 let layout_node = root_parent_id
314 .and_then(|root_parent_id| self.get(&root_parent_id).cloned())
315 .unwrap_or(LayoutNode {
316 area: root_area,
317 inner_area: root_area,
318 margin: Gaps::default(),
319 offset_x: Length::default(),
320 offset_y: Length::default(),
321 data: None,
322 });
323 let root = dom_adapter.get_node(&root_id).unwrap();
324
325 #[cfg(debug_assertions)]
326 {
327 let root_height = dom_adapter.height(&root_id).unwrap();
328 tracing::info!(
329 "Processing {} dirty nodes and {} cached nodes from a height of {}",
330 self.dirty.len(),
331 self.results.len(),
332 root_height
333 );
334 }
335
336 let layout_metadata = LayoutMetadata { root_area };
337
338 let mut available_area = layout_node.inner_area;
339 if let Some(root_parent_id) = root_parent_id {
340 let root_parent = dom_adapter.get_node(&root_parent_id).unwrap();
341 available_area.move_with_offsets(&root_parent.offset_x, &root_parent.offset_y);
342 }
343
344 let mut measure_context = MeasureContext {
345 layout: self,
346 layout_metadata,
347 dom_adapter,
348 measurer,
349 };
350
351 let (root_revalidated, mut root_layout_node) = measure_context.measure_node(
352 root_id,
353 &root,
354 &layout_node.inner_area,
355 &available_area,
356 true,
357 false,
358 Phase::Final,
359 );
360
361 if root_revalidated {
363 root_layout_node.area.adjust_size(&root);
365
366 self.cache_node(root_id, root_layout_node);
367 }
368
369 self.dirty.clear();
370 self.root_node_candidate = RootNodeCandidate::None;
371 }
372
373 pub fn get(&self, node_id: &Key) -> Option<&LayoutNode> {
375 self.results.get(node_id)
376 }
377
378 pub fn get_mut(&mut self, node_id: &Key) -> Option<&mut LayoutNode> {
380 self.results.get_mut(node_id)
381 }
382
383 pub fn cache_node(&mut self, node_id: Key, layout_node: LayoutNode) {
385 self.results.insert(node_id, layout_node);
386 }
387}