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