Skip to main content

ansiq_layout/
lib.rs

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    // Dirty subtree replacement already rebuilds the changed branch with valid
135    // child caches. Here we only need to remeasure the changed node and its
136    // ancestors, while rect-only shifts can reuse cached heights.
137    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}