1use std::ops::Range;
2use bevy::math::*;
3use itertools::Itertools;
4
5use crate::{LayoutItem, LayoutControl};
6
7fn xy(v: Vec2) -> f32 {
8 v.x + v.y
9}
10
11pub(crate) fn grid(
12 margin: Vec2,
13 items: impl IntoIterator<Item = LayoutItem>,
14 columns: usize,
15 cell_size: Vec2,
16 row_dir: impl Fn(Vec2) -> Vec2,
17 column_dir: impl Fn(Vec2) -> Vec2,
18 alignment: f32,
19) -> (Vec<Vec2>, Vec2) {
20 let mut cursor = Vec2::ZERO;
21 let mut dimension = Vec2::ZERO;
22 let mut max_columns = 0;
23 let mut result = Vec::new();
24 let mut row_ranges: Vec<Range<usize>> = Vec::new();
25 let mut row_start = 0;
26 let half_size = cell_size - margin / 2.0;
27 let half_dir = row_dir(half_size / 2.0) + column_dir(half_size / 2.0);
28
29 let delta_cell = row_dir(cell_size);
30 let delta_row = column_dir(cell_size);
31 let mut row_cursor = cursor;
32 for (i, item) in items.into_iter().enumerate() {
33 if item.control != LayoutControl::LinebreakMarker {
34 result.push(row_cursor + half_dir + half_size * item.anchor.as_vec());
35 row_cursor += delta_cell;
36 }
37 if result.len() - row_start >= columns || item.control.is_linebreak() {
38 row_ranges.push(row_start..result.len());
39 max_columns = max_columns.max(result.len() - row_start);
40 dimension = dimension.max((row_cursor + delta_row).abs());
41 row_start = i + 1;
42 cursor += delta_row;
43 row_cursor = cursor;
44 }
45 if item.control == LayoutControl::LinebreakMarker {
46 result.push(Vec2::ZERO);
47 }
48 }
49 if row_start < result.len() {
50 row_ranges.push(row_start..result.len());
51 max_columns = max_columns.max(result.len() - row_start);
52 dimension = dimension.max((row_cursor + delta_row).abs());
53 }
54 for row in row_ranges {
55 let roll = (max_columns - row.len()) as f32 / max_columns as f32;
56 let roll = row_dir(dimension) * roll * alignment;
57 result[row].iter_mut().for_each(|x| *x += roll);
58 }
59 let normalize = (row_dir(dimension) + column_dir(dimension)).min(Vec2::ZERO);
60 result.iter_mut().for_each(|x| *x -= normalize);
62 (result, dimension)
64}
65
66
67pub(crate) fn table(
68 margin: Vec2,
69 items: impl IntoIterator<Item = LayoutItem>,
70 columns: Vec<(Vec2, Vec2)>,
71 row_dir: impl Fn(Vec2) -> Vec2,
72 column_dir: impl Fn(Vec2) -> Vec2,
73) -> (Vec<Vec2>, Vec2) {
74
75 let rabs = |x| row_dir(x).abs();
76 let cabs = |x| column_dir(x).abs();
77
78 let mut cursor = Vec2::ZERO;
79 let mut result = Vec::new();
80
81 let as_cell_size = |x| rabs(x) + cabs(Vec2::ONE);
82 let mut line_height = Vec2::ZERO;
83 let line_margin = column_dir(margin);
84 let mut col = 0;
85 let max = columns.first().map(|x| x.0 + x.1).unwrap_or(Vec2::ZERO).max(
86 columns.last().map(|x| x.0 + x.1).unwrap_or(Vec2::ZERO)
87 );
88 let unit_row = rabs(Vec2::ONE);
89 for item in items {
90 line_height = line_height.max(column_dir(item.dimension).abs());
91 let (offset, dim) = columns[col];
92 let dim = as_cell_size(dim);
93 if item.control != LayoutControl::LinebreakMarker {
94 result.push(offset + dim / 2.0 + dim * item.anchor.as_vec());
95 col += 1;
96 }
97 if col >= columns.len() || item.control.is_linebreak() {
98 let len = result.len();
99 let height = column_dir(line_height);
100 cursor += height.min(Vec2::ZERO);
101 for item in &mut result[(len - col)..] {
102 *item = *item * (height.abs() + unit_row) + cursor;
103 }
104 cursor += height.max(Vec2::ZERO);
105 cursor += line_margin;
106 col = 0;
107 line_height = Vec2::ZERO;
108 }
109 if item.control == LayoutControl::LinebreakMarker {
110 result.push(Vec2::ZERO);
111 }
112 }
113 if col > 0 {
114 let len = result.len();
115 let height = column_dir(line_height);
116 cursor += height.min(Vec2::ZERO);
117 for item in &mut result[(len - col)..] {
118 *item = *item * (height.abs() + unit_row) + cursor;
119 }
120 cursor += height.max(Vec2::ZERO);
121 } else if cursor != Vec2::ZERO {
122 cursor -= line_margin;
123 }
124
125 let normalize = cursor.min(Vec2::ZERO);
126 result.iter_mut().for_each(|x| *x -= normalize);
127 (result, max + cursor.abs())
128}
129
130pub(crate) fn porportional_table(
131 dimension: Vec2,
132 margin: Vec2,
133 items: impl IntoIterator<Item = LayoutItem>,
134 columns: impl IntoIterator<Item = f32> + ExactSizeIterator,
135 row_dir: impl Fn(Vec2) -> Vec2,
136 column_dir: impl Fn(Vec2) -> Vec2,
137) -> (Vec<Vec2>, Vec2) {
138
139 let len = columns.len();
140 if len == 0 {
141 assert_ne!(len, 0, "Columns should not be 0.");
142 }
143 let row = xy(row_dir(dimension).abs());
144 let margin_per_item = xy(margin) * (len - 1) as f32 / len as f32;
145
146 let mut last = 0.0;
147
148 let columns = columns.into_iter().chain(std::iter::once(1.0)).map(|x| {
149 let result = (x - last) * row - margin_per_item;
150 last = x;
151 result
152 });
153
154 fixed_table(dimension, margin, items, columns, row_dir, column_dir, false)
155}
156
157pub(crate) fn fixed_table(
158 dimension: Vec2,
159 margin: Vec2,
160 items: impl IntoIterator<Item = LayoutItem>,
161 columns: impl IntoIterator<Item = f32>,
162 row_dir: impl Fn(Vec2) -> Vec2,
163 column_dir: impl Fn(Vec2) -> Vec2,
164 stretch: bool,
165) -> (Vec<Vec2>, Vec2) {
166 let len = row_dir(dimension);
167 let columns: Vec<Vec2> = columns.into_iter().map(|x| row_dir(x * Vec2::ONE)).collect_vec();
168 let row_margin = match stretch {
169 false => row_dir(margin),
170 true => match columns.len() {
171 0|1 => Vec2::ZERO,
172 count => (len - columns.iter().sum::<Vec2>()) / (count - 1) as f32,
173 },
174 };
175
176 let mut result = Vec::new();
177 if len.cmplt(Vec2::ZERO).any() {
178 let mut cursor = len.abs();
179 for item in columns {
180 result.push((cursor + item, item.abs()));
181 cursor += item + row_margin;
182 }
183 } else {
184 let mut cursor = Vec2::ZERO;
185 for item in columns {
186 result.push((cursor, item.abs()));
187 cursor += item + row_margin;
188 }
189 }
190 table(margin, items, result, row_dir, column_dir)
191}
192
193pub fn flex_table(
194 dimension: Vec2,
195 margin: Vec2,
196 items: impl IntoIterator<Item = LayoutItem>,
197 columns: usize,
198 row_dir: impl Fn(Vec2) -> Vec2,
199 column_dir: impl Fn(Vec2) -> Vec2,
200 stretch: bool,
201) -> (Vec<Vec2>, Vec2) {
202 assert_ne!(columns, 0, "Columns should not be 0.");
203 let mut index = 0;
204 let mut cols: Vec<f32> = Vec::new();
205 let items = items.into_iter().map(|item| {
206 let len = xy(row_dir(item.dimension).abs());
207 match cols.get_mut(index) {
208 Some(x) => *x = (*x).max(len),
209 None => cols.push(len),
210 }
211 index += 1;
212 if index >= columns || item.control.is_linebreak() {
213 index = 0;
214 }
215 item
216 }).collect_vec();
217
218 let row_len = row_dir(dimension);
219 let row_one = row_dir(Vec2::ONE).abs();
220
221 let col_margin = if stretch {
222 (xy(row_len).abs() - cols.iter().sum::<f32>()).max(0.0) / (cols.len() - 1) as f32 * row_one
223 } else {
224 row_dir(margin).abs()
225 };
226 let total = if stretch {
227 row_len.abs()
228 } else {
229 cols.iter().sum::<f32>() * row_one + row_dir(margin).abs() * (cols.len() - 1) as f32
230 };
231 let columns = if row_len.cmplt(Vec2::ZERO).any() {
232 let mut cursor = total;
233 cols.into_iter()
234 .map(|dim|{
235 let dim = (dim * row_one).abs();
236 let result = (cursor - dim, dim);
237 cursor -= dim + col_margin;
238 result
239 })
240 .collect_vec()
241 } else {
242 let mut cursor = Vec2::ZERO;
243 cols.into_iter()
244 .map(|dim|{
245 let dim = (dim * row_one).abs();
246 let result = (cursor, dim);
247 cursor += dim + col_margin;
248 result
249 })
250 .collect_vec()
251 };
252 table(margin, items, columns, row_dir, column_dir)
253}