1use ansiq_core::{ChildLayoutKind, Direction, Element, Length, Node, Rect};
2
3#[derive(Debug, Default, Clone, PartialEq, Eq)]
4pub struct RelayoutStats {
5 pub remeasured_nodes: usize,
6 pub repositioned_nodes: usize,
7 pub invalidated_regions: Vec<Rect>,
8}
9
10pub fn layout_tree<Message>(element: Element<Message>, bounds: Rect) -> Node<Message> {
11 let mut next_id = 0;
12 layout_tree_with_ids(element, bounds, &mut next_id)
13}
14
15pub fn layout_tree_with_ids<Message>(
16 element: Element<Message>,
17 bounds: Rect,
18 next_id: &mut usize,
19) -> Node<Message> {
20 layout_node(element, bounds, next_id)
21}
22
23pub fn measure_height<Message>(element: &Element<Message>, width: u16) -> u16 {
24 measured_height(element, width).max(1)
25}
26
27pub fn relayout_tree<Message>(node: &mut Node<Message>, bounds: Rect) {
28 relayout_node(node, bounds);
29}
30
31pub fn relayout_tree_along_paths<Message>(
32 node: &mut Node<Message>,
33 bounds: Rect,
34 dirty_paths: &[Vec<usize>],
35) -> RelayoutStats {
36 let mut stats = RelayoutStats::default();
37 let normalized_dirty_paths = normalize_dirty_paths(dirty_paths);
38 relayout_node_along_paths(
39 node,
40 bounds,
41 &mut Vec::new(),
42 &normalized_dirty_paths,
43 &mut stats,
44 );
45 stats
46}
47
48pub fn measure_node_height<Message>(node: &Node<Message>, width: u16) -> u16 {
49 measured_node_height_cached(node, width).max(1)
50}
51
52fn layout_node<Message>(
53 element: Element<Message>,
54 bounds: Rect,
55 next_id: &mut usize,
56) -> Node<Message> {
57 let id = *next_id;
58 *next_id += 1;
59
60 let child_rects = child_rects(&element, bounds);
61 let Element {
62 kind,
63 layout,
64 style,
65 focusable,
66 continuity_key,
67 children,
68 } = element;
69 let children = children
70 .into_iter()
71 .zip(child_rects)
72 .map(|(child, rect)| layout_node(child, rect, next_id))
73 .collect();
74
75 let mut node = Node {
76 id,
77 rect: bounds,
78 measured_height: 0,
79 element: Element {
80 kind,
81 layout,
82 style,
83 focusable,
84 continuity_key,
85 children: Vec::new(),
86 },
87 children,
88 };
89 node.measured_height = remeasure_node_height(&node, bounds.width).max(1);
90 node
91}
92
93fn relayout_node<Message>(node: &mut Node<Message>, bounds: Rect) {
94 let rect_changed = node.rect != bounds;
95 node.rect = bounds;
96 node.measured_height = remeasure_node_height(node, bounds.width).max(1);
97
98 let child_rects = child_rects_for_node(node, bounds);
99 for (child, rect) in node.children.iter_mut().zip(child_rects) {
100 if rect_changed || child.rect != rect {
101 relayout_node(child, rect);
102 }
103 }
104}
105
106fn relayout_node_along_paths<Message>(
107 node: &mut Node<Message>,
108 bounds: Rect,
109 path: &mut Vec<usize>,
110 dirty_paths: &[Vec<usize>],
111 stats: &mut RelayoutStats,
112) {
113 let old_rect = node.rect;
114 let rect_changed = old_rect != bounds;
115 let width_changed = old_rect.width != bounds.width;
116 let affects_node = path_affects_node(dirty_paths, path);
117 let invalidates_self = node.element.invalidates_self_on_layout_change();
118
119 if rect_changed {
120 node.rect = bounds;
121 stats.repositioned_nodes += 1;
122 }
123
124 if path_is_dirty_target(dirty_paths, path) {
125 push_invalidated_region(&mut stats.invalidated_regions, old_rect);
126 push_invalidated_region(&mut stats.invalidated_regions, bounds);
127 } else if rect_changed && invalidates_self {
128 push_invalidated_region(&mut stats.invalidated_regions, old_rect);
129 push_invalidated_region(&mut stats.invalidated_regions, bounds);
130 } else if width_changed && invalidates_self {
131 push_invalidated_region(&mut stats.invalidated_regions, bounds);
132 }
133
134 if affects_node || width_changed {
138 node.measured_height = remeasure_node_height(node, bounds.width).max(1);
139 stats.remeasured_nodes += 1;
140 }
141
142 if !(rect_changed || affects_node || width_changed) {
143 return;
144 }
145
146 let child_rects = child_rects_for_node(node, bounds);
147 for (index, (child, rect)) in node.children.iter_mut().zip(child_rects).enumerate() {
148 path.push(index);
149 if child.rect != rect || path_affects_node(dirty_paths, path) {
150 relayout_node_along_paths(child, rect, path, dirty_paths, stats);
151 }
152 path.pop();
153 }
154
155 if affects_node || width_changed {
156 node.measured_height = remeasure_node_height(node, bounds.width).max(1);
157 }
158}
159
160fn child_rects<Message>(element: &Element<Message>, bounds: Rect) -> Vec<Rect> {
161 let spec = element.child_layout_spec(bounds);
162 match spec.kind {
163 ChildLayoutKind::Fill => fill_children(element.children.len(), spec.bounds),
164 ChildLayoutKind::Shell => {
165 shell_child_rects_with_heights(spec.bounds, element.children.len(), |index, width| {
166 measured_height(
167 &element.children[index],
168 child_width(&element.children[index], width),
169 )
170 })
171 }
172 ChildLayoutKind::Stack { direction, gap } => stack_child_rects(
173 element.children.len(),
174 spec.bounds,
175 direction,
176 gap,
177 |index| main_length(&element.children[index], direction),
178 |index, child_bounds, child_direction| {
179 auto_main_length(&element.children[index], child_bounds, child_direction)
180 },
181 |index, child_bounds, child_direction| {
182 cross_size_for(&element.children[index], child_bounds, child_direction)
183 },
184 ),
185 }
186}
187
188fn child_rects_for_node<Message>(node: &Node<Message>, bounds: Rect) -> Vec<Rect> {
189 let spec = node.child_layout_spec(bounds);
190 match spec.kind {
191 ChildLayoutKind::Fill => fill_children(node.children.len(), spec.bounds),
192 ChildLayoutKind::Shell => {
193 shell_child_rects_with_heights(spec.bounds, node.children.len(), |index, width| {
194 measured_node_height_cached(
195 &node.children[index],
196 child_node_width(&node.children[index], width),
197 )
198 })
199 }
200 ChildLayoutKind::Stack { direction, gap } => stack_child_rects(
201 node.children.len(),
202 spec.bounds,
203 direction,
204 gap,
205 |index| main_length_for_layout(node.children[index].element.layout, direction),
206 |index, child_bounds, child_direction| {
207 auto_main_length_node(&node.children[index], child_bounds, child_direction)
208 },
209 |index, child_bounds, child_direction| {
210 cross_size_for_layout(
211 node.children[index].element.layout,
212 child_bounds,
213 child_direction,
214 )
215 },
216 ),
217 }
218}
219
220fn fill_children(count: usize, bounds: Rect) -> Vec<Rect> {
221 (0..count).map(|_| bounds).collect()
222}
223
224fn shell_child_rects_with_heights(
225 bounds: Rect,
226 child_count: usize,
227 mut child_height_at: impl FnMut(usize, u16) -> u16,
228) -> Vec<Rect> {
229 debug_assert!(
230 child_count <= 3,
231 "Shell takes at most 3 children: header / body / footer"
232 );
233
234 match child_count {
235 0 => Vec::new(),
236 1 => vec![bounds],
237 2 => {
238 let header_height = child_height_at(0, bounds.width).min(bounds.height);
239 vec![
240 Rect::new(bounds.x, bounds.y, bounds.width, header_height),
241 Rect::new(
242 bounds.x,
243 bounds.y.saturating_add(header_height),
244 bounds.width,
245 bounds.height.saturating_sub(header_height),
246 ),
247 ]
248 }
249 _ => {
250 let header_height = child_height_at(0, bounds.width).min(bounds.height);
251 let footer_height =
252 child_height_at(2, bounds.width).min(bounds.height.saturating_sub(header_height));
253 let body_y = bounds.y.saturating_add(header_height);
254 let body_height = bounds
255 .height
256 .saturating_sub(header_height.saturating_add(footer_height));
257 let footer_y = bounds
258 .y
259 .saturating_add(bounds.height.saturating_sub(footer_height));
260
261 vec![
262 Rect::new(bounds.x, bounds.y, bounds.width, header_height),
263 Rect::new(bounds.x, body_y, bounds.width, body_height),
264 Rect::new(bounds.x, footer_y, bounds.width, footer_height),
265 ]
266 }
267 }
268}
269
270fn stack_child_rects(
271 len: usize,
272 bounds: Rect,
273 direction: Direction,
274 gap: u16,
275 mut main_length_at: impl FnMut(usize) -> Length,
276 mut auto_main_length_at: impl FnMut(usize, Rect, Direction) -> u16,
277 mut cross_size_at: impl FnMut(usize, Rect, Direction) -> u16,
278) -> Vec<Rect> {
279 if len == 0 {
280 return Vec::new();
281 }
282
283 let gap_total = gap.saturating_mul(len.saturating_sub(1) as u16);
284 let main_available = main_size(bounds, direction).saturating_sub(gap_total);
285
286 let mut reserved = 0u16;
287 let mut fill_count = 0u16;
288
289 for index in 0..len {
290 match main_length_at(index) {
291 Length::Fixed(size) => reserved = reserved.saturating_add(size),
292 Length::Auto => {
293 reserved = reserved.saturating_add(auto_main_length_at(index, bounds, direction))
294 }
295 Length::Fill => fill_count = fill_count.saturating_add(1),
296 }
297 }
298
299 let fill_available = main_available.saturating_sub(reserved);
300 let fill_base = if fill_count == 0 {
301 0
302 } else {
303 fill_available / fill_count
304 };
305 let fill_remainder = if fill_count == 0 {
306 0
307 } else {
308 fill_available % fill_count
309 };
310
311 let mut cursor_x = bounds.x;
312 let mut cursor_y = bounds.y;
313 let mut remaining = main_size(bounds, direction);
314 let mut assigned_fill = 0u16;
315 let mut rects = Vec::with_capacity(len);
316
317 for index in 0..len {
318 let wants_gap = !rects.is_empty();
319 if wants_gap {
320 let gap_size = gap.min(remaining);
321 advance_cursor(&mut cursor_x, &mut cursor_y, direction, gap_size);
322 remaining = remaining.saturating_sub(gap_size);
323 }
324
325 let planned_main = match main_length_at(index) {
326 Length::Fixed(size) => size,
327 Length::Auto => auto_main_length_at(index, bounds, direction),
328 Length::Fill => {
329 let extra = u16::from(assigned_fill < fill_remainder);
330 assigned_fill = assigned_fill.saturating_add(1);
331 fill_base.saturating_add(extra)
332 }
333 };
334 let actual_main = planned_main.min(remaining);
335 let cross = cross_size_at(index, bounds, direction);
336
337 rects.push(match direction {
338 Direction::Column => Rect::new(cursor_x, cursor_y, cross, actual_main),
339 Direction::Row => Rect::new(cursor_x, cursor_y, actual_main, cross),
340 });
341
342 advance_cursor(&mut cursor_x, &mut cursor_y, direction, actual_main);
343 remaining = remaining.saturating_sub(actual_main);
344 }
345
346 rects
347}
348
349fn main_length<Message>(element: &Element<Message>, direction: Direction) -> Length {
350 main_length_for_layout(element.layout, direction)
351}
352
353fn main_length_for_layout(layout: ansiq_core::Layout, direction: Direction) -> Length {
354 match direction {
355 Direction::Column => layout.height,
356 Direction::Row => layout.width,
357 }
358}
359
360fn cross_length_for_layout(layout: ansiq_core::Layout, direction: Direction) -> Length {
361 match direction {
362 Direction::Column => layout.width,
363 Direction::Row => layout.height,
364 }
365}
366
367fn main_size(bounds: Rect, direction: Direction) -> u16 {
368 match direction {
369 Direction::Column => bounds.height,
370 Direction::Row => bounds.width,
371 }
372}
373
374fn cross_size(bounds: Rect, direction: Direction) -> u16 {
375 match direction {
376 Direction::Column => bounds.width,
377 Direction::Row => bounds.height,
378 }
379}
380
381fn cross_size_for<Message>(element: &Element<Message>, bounds: Rect, direction: Direction) -> u16 {
382 cross_size_for_layout(element.layout, bounds, direction)
383}
384
385fn cross_size_for_layout(layout: ansiq_core::Layout, bounds: Rect, direction: Direction) -> u16 {
386 match cross_length_for_layout(layout, direction) {
387 Length::Fixed(size) => size.min(cross_size(bounds, direction)),
388 Length::Auto | Length::Fill => cross_size(bounds, direction),
389 }
390}
391
392fn advance_cursor(cursor_x: &mut u16, cursor_y: &mut u16, direction: Direction, amount: u16) {
393 match direction {
394 Direction::Column => *cursor_y = cursor_y.saturating_add(amount),
395 Direction::Row => *cursor_x = cursor_x.saturating_add(amount),
396 }
397}
398
399fn auto_main_length<Message>(
400 element: &Element<Message>,
401 bounds: Rect,
402 direction: Direction,
403) -> u16 {
404 match direction {
405 Direction::Column => measured_height(element, cross_size(bounds, direction)),
406 Direction::Row => measured_width(element).min(main_size(bounds, direction)),
407 }
408}
409
410fn auto_main_length_node<Message>(node: &Node<Message>, bounds: Rect, direction: Direction) -> u16 {
411 match direction {
412 Direction::Column => measured_node_height_cached(node, cross_size(bounds, direction)),
413 Direction::Row => measured_node_width(node).min(main_size(bounds, direction)),
414 }
415}
416
417fn measured_height<Message>(element: &Element<Message>, width: u16) -> u16 {
418 match element.layout.height {
419 Length::Fixed(height) => height,
420 Length::Auto | Length::Fill => intrinsic_measured_height(element, width),
421 }
422}
423
424fn measured_node_height_cached<Message>(node: &Node<Message>, width: u16) -> u16 {
425 if width == node.rect.width && node.measured_height > 0 {
426 return node.measured_height;
427 }
428
429 remeasure_node_height(node, width)
430}
431
432fn remeasure_node_height<Message>(node: &Node<Message>, width: u16) -> u16 {
433 match node.element.layout.height {
434 Length::Fixed(height) => height,
435 Length::Auto | Length::Fill => intrinsic_node_height(node, width),
436 }
437}
438
439fn intrinsic_measured_height<Message>(element: &Element<Message>, width: u16) -> u16 {
440 let child_width_base = element
441 .child_layout_spec(Rect::new(0, 0, width, u16::MAX))
442 .bounds
443 .width;
444 let child_heights = element
445 .children
446 .iter()
447 .map(|child| measured_height(child, child_width(child, child_width_base)))
448 .collect::<Vec<_>>();
449 element.intrinsic_height(width, &child_heights)
450}
451
452fn measured_width<Message>(element: &Element<Message>) -> u16 {
453 match element.layout.width {
454 Length::Fixed(width) => width,
455 Length::Auto | Length::Fill => intrinsic_measured_width(element),
456 }
457}
458
459fn measured_node_width<Message>(node: &Node<Message>) -> u16 {
460 match node.element.layout.width {
461 Length::Fixed(width) => width,
462 Length::Auto | Length::Fill => intrinsic_node_width(node),
463 }
464}
465
466fn intrinsic_measured_width<Message>(element: &Element<Message>) -> u16 {
467 let child_widths = element
468 .children
469 .iter()
470 .map(measured_width)
471 .collect::<Vec<_>>();
472 element.intrinsic_width(&child_widths)
473}
474
475fn intrinsic_node_width<Message>(node: &Node<Message>) -> u16 {
476 let child_widths = node
477 .children
478 .iter()
479 .map(measured_node_width)
480 .collect::<Vec<_>>();
481 node.element.intrinsic_width(&child_widths)
482}
483
484fn intrinsic_node_height<Message>(node: &Node<Message>, width: u16) -> u16 {
485 let child_width_base = node
486 .child_layout_spec(Rect::new(0, 0, width, u16::MAX))
487 .bounds
488 .width;
489 let child_heights = node
490 .children
491 .iter()
492 .map(|child| measured_node_height_cached(child, child_node_width(child, child_width_base)))
493 .collect::<Vec<_>>();
494 node.element.intrinsic_height(width, &child_heights)
495}
496
497fn child_width<Message>(element: &Element<Message>, available_width: u16) -> u16 {
498 match element.layout.width {
499 Length::Fixed(width) => width.min(available_width),
500 Length::Auto | Length::Fill => available_width,
501 }
502}
503
504fn child_node_width<Message>(node: &Node<Message>, available_width: u16) -> u16 {
505 match node.element.layout.width {
506 Length::Fixed(width) => width.min(available_width),
507 Length::Auto | Length::Fill => available_width,
508 }
509}
510
511fn path_affects_node(dirty_paths: &[Vec<usize>], path: &[usize]) -> bool {
512 dirty_paths
513 .iter()
514 .any(|dirty_path| dirty_path.starts_with(path))
515}
516
517fn path_is_dirty_target(dirty_paths: &[Vec<usize>], path: &[usize]) -> bool {
518 dirty_paths.iter().any(|dirty_path| dirty_path == path)
519}
520
521fn normalize_dirty_paths(dirty_paths: &[Vec<usize>]) -> Vec<Vec<usize>> {
522 let mut normalized = dirty_paths.to_vec();
523 normalized.sort();
524 normalized.dedup();
525
526 let mut compressed = Vec::with_capacity(normalized.len());
527 for path in normalized {
528 if compressed
529 .iter()
530 .any(|existing: &Vec<usize>| path.starts_with(existing))
531 {
532 continue;
533 }
534 compressed.push(path);
535 }
536
537 compressed
538}
539
540fn push_invalidated_region(regions: &mut Vec<Rect>, rect: Rect) {
541 if rect.is_empty() {
542 return;
543 }
544
545 if regions.iter().any(|existing| existing.contains(rect)) {
546 return;
547 }
548
549 let mut merged = rect;
550 let mut index = 0;
551 while index < regions.len() {
552 let existing = regions[index];
553 if merged.contains(existing) || merged.can_merge_rect(existing) {
554 merged = merged.union(existing);
555 regions.remove(index);
556 continue;
557 }
558 index += 1;
559 }
560
561 regions.push(merged);
562}