bevy_aoui/layout/
grid.rs

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    //if normalize.cmplt(Vec2::ZERO).any() {
61        result.iter_mut().for_each(|x| *x -= normalize);
62    //}
63    (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}