fltk_float/
grid.rs

1use std::borrow::Borrow;
2use std::rc::Rc;
3
4use fltk::group::Group;
5use fltk::prelude::*;
6
7use crate::WrapperFactory;
8
9use super::{LayoutElement, Padding, Size};
10
11mod builder;
12
13pub use builder::{CellBuilder, GridBuilder, StripeBuilder};
14
15#[derive(Debug, Clone, Copy)]
16pub enum CellAlign {
17    Start,
18    Center,
19    End,
20    Stretch,
21}
22
23pub struct Grid<G: GroupExt + Clone = Group> {
24    props: GridProperties<G>,
25    stretch_rows: Vec<usize>,
26    stretch_cols: Vec<usize>,
27    min_size: Size,
28}
29
30struct GridProperties<G: GroupExt + Clone = Group> {
31    group: G,
32    padding: Padding,
33    row_spacing: i32,
34    col_spacing: i32,
35    cells: Vec<Cell>,
36    spans: Vec<Cell>,
37    groups: Vec<StripeProperties>,
38    rows: Vec<Stripe>,
39    cols: Vec<Stripe>,
40}
41
42struct Cell {
43    element: Rc<dyn LayoutElement>,
44    min_size: Size,
45    props: CellProperties,
46}
47
48struct CellProperties {
49    row: usize,
50    col: usize,
51    row_span: usize,
52    col_span: usize,
53    padding: Padding,
54    horz_align: CellAlign,
55    vert_align: CellAlign,
56}
57
58#[derive(Debug, Clone, Copy)]
59struct StripeProperties {
60    stretch: u8,
61    min_size: i32,
62}
63
64struct Stripe {
65    cells: Vec<StripeCell>,
66    group_idx: usize,
67}
68
69#[derive(Debug, Clone, Copy)]
70enum StripeCell {
71    Free,
72    Skipped,
73    Cell(usize),
74    Span,
75}
76
77impl StripeCell {
78    fn cell_idx(&self) -> Option<usize> {
79        if let Self::Cell(idx) = self {
80            Some(*idx)
81        } else {
82            None
83        }
84    }
85}
86
87impl<G: GroupExt + Clone> LayoutElement for Grid<G> {
88    fn min_size(&self) -> Size {
89        self.min_size
90    }
91
92    fn layout(&self, x: i32, y: i32, width: i32, height: i32) {
93        self.props.group.clone().resize(x, y, width, height);
94        self.layout_children()
95    }
96}
97
98impl Grid {
99    pub fn builder() -> GridBuilder<Group, WrapperFactory> {
100        GridBuilder::new(Group::default_fill())
101    }
102
103    pub fn builder_with_factory<F: Borrow<WrapperFactory>>(factory: F) -> GridBuilder<Group, F> {
104        GridBuilder::with_factory(Group::default_fill(), factory)
105    }
106}
107
108impl<G: GroupExt + Clone> Grid<G> {
109    pub fn group(&self) -> G {
110        self.props.group.clone()
111    }
112
113    pub fn layout_children(&self) {
114        let x = self.props.group.x() + self.props.padding.left;
115        let y = self.props.group.y() + self.props.padding.top;
116        let width = self.props.group.width() - (self.props.padding.left + self.props.padding.right);
117        let height =
118            self.props.group.height() - (self.props.padding.top + self.props.padding.bottom);
119
120        // TODO: Eliminate unnecessary allocation
121        let col_bounds = calc_stripe_bounds(
122            width,
123            &self.props.cols,
124            &self.props.groups,
125            &self.stretch_cols,
126            self.props.col_spacing,
127        );
128        let row_bounds = calc_stripe_bounds(
129            height,
130            &self.props.rows,
131            &self.props.groups,
132            &self.stretch_rows,
133            self.props.row_spacing,
134        );
135
136        for cell in self.props.cells.iter() {
137            let (cell_x, cell_width) = col_bounds[cell.props.col];
138            let (cell_y, cell_height) = row_bounds[cell.props.row];
139            let (widget_x, widget_width) = calc_widget_bounds(
140                x,
141                cell_x,
142                cell_width,
143                cell.min_size.width,
144                cell.props.padding.left,
145                cell.props.padding.right,
146                cell.props.horz_align,
147            );
148            let (widget_y, widget_height) = calc_widget_bounds(
149                y,
150                cell_y,
151                cell_height,
152                cell.min_size.height,
153                cell.props.padding.top,
154                cell.props.padding.bottom,
155                cell.props.vert_align,
156            );
157            cell.element
158                .layout(widget_x, widget_y, widget_width, widget_height);
159        }
160
161        for span in self.props.spans.iter() {
162            let left_col = span.props.col;
163            let right_col = left_col + span.props.col_span - 1;
164            let span_x = col_bounds[left_col].0;
165            let span_width = col_bounds[right_col].0 + col_bounds[right_col].1 - span_x;
166
167            let top_row = span.props.row;
168            let bottom_row = top_row + span.props.row_span - 1;
169            let span_y = row_bounds[top_row].0;
170            let span_height = row_bounds[bottom_row].0 + row_bounds[bottom_row].1 - span_y;
171
172            let (widget_x, widget_width) = calc_widget_bounds(
173                x,
174                span_x,
175                span_width,
176                span.min_size.width,
177                span.props.padding.left,
178                span.props.padding.right,
179                span.props.horz_align,
180            );
181            let (widget_y, widget_height) = calc_widget_bounds(
182                y,
183                span_y,
184                span_height,
185                span.min_size.height,
186                span.props.padding.top,
187                span.props.padding.bottom,
188                span.props.vert_align,
189            );
190            span.element
191                .layout(widget_x, widget_y, widget_width, widget_height);
192        }
193    }
194
195    fn new(props: GridProperties<G>) -> Self {
196        let stretch_rows = collect_stretch_stripes(&props.rows, &props.groups);
197        let stretch_cols = collect_stretch_stripes(&props.cols, &props.groups);
198
199        let mut grid = Self {
200            props,
201            stretch_rows,
202            stretch_cols,
203            min_size: Default::default(),
204        };
205
206        grid.cache_min_sizes();
207
208        sort_stretch_stripes(&grid.props.rows, &grid.props.groups, &mut grid.stretch_rows);
209        sort_stretch_stripes(&grid.props.cols, &grid.props.groups, &mut grid.stretch_cols);
210
211        grid
212    }
213
214    fn cache_min_sizes(&mut self) {
215        self.cache_cell_min_sizes();
216        self.cache_span_min_sizes();
217
218        self.min_size.width =
219            span_size(&self.props.cols, &self.props.groups, self.props.col_spacing)
220                + self.props.padding.left
221                + self.props.padding.right;
222        self.min_size.height =
223            span_size(&self.props.rows, &self.props.groups, self.props.row_spacing)
224                + self.props.padding.top
225                + self.props.padding.bottom;
226    }
227
228    fn cache_cell_min_sizes(&mut self) {
229        for cell in self.props.cells.iter_mut() {
230            cell.cache_min_size();
231        }
232        for col in self.props.cols.iter_mut() {
233            self.props.groups[col.group_idx].min_size = col
234                .cells
235                .iter()
236                .filter_map(StripeCell::cell_idx)
237                .map(|idx| self.props.cells[idx].min_size.width)
238                .fold(self.props.groups[col.group_idx].min_size, std::cmp::max);
239        }
240        for row in self.props.rows.iter_mut() {
241            self.props.groups[row.group_idx].min_size = row
242                .cells
243                .iter()
244                .filter_map(StripeCell::cell_idx)
245                .map(|idx| self.props.cells[idx].min_size.height)
246                .fold(self.props.groups[row.group_idx].min_size, std::cmp::max);
247        }
248    }
249
250    fn cache_span_min_sizes(&mut self) {
251        for span in self.props.spans.iter_mut() {
252            span.cache_min_size();
253
254            let top = span.props.row;
255            let bottom = top + span.props.row_span;
256            let left = span.props.col;
257            let right = left + span.props.col_span;
258
259            adjust_span_stripes(
260                span.min_size.width,
261                &self.props.cols[left..right],
262                &mut self.props.groups,
263                self.props.col_spacing,
264            );
265            adjust_span_stripes(
266                span.min_size.height,
267                &self.props.rows[top..bottom],
268                &mut self.props.groups,
269                self.props.row_spacing,
270            );
271        }
272    }
273}
274
275impl Cell {
276    fn cache_min_size(&mut self) {
277        self.min_size = self.element.min_size();
278        self.min_size.width += self.props.padding.left + self.props.padding.right;
279        self.min_size.height += self.props.padding.top + self.props.padding.bottom;
280    }
281}
282
283fn collect_stretch_stripes(stripes: &[Stripe], groups: &[StripeProperties]) -> Vec<usize> {
284    stripes
285        .iter()
286        .enumerate()
287        .filter_map(
288            |(idx, stripe)| {
289                if groups[stripe.group_idx].stretch > 0 {
290                    Some(idx)
291                } else {
292                    None
293                }
294            },
295        )
296        .collect()
297}
298
299fn sort_stretch_stripes(
300    stripes: &[Stripe],
301    groups: &[StripeProperties],
302    stretch_stripes: &mut [usize],
303) {
304    stretch_stripes.sort_by(|lidx, ridx| {
305        groups[stripes[*ridx].group_idx]
306            .min_size
307            .cmp(&groups[stripes[*lidx].group_idx].min_size)
308    });
309}
310
311fn span_size(stripes: &[Stripe], groups: &[StripeProperties], spacing: i32) -> i32 {
312    if stripes.len() == 0 {
313        return 0;
314    }
315
316    let mut size = stripes
317        .iter()
318        .map(|stripe| groups[stripe.group_idx].min_size)
319        .sum();
320    size += (stripes.len() as i32 - 1) * spacing;
321    size
322}
323
324fn adjust_span_stripes(
325    min_size: i32,
326    stripes: &[Stripe],
327    groups: &mut [StripeProperties],
328    spacing: i32,
329) {
330    let current_size = span_size(stripes, groups, spacing);
331    if current_size >= min_size {
332        return;
333    }
334
335    let mut stretch_stripes = collect_stretch_stripes(stripes, groups);
336    if stretch_stripes.len() > 0 {
337        sort_stretch_stripes(stripes, groups, &mut stretch_stripes);
338        let bounds = calc_stripe_bounds(min_size, stripes, groups, &stretch_stripes, spacing);
339        for idx in stretch_stripes {
340            groups[stripes[idx].group_idx].min_size = bounds[idx].1;
341        }
342    } else {
343        groups[stripes[0].group_idx].min_size += min_size - current_size;
344    }
345}
346
347fn calc_stripe_bounds(
348    total_size: i32,
349    stripes: &[Stripe],
350    groups: &[StripeProperties],
351    stretch_stripes: &[usize],
352    spacing: i32,
353) -> Vec<(i32, i32)> {
354    let mut bounds = Vec::with_capacity(stripes.len());
355
356    let mut stretch_budget = total_size - (stripes.len() - 1) as i32 * spacing;
357    let mut stretch_count: i32 = 0;
358    for stripe in stripes.iter() {
359        let group = &groups[stripe.group_idx];
360        if group.stretch == 0 {
361            stretch_budget -= group.min_size;
362        } else {
363            stretch_count += group.stretch as i32;
364        }
365        bounds.push((0, group.min_size));
366    }
367    stretch_budget = std::cmp::max(0, stretch_budget);
368
369    let mut stretch_unit = if stretch_count > 0 { stretch_budget / stretch_count } else { 0 };
370    for &stripe_idx in stretch_stripes.iter() {
371        let stripe = &stripes[stripe_idx];
372        let group = &groups[stripe.group_idx];
373
374        let factor = group.stretch as i32;
375        stretch_count -= factor;
376        let stripe_size = if stretch_count > 0 { stretch_unit * factor } else { stretch_budget };
377
378        if stripe_size < group.min_size {
379            stretch_budget -= group.min_size;
380            if stretch_count > 0 {
381                stretch_unit = stretch_budget / stretch_count;
382            }
383        } else {
384            stretch_budget -= stripe_size;
385            bounds[stripe_idx].1 = stripe_size;
386        }
387    }
388
389    let mut start = 0;
390    for stripe_bounds in bounds.iter_mut() {
391        stripe_bounds.0 = start;
392        start += stripe_bounds.1 + spacing;
393    }
394
395    bounds
396}
397
398fn calc_widget_bounds(
399    group_start: i32,
400    cell_start: i32,
401    cell_size: i32,
402    min_size: i32,
403    pad_start: i32,
404    pad_end: i32,
405    align: CellAlign,
406) -> (i32, i32) {
407    let widget_size = match align {
408        CellAlign::Stretch => cell_size,
409        _ => min_size,
410    };
411
412    let widget_size = widget_size - pad_start - pad_end;
413    let cell_size = cell_size - pad_start - pad_end;
414
415    let widget_start = match align {
416        CellAlign::Start => 0,
417        CellAlign::Center => (cell_size - widget_size) / 2,
418        CellAlign::End => cell_size - widget_size,
419        CellAlign::Stretch => 0,
420    };
421    let widget_start = group_start + pad_start + cell_start + widget_start;
422
423    (widget_start, widget_size)
424}