Skip to main content

sequoia_layout/
wrap.rs

1use crate::container::{Align, Axis};
2use crate::dock::sanitize_gap;
3
4#[derive(Clone, Copy, Debug, PartialEq)]
5pub struct WrapStyle {
6    pub axis: Axis,
7
8    pub align: Align,
9
10    pub column_gap: f32,
11
12    pub row_gap: f32,
13}
14
15impl Default for WrapStyle {
16    fn default() -> Self {
17        Self {
18            axis: Axis::Horizontal,
19            align: Align::Start,
20            column_gap: 0.0,
21            row_gap: 0.0,
22        }
23    }
24}
25
26impl WrapStyle {
27    pub fn new(axis: Axis) -> Self {
28        Self {
29            axis,
30            ..Self::default()
31        }
32    }
33
34    pub fn main_gap(self) -> f32 {
35        match self.axis {
36            Axis::Horizontal => self.column_gap,
37            Axis::Vertical => self.row_gap,
38        }
39    }
40
41    pub fn cross_gap(self) -> f32 {
42        match self.axis {
43            Axis::Horizontal => self.row_gap,
44            Axis::Vertical => self.column_gap,
45        }
46    }
47
48    pub fn gap(mut self, gap: f32) -> Self {
49        self.column_gap = sanitize_gap(gap);
50        self.row_gap = sanitize_gap(gap);
51        self
52    }
53
54    pub fn column_gap(mut self, gap: f32) -> Self {
55        self.column_gap = sanitize_gap(gap);
56        self
57    }
58
59    pub fn row_gap(mut self, gap: f32) -> Self {
60        self.row_gap = sanitize_gap(gap);
61        self
62    }
63
64    pub fn align(mut self, align: Align) -> Self {
65        self.align = align;
66        self
67    }
68
69    pub fn axis(mut self, axis: Axis) -> Self {
70        self.axis = axis;
71        self
72    }
73}
74
75pub fn wrap_lines(main_sizes: &[f32], avail_main: f32, gap: f32) -> Vec<(usize, usize)> {
76    let mut lines = Vec::new();
77    if main_sizes.is_empty() {
78        return lines;
79    }
80    let gap = sanitize_gap(gap);
81    let avail = if avail_main.is_finite() {
82        avail_main.max(0.0)
83    } else {
84        f32::INFINITY
85    };
86
87    let mut start = 0usize;
88    let mut used = 0.0_f32;
89    for (i, &raw) in main_sizes.iter().enumerate() {
90        let size = if raw.is_finite() { raw.max(0.0) } else { 0.0 };
91        if i == start {
92            used = size;
93            continue;
94        }
95        let next = used + gap + size;
96        if next > avail {
97            lines.push((start, i));
98            start = i;
99            used = size;
100        } else {
101            used = next;
102        }
103    }
104    lines.push((start, main_sizes.len()));
105    lines
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn empty_input_has_no_lines() {
114        assert!(wrap_lines(&[], 100.0, 0.0).is_empty());
115    }
116
117    #[test]
118    fn children_that_fit_stay_on_one_line() {
119        let lines = wrap_lines(&[30.0, 30.0, 30.0], 100.0, 0.0);
120        assert_eq!(lines, vec![(0, 3)]);
121    }
122
123    #[test]
124    fn overflow_wraps_to_the_next_line() {
125        let lines = wrap_lines(&[40.0, 40.0, 40.0], 100.0, 0.0);
126        assert_eq!(lines, vec![(0, 2), (2, 3)]);
127    }
128
129    #[test]
130    fn gap_counts_toward_the_line_width() {
131        assert_eq!(
132            wrap_lines(&[40.0, 40.0, 40.0], 100.0, 10.0),
133            vec![(0, 2), (2, 3)]
134        );
135
136        assert_eq!(
137            wrap_lines(&[40.0, 40.0, 40.0], 80.0, 10.0),
138            vec![(0, 1), (1, 2), (2, 3)]
139        );
140    }
141
142    #[test]
143    fn wider_than_the_line_gets_its_own_line() {
144        let lines = wrap_lines(&[200.0, 30.0], 100.0, 0.0);
145        assert_eq!(lines, vec![(0, 1), (1, 2)]);
146    }
147
148    #[test]
149    fn unbounded_axis_keeps_everything_on_one_line() {
150        let lines = wrap_lines(&[500.0, 500.0, 500.0], f32::INFINITY, 8.0);
151        assert_eq!(lines, vec![(0, 3)]);
152    }
153
154    #[test]
155    fn gaps_sanitize() {
156        let s = WrapStyle::new(Axis::Horizontal).gap(f32::NAN);
157        assert_eq!(s.column_gap, 0.0);
158        assert_eq!(s.row_gap, 0.0);
159        let s = s.row_gap(-5.0).column_gap(4.0);
160        assert_eq!(s.row_gap, 0.0);
161        assert_eq!(s.column_gap, 4.0);
162    }
163
164    #[test]
165    fn main_and_cross_gaps_follow_the_axis() {
166        let s = WrapStyle::new(Axis::Horizontal)
167            .column_gap(6.0)
168            .row_gap(10.0);
169        assert_eq!(s.main_gap(), 6.0);
170        assert_eq!(s.cross_gap(), 10.0);
171        let s = s.axis(Axis::Vertical);
172        assert_eq!(s.main_gap(), 10.0);
173        assert_eq!(s.cross_gap(), 6.0);
174    }
175}