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}