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 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}