zng_wgt_grid/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! Grid widgets, properties and nodes.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12use std::{fmt, mem};
13
14use zng_layout::unit::{GridSpacing, PxGridSpacing};
15use zng_wgt::prelude::*;
16use zng_wgt_access::{AccessRole, access_role};
17use zng_wgt_size_offset::*;
18
19/// Grid layout with cells of variable sizes.
20#[widget($crate::Grid)]
21pub struct Grid(WidgetBase);
22impl Grid {
23    fn widget_intrinsic(&mut self) {
24        self.widget_builder().push_build_action(|w| {
25            let child = node(
26                w.capture_ui_node_or_nil(property_id!(Self::cells)),
27                w.capture_ui_node_or_nil(property_id!(Self::columns)),
28                w.capture_ui_node_or_nil(property_id!(Self::rows)),
29                w.capture_var_or_else(property_id!(Self::auto_grow_fn), WidgetFn::nil),
30                w.capture_var_or_else(property_id!(Self::auto_grow_mode), AutoGrowMode::rows),
31                w.capture_var_or_default(property_id!(Self::spacing)),
32            );
33            w.set_child(child);
34        });
35
36        widget_set! {
37            self;
38
39            access_role = AccessRole::Grid;
40        }
41    }
42}
43
44/// Cell widget items.
45///
46/// Cells can select their own column, row, column-span and row-span using the properties in the [`Cell!`] widget.
47/// Note that you don't need to use that widget, only the [`cell`] properties.
48///
49/// If the column or row index is set to [`usize::MAX`] the widget is positioned using the
50/// logical index *i*, the column *i % columns* and the row *i / columns*.
51///
52/// [`Cell!`]: struct@Cell
53#[property(CHILD, widget_impl(Grid))]
54pub fn cells(wgt: &mut WidgetBuilding, cells: impl IntoUiNode) {
55    let _ = cells;
56    wgt.expect_property_capture();
57}
58
59/// Column definitions.
60///
61/// Columns are defined by widgets, the column widget width defines the width of cells assigned to the column,
62/// the [`Column!`] widget is recommended, but you can use any widget to define a column. The column widget is rendered
63/// as the background of the column region, behind cells and row backgrounds.
64///
65/// ### Layout Modes
66///
67/// The grid uses the [`WIDGET_SIZE`] value to select one of three layout modes for columns:
68///
69/// * *Default*, used for columns that do not set width or set it to [`Length::Default`].
70/// * *Exact*, used for columns that set the width to an unit that is exact or only depends on the grid context.
71/// * *Leftover*, used for columns that set width to a [`lft`] value.
72///
73/// The column (and row) measure follows these steps:
74///
75/// 1 - All *Exact* column widgets are measured, their final width defines the column width.
76/// 2 - All *Default* sized column widgets are measured twice to find its min and max widths.
77/// 3 - All cell widgets with span `1` in *Default* columns are measured to find the widest cell width. That defines the column width.
78/// 4 - All *Leftover* columns receive the proportional leftover grid width for each.
79///
80/// So given the columns `200 | 1.lft() | 1.lft()` and grid width of `1000` with spacing `5` the final widths are `200 | 395 | 395`,
81/// for `200 + 5 + 395 + 5 + 395 = 1000`.
82///
83/// #### Overflow Recovery
84///
85/// In case the columns width overflows and all rows are *Default* height and some columns are *Default* width these recovery steps are taken:
86///
87/// 1 - All cell widgets with span `1` in *Default* columns are measured to find the minimum width they can wrap down too.
88/// 2 - All *Default* columns are sized to the minimum width plus the extra space now available, proportionally divided.
89/// 3 - All cells widgets affected are measured again to define the row heights.
90///
91/// The proportion of each *Default* is the difference between the previous measured width with the new minimum, this is very similar to
92/// the CSS table layout, except the previous measured width is used instead of another measure pass to find the cells maximum width.
93///
94/// ### Notes
95///
96/// Note that the column widget is not the parent of the cells that match it.
97/// Properties like `padding` and `align` only affect the column visual, not the cells, similarly contextual properties like `text_color`
98/// don't affect the cells.
99///
100/// Note that the *Default* layout mode scales with the cell count, the other modes scale with the column count. This
101/// is fine for small grids (<100 cells) or for simple cell widgets, but for larger grids you should really consider using
102/// an *Exact* width or *Leftover* proportion, specially if the grid width is bounded.
103///
104/// [`Column!`]: struct@Column
105/// [`lft`]: zng_layout::unit::LengthUnits::lft
106/// [`WIDGET_SIZE`]: zng_wgt_size_offset::WIDGET_SIZE
107/// [`Length::Default`]: zng_layout::unit::Length::Default
108#[property(CHILD, widget_impl(Grid))]
109pub fn columns(wgt: &mut WidgetBuilding, columns: impl IntoUiNode) {
110    let _ = columns;
111    wgt.expect_property_capture();
112}
113
114/// Row definitions.
115///
116/// Rows are defined by widgets, the row widget height defines the height of cells assigned to the row, the [`Row!`] widget is recommended,
117/// but you can use any widget to define a row. The row widget is rendered as the background of the row region, behind cells and in front
118/// of column backgrounds.
119///
120/// ## Layout Modes
121///
122/// The grid uses the [`WIDGET_SIZE`] value to select one of three layout modes for rows:
123///
124/// * *Default*, used for rows that do not set height or set it to [`Length::Default`].
125/// * *Exact*, used for rows that set the height to an unit that is exact or only depends on the grid context.
126/// * *Leftover*, used for rows that set height to a [`lft`] value.
127///
128/// The row measure follows the same steps as [`columns`], the only difference is that there is no
129/// overflow recovery for row heights exceeding the available height.
130///
131/// ### Notes
132///
133/// Note that the row widget is not the parent of the cells that match it.
134/// Properties like `padding` and `align` only affect the row visual, not the cells, similarly contextual properties like `text_color`
135/// don't affect the cells.
136///
137/// Note that the *Default* layout mode scales with the cell count, the other modes scale with the row count. This has less impact
138/// for rows, but you should consider setting a fixed row height for larger grids. Also note that you can define the [`auto_grow_fn`]
139/// instead of manually adding rows. With fixed heights a data table of up to 1000 rows with simple text cells should have good performance.
140///
141/// For massive data tables consider a paginating layout with a separate grid instance per *page*, the page grids don't need to be actually
142/// presented as pages, you can use lazy loading and a simple stack layout to seamless virtualize data loading and presentation.
143///
144/// [`columns`]: fn@columns
145/// [`auto_grow_fn`]: fn@auto_grow_fn
146/// [`Row!`]: struct@Row
147/// [`lft`]: zng_layout::unit::LengthUnits::lft
148/// [`WIDGET_SIZE`]: zng_wgt_size_offset::WIDGET_SIZE
149/// [`Length::Default`]: zng_layout::unit::Length::Default
150#[property(CHILD, widget_impl(Grid))]
151pub fn rows(wgt: &mut WidgetBuilding, rows: impl IntoUiNode) {
152    let _ = rows;
153    wgt.expect_property_capture();
154}
155
156/// Widget function used when new rows or columns are needed to cover a cell placement.
157///
158/// The function is used according to the [`auto_grow_mode`]. Note that *imaginary* rows or columns are used if
159/// the function is [`WidgetFn::nil`].
160///
161/// [`auto_grow_mode`]: fn@auto_grow_mode
162/// [`WidgetFn::nil`]: zng_wgt::prelude::WidgetFn::nil
163#[property(CONTEXT, default(WidgetFn::nil()), widget_impl(Grid))]
164pub fn auto_grow_fn(wgt: &mut WidgetBuilding, auto_grow: impl IntoVar<WidgetFn<AutoGrowFnArgs>>) {
165    let _ = auto_grow;
166    wgt.expect_property_capture();
167}
168
169/// Defines the direction the grid auto-grows and the maximum inclusive index that can be covered by auto-generated columns or rows.
170/// If a cell is outside this index and is not covered by predefined columns or rows a new one is auto generated for it, but if the
171/// cell is also outside this max it is *collapsed*.
172///
173/// Is `AutoGrowMode::rows() by default.
174#[property(CONTEXT, default(AutoGrowMode::rows()), widget_impl(Grid))]
175pub fn auto_grow_mode(wgt: &mut WidgetBuilding, mode: impl IntoVar<AutoGrowMode>) {
176    let _ = mode;
177    wgt.expect_property_capture();
178}
179
180/// Space in-between cells.
181#[property(LAYOUT, default(GridSpacing::default()), widget_impl(Grid))]
182pub fn spacing(wgt: &mut WidgetBuilding, spacing: impl IntoVar<GridSpacing>) {
183    let _ = spacing;
184    wgt.expect_property_capture();
185}
186
187/// Grid node.
188///
189/// Can be used directly to layout widgets without declaring a grid widget info. This node is the child
190/// of the `Grid!` widget.
191pub fn node(
192    cells: impl IntoUiNode,
193    columns: impl IntoUiNode,
194    rows: impl IntoUiNode,
195    auto_grow_fn: impl IntoVar<WidgetFn<AutoGrowFnArgs>>,
196    auto_grow_mode: impl IntoVar<AutoGrowMode>,
197    spacing: impl IntoVar<GridSpacing>,
198) -> UiNode {
199    let auto_columns = ui_vec![];
200    let auto_rows = ui_vec![];
201    let children = ui_vec![
202        ChainList(ui_vec![columns.into_node().into_list(), auto_columns]),
203        ChainList(ui_vec![rows.into_node().into_list(), auto_rows]),
204        PanelList::new(cells),
205    ];
206    let spacing = spacing.into_var();
207    let auto_grow_fn = auto_grow_fn.into_var();
208    let auto_grow_mode = auto_grow_mode.into_var();
209
210    let mut grid = GridLayout::default();
211    let mut is_measured = false;
212    let mut last_layout = LayoutMetrics::new(1.fct(), PxSize::zero(), Px(0));
213
214    match_node(children, move |c, op| match op {
215        UiNodeOp::Init => {
216            WIDGET.sub_var(&auto_grow_fn).sub_var(&auto_grow_mode).sub_var_layout(&spacing);
217            c.init();
218            grid.update_entries(c.node(), auto_grow_mode.get(), &auto_grow_fn);
219        }
220        UiNodeOp::Deinit => {
221            c.deinit();
222            GridChildrenMut(c.node()).auto_columns().clear();
223            GridChildrenMut(c.node()).auto_rows().clear();
224            is_measured = false;
225        }
226        UiNodeOp::Update { updates } => {
227            let mut any = false;
228            c.update_list(updates, &mut any);
229
230            if auto_grow_fn.is_new() || auto_grow_mode.is_new() {
231                for mut auto in GridChildrenMut(c.node()).auto_columns().drain(..) {
232                    auto.deinit();
233                }
234                for mut auto in GridChildrenMut(c.node()).auto_rows().drain(..) {
235                    auto.deinit();
236                }
237                any = true;
238            }
239            if any {
240                grid.update_entries(c.node(), auto_grow_mode.get(), &auto_grow_fn);
241                WIDGET.layout();
242            }
243        }
244        UiNodeOp::Measure { wm, desired_size } => {
245            c.delegated();
246
247            let constraints = LAYOUT.constraints().inner();
248
249            *desired_size = if let Some(size) = constraints.fill_or_exact() {
250                size
251            } else {
252                is_measured = true;
253                let s = grid.grid_layout(wm, c.node(), &spacing).1;
254                constraints.clamp_size(s)
255            };
256        }
257        UiNodeOp::Layout { wl, final_size } => {
258            c.delegated();
259            is_measured = false;
260            last_layout = LAYOUT.metrics();
261
262            let (spacing, grid_size) = grid.grid_layout(&mut wl.to_measure(None), c.node(), &spacing);
263            let constraints = last_layout.constraints();
264
265            if grid.is_collapse() {
266                wl.collapse_descendants();
267                *final_size = constraints.fill_or_exact().unwrap_or_default();
268                return;
269            }
270
271            let mut children = GridChildrenMut(c.node());
272            let mut children = children.children().iter_mut();
273            let columns = children.next().unwrap();
274            let rows = children.next().unwrap();
275            let cells = children.next().unwrap();
276            let cells: &mut PanelList = cells.downcast_mut().unwrap();
277
278            let grid = &grid;
279
280            // layout columns
281            let _ = columns.layout_list(
282                wl,
283                |ci, col, wl| {
284                    let info = grid.columns[ci];
285                    LAYOUT.with_constraints(constraints.with_exact(info.width, grid_size.height), || col.layout(wl))
286                },
287                |_, _| PxSize::zero(),
288            );
289            // layout rows
290            let _ = rows.layout_list(
291                wl,
292                |ri, row, wl| {
293                    let info = grid.rows[ri];
294                    LAYOUT.with_constraints(constraints.with_exact(grid_size.width, info.height), || row.layout(wl))
295                },
296                |_, _| PxSize::zero(),
297            );
298            // layout and translate cells
299            let cells_offset = columns.children_len() + rows.children_len();
300
301            cells.layout_list(
302                wl,
303                |i, cell, o, wl| {
304                    let cell_info = cell::CellInfo::get_wgt(cell).actual(i, grid.columns.len());
305
306                    if cell_info.column >= grid.columns.len() || cell_info.row >= grid.rows.len() {
307                        wl.collapse_child(cells_offset + i);
308                        return PxSize::zero(); // continue;
309                    }
310
311                    let cell_offset = PxVector::new(grid.columns[cell_info.column].x, grid.rows[cell_info.row].y);
312                    let mut cell_size = PxSize::zero();
313
314                    for col in cell_info.column..(cell_info.column + cell_info.column_span).min(grid.columns.len()) {
315                        if grid.columns[col].width != Px(0) {
316                            cell_size.width += grid.columns[col].width + spacing.column;
317                        }
318                    }
319                    cell_size.width -= spacing.column;
320
321                    for row in cell_info.row..(cell_info.row + cell_info.row_span).min(grid.rows.len()) {
322                        if grid.rows[row].height != Px(0) {
323                            cell_size.height += grid.rows[row].height + spacing.row;
324                        }
325                    }
326                    cell_size.height -= spacing.row;
327
328                    if cell_size.is_empty() {
329                        wl.collapse_child(cells_offset + i);
330                        return PxSize::zero(); // continue;
331                    }
332
333                    let (_, define_ref_frame) =
334                        LAYOUT.with_constraints(constraints.with_exact_size(cell_size), || wl.with_child(|wl| cell.layout(wl)));
335                    o.child_offset = cell_offset;
336                    o.define_reference_frame = define_ref_frame;
337
338                    cell_size
339                },
340                |_, _| PxSize::zero(),
341            );
342            cells.commit_data().request_render();
343
344            *final_size = constraints.inner().fill_size_or(grid_size);
345        }
346        UiNodeOp::Render { frame } => {
347            c.delegated();
348
349            if mem::take(&mut is_measured) {
350                LAYOUT.with_context(last_layout.clone(), || {
351                    let _ = grid.grid_layout(&mut WidgetMeasure::new_reuse(None), c.node(), &spacing);
352                });
353            }
354
355            let grid = &grid;
356
357            if grid.is_collapse() {
358                return;
359            }
360
361            let mut children = GridChildrenMut(c.node());
362            let mut children = children.children().iter_mut();
363            let columns = children.next().unwrap();
364            let rows = children.next().unwrap();
365            let cells: &mut PanelList = children.next().unwrap().downcast_mut().unwrap();
366            let offset_key = cells.offset_key();
367
368            columns.for_each_child(|i, child| {
369                let offset = PxVector::new(grid.columns[i].x, Px(0));
370                frame.push_reference_frame(
371                    (offset_key, i as u32).into(),
372                    FrameValue::Value(offset.into()),
373                    true,
374                    true,
375                    |frame| {
376                        child.render(frame);
377                    },
378                );
379            });
380            let i_extra = columns.children_len();
381            rows.for_each_child(|i, child| {
382                let offset = PxVector::new(Px(0), grid.rows[i].y);
383                frame.push_reference_frame(
384                    (offset_key, (i + i_extra) as u32).into(),
385                    FrameValue::Value(offset.into()),
386                    true,
387                    true,
388                    |frame| {
389                        child.render(frame);
390                    },
391                );
392            });
393            let i_extra = i_extra + rows.children_len();
394            cells.for_each_z_sorted(|i, child, data| {
395                if data.define_reference_frame {
396                    frame.push_reference_frame(
397                        (offset_key, (i + i_extra) as u32).into(),
398                        FrameValue::Value(data.child_offset.into()),
399                        true,
400                        true,
401                        |frame| {
402                            child.render(frame);
403                        },
404                    );
405                } else {
406                    frame.push_child(data.child_offset, |frame| child.render(frame));
407                }
408            });
409        }
410        UiNodeOp::RenderUpdate { update } => {
411            c.delegated();
412
413            if mem::take(&mut is_measured) {
414                LAYOUT.with_context(last_layout.clone(), || {
415                    let _ = grid.grid_layout(&mut WidgetMeasure::new_reuse(None), c.node(), &spacing);
416                });
417            }
418
419            let grid = &grid;
420
421            if grid.is_collapse() {
422                return;
423            }
424
425            let mut children = GridChildrenMut(c.node());
426            let mut children = children.children().iter_mut();
427            let columns = children.next().unwrap();
428            let rows = children.next().unwrap();
429            let cells: &mut PanelList = children.next().unwrap().downcast_mut().unwrap();
430
431            columns.for_each_child(|i, child| {
432                let offset = PxVector::new(grid.columns[i].x, Px(0));
433                update.with_transform_value(&offset.into(), |update| {
434                    child.render_update(update);
435                });
436            });
437            rows.for_each_child(|i, child| {
438                let offset = PxVector::new(Px(0), grid.rows[i].y);
439                update.with_transform_value(&offset.into(), |update| {
440                    child.render_update(update);
441                });
442            });
443            cells.for_each_child(|_, child, data| {
444                if data.define_reference_frame {
445                    update.with_transform_value(&data.child_offset.into(), |update| {
446                        child.render_update(update);
447                    });
448                } else {
449                    update.with_child(data.child_offset, |update| {
450                        child.render_update(update);
451                    })
452                }
453            })
454        }
455        _ => {}
456    })
457}
458
459/// Arguments for [`auto_grow_fn`].
460///
461/// [`auto_grow_fn`]: fn@auto_grow_fn
462#[derive(Clone, Debug)]
463#[non_exhaustive]
464pub struct AutoGrowFnArgs {
465    /// Auto-grow direction.
466    pub mode: AutoGrowMode,
467    /// Column index.
468    pub index: usize,
469}
470impl AutoGrowFnArgs {
471    /// New args.
472    pub fn new(mode: AutoGrowMode, index: usize) -> Self {
473        Self { mode, index }
474    }
475}
476
477/// Grid auto-grow direction.
478///
479/// The associated value is the maximum columns or rows that are allowed in the grid.
480#[derive(Clone, Copy, PartialEq, Eq)]
481pub enum AutoGrowMode {
482    /// Auto generate columns.
483    Columns(u32),
484    /// Auto generate rows.
485    Rows(u32),
486}
487impl AutoGrowMode {
488    /// Value that does not generate any new row or column.
489    pub const fn disabled() -> Self {
490        Self::Rows(0)
491    }
492
493    /// Columns, not specific maximum limit.
494    pub const fn columns() -> Self {
495        Self::Columns(u32::MAX)
496    }
497
498    /// Rows, not specific maximum limit.
499    pub const fn rows() -> Self {
500        Self::Rows(u32::MAX)
501    }
502
503    /// Set the maximum columns or rows allowed.
504    pub fn with_limit(self, limit: u32) -> Self {
505        match self {
506            AutoGrowMode::Columns(_) => AutoGrowMode::Columns(limit),
507            AutoGrowMode::Rows(_) => AutoGrowMode::Rows(limit),
508        }
509    }
510}
511impl fmt::Debug for AutoGrowMode {
512    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
513        if f.alternate() {
514            write!(f, "AutoGrowMode::")?;
515        }
516        match self {
517            AutoGrowMode::Rows(0) => write!(f, "disabled()"),
518            AutoGrowMode::Columns(u32::MAX) => write!(f, "Columns(MAX)"),
519            AutoGrowMode::Rows(u32::MAX) => write!(f, "Rows(MAX)"),
520            AutoGrowMode::Columns(l) => write!(f, "Columns({l})"),
521            AutoGrowMode::Rows(l) => write!(f, "Rows({l})"),
522        }
523    }
524}
525
526#[doc(inline)]
527pub use column::Column;
528
529/// Column widget and properties.
530pub mod column {
531    use super::*;
532
533    /// Grid column definition.
534    ///
535    /// This widget is layout to define the actual column width, it is not the parent
536    /// of the cells, only the `width` and `align` properties affect the cells.
537    ///
538    /// See the [`Grid::columns`] property for more details.
539    ///
540    /// # Shorthand
541    ///
542    /// The `Column!` macro provides a shorthand init that sets the width, `grid::Column!(1.lft())` instantiates
543    /// a column with width of *1 leftover*.
544    #[widget($crate::Column { ($width:expr) => { width = $width; }; })]
545    pub struct Column(WidgetBase);
546    impl Column {
547        widget_impl! {
548            /// Column max width.
549            pub max_width(max: impl IntoVar<Length>);
550
551            /// Column min width.
552            pub min_width(min: impl IntoVar<Length>);
553
554            /// Column width.
555            pub width(width: impl IntoVar<Length>);
556        }
557
558        fn widget_intrinsic(&mut self) {
559            widget_set! {
560                self;
561                access_role = AccessRole::Column;
562            }
563        }
564    }
565
566    static_id! {
567        /// Column index, total in the parent widget set by the parent.
568        pub(super) static ref INDEX_ID: StateId<(usize, usize)>;
569    }
570
571    /// If the column index is even.
572    ///
573    /// Column index is zero-based, so the first column is even, the next [`is_odd`].
574    ///
575    /// [`is_odd`]: fn@is_odd
576    #[property(CONTEXT, widget_impl(Column))]
577    pub fn is_even(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
578        widget_state_is_state(child, |w| w.get(*INDEX_ID).copied().unwrap_or((0, 0)).0 % 2 == 0, |_| false, state)
579    }
580
581    /// If the column index is odd.
582    ///
583    /// Column index is zero-based, so the first column [`is_even`], the next one is odd.
584    ///
585    /// [`is_even`]: fn@is_even
586    #[property(CONTEXT, widget_impl(Column))]
587    pub fn is_odd(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
588        widget_state_is_state(child, |w| w.get(*INDEX_ID).copied().unwrap_or((0, 0)).0 % 2 != 0, |_| false, state)
589    }
590
591    /// If the column is the first.
592    #[property(CONTEXT, widget_impl(Column))]
593    pub fn is_first(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
594        widget_state_is_state(
595            child,
596            |w| {
597                let (i, l) = w.get(*INDEX_ID).copied().unwrap_or((0, 0));
598                i == 0 && l > 0
599            },
600            |_| false,
601            state,
602        )
603    }
604
605    /// If the column is the last.
606    #[property(CONTEXT, widget_impl(Column))]
607    pub fn is_last(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
608        widget_state_is_state(
609            child,
610            |w| {
611                let (i, l) = w.get(*INDEX_ID).copied().unwrap_or((0, 0));
612                i < l && i == l - 1
613            },
614            |_| false,
615            state,
616        )
617    }
618
619    /// Get the column index.
620    ///
621    /// The column index is zero-based.
622    #[property(CONTEXT, widget_impl(Column))]
623    pub fn get_index(child: impl IntoUiNode, state: impl IntoVar<usize>) -> UiNode {
624        widget_state_get_state(
625            child,
626            |w, &i| {
627                let a = w.get(*INDEX_ID).copied().unwrap_or((0, 0)).0;
628                if a != i { Some(a) } else { None }
629            },
630            |_, &i| if i != 0 { Some(0) } else { None },
631            state,
632        )
633    }
634
635    /// Get the column index and number of columns.
636    #[property(CONTEXT, widget_impl(Column))]
637    pub fn get_index_len(child: impl IntoUiNode, state: impl IntoVar<(usize, usize)>) -> UiNode {
638        widget_state_get_state(
639            child,
640            |w, &i| {
641                let a = w.get(*INDEX_ID).copied().unwrap_or((0, 0));
642                if a != i { Some(a) } else { None }
643            },
644            |_, &i| if i != (0, 0) { Some((0, 0)) } else { None },
645            state,
646        )
647    }
648
649    /// Get the column index, starting from the last column at `0`.
650    #[property(CONTEXT, widget_impl(Column))]
651    pub fn get_rev_index(child: impl IntoUiNode, state: impl IntoVar<usize>) -> UiNode {
652        widget_state_get_state(
653            child,
654            |w, &i| {
655                let a = w.get(*INDEX_ID).copied().unwrap_or((0, 0));
656                let a = a.1 - a.0;
657                if a != i { Some(a) } else { None }
658            },
659            |_, &i| if i != 0 { Some(0) } else { None },
660            state,
661        )
662    }
663}
664
665#[doc(inline)]
666pub use row::Row;
667
668/// Row widget and properties.
669pub mod row {
670    use super::*;
671
672    /// Grid row definition.
673    ///
674    /// This widget is layout to define the actual row height, it is not the parent
675    /// of the cells, only the `height` property affect the cells.
676    ///
677    /// See the [`Grid::rows`] property for more details.
678    ///
679    /// # Shorthand
680    ///
681    /// The `Row!` macro provides a shorthand init that sets the height, `grid::Row!(1.lft())` instantiates
682    /// a row with height of *1 leftover*.
683    #[widget($crate::Row { ($height:expr) => { height = $height; }; })]
684    pub struct Row(WidgetBase);
685    impl Row {
686        widget_impl! {
687            /// Row max height.
688            pub max_height(max: impl IntoVar<Length>);
689
690            /// Row min height.
691            pub min_height(max: impl IntoVar<Length>);
692
693            /// Row height.
694            pub height(max: impl IntoVar<Length>);
695        }
696
697        fn widget_intrinsic(&mut self) {
698            widget_set! {
699                self;
700                access_role = AccessRole::Row;
701            }
702        }
703    }
704
705    static_id! {
706        /// Row index, total in the parent widget set by the parent.
707        pub(super) static ref INDEX_ID: StateId<(usize, usize)>;
708    }
709
710    /// If the row index is even.
711    ///
712    /// Row index is zero-based, so the first row is even, the next [`is_odd`].
713    ///
714    /// [`is_odd`]: fn@is_odd
715    #[property(CONTEXT, widget_impl(Row))]
716    pub fn is_even(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
717        widget_state_is_state(child, |w| w.get(*INDEX_ID).copied().unwrap_or((0, 0)).0 % 2 == 0, |_| false, state)
718    }
719
720    /// If the row index is odd.
721    ///
722    /// Row index is zero-based, so the first row [`is_even`], the next one is odd.
723    ///
724    /// [`is_even`]: fn@is_even
725    #[property(CONTEXT, widget_impl(Row))]
726    pub fn is_odd(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
727        widget_state_is_state(child, |w| w.get(*INDEX_ID).copied().unwrap_or((0, 0)).0 % 2 != 0, |_| false, state)
728    }
729
730    /// If the row is the first.
731    #[property(CONTEXT, widget_impl(Row))]
732    pub fn is_first(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
733        widget_state_is_state(
734            child,
735            |w| {
736                let (i, l) = w.get(*INDEX_ID).copied().unwrap_or((0, 0));
737                i == 0 && l > 0
738            },
739            |_| false,
740            state,
741        )
742    }
743
744    /// If the row is the last.
745    #[property(CONTEXT, widget_impl(Row))]
746    pub fn is_last(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
747        widget_state_is_state(
748            child,
749            |w| {
750                let (i, l) = w.get(*INDEX_ID).copied().unwrap_or((0, 0));
751                i < l && i == l - 1
752            },
753            |_| false,
754            state,
755        )
756    }
757
758    /// Get the row index.
759    ///
760    /// The row index is zero-based.
761    #[property(CONTEXT, widget_impl(Row))]
762    pub fn get_index(child: impl IntoUiNode, state: impl IntoVar<usize>) -> UiNode {
763        widget_state_get_state(
764            child,
765            |w, &i| {
766                let a = w.get(*INDEX_ID).copied().unwrap_or((0, 0)).0;
767                if a != i { Some(a) } else { None }
768            },
769            |_, &i| if i != 0 { Some(0) } else { None },
770            state,
771        )
772    }
773
774    /// Get the row index and number of rows.
775    #[property(CONTEXT, widget_impl(Row))]
776    pub fn get_index_len(child: impl IntoUiNode, state: impl IntoVar<(usize, usize)>) -> UiNode {
777        widget_state_get_state(
778            child,
779            |w, &i| {
780                let a = w.get(*INDEX_ID).copied().unwrap_or((0, 0));
781                if a != i { Some(a) } else { None }
782            },
783            |_, &i| if i != (0, 0) { Some((0, 0)) } else { None },
784            state,
785        )
786    }
787
788    /// Get the row index, starting from the last row at `0`.
789    #[property(CONTEXT, widget_impl(Row))]
790    pub fn get_rev_index(child: impl IntoUiNode, state: impl IntoVar<usize>) -> UiNode {
791        widget_state_get_state(
792            child,
793            |w, &i| {
794                let a = w.get(*INDEX_ID).copied().unwrap_or((0, 0));
795                let a = a.1 - a.0;
796                if a != i { Some(a) } else { None }
797            },
798            |_, &i| if i != 0 { Some(0) } else { None },
799            state,
800        )
801    }
802}
803
804#[doc(inline)]
805pub use cell::Cell;
806
807/// Cell widget and properties.
808pub mod cell {
809    use super::*;
810
811    /// Grid cell container.
812    ///
813    /// This widget defines properties that position and size widgets in a [`Grid!`].
814    ///
815    /// See the [`Grid::cells`] property for more details.
816    ///
817    /// [`Grid!`]: struct@Grid
818    #[widget($crate::Cell)]
819    pub struct Cell(zng_wgt_container::Container);
820    impl Cell {
821        fn widget_intrinsic(&mut self) {
822            widget_set! {
823                self;
824                access_role = AccessRole::GridCell;
825            }
826        }
827    }
828
829    /// Represents values set by cell properties in a widget.
830    #[derive(Clone, Copy, Debug)]
831    #[non_exhaustive]
832    pub struct CellInfo {
833        /// The [`column`] value.
834        ///
835        /// [`column`]: fn@column
836        pub column: usize,
837
838        /// The [`column_span`] value.
839        ///
840        /// [`column_span`]: fn@column_span
841        pub column_span: usize,
842
843        /// The [`row`] value.
844        ///
845        /// [`row`]: fn@row
846        pub row: usize,
847
848        /// The [`row_span`] value.
849        ///
850        /// [`row_span`]: fn@row_span
851        pub row_span: usize,
852    }
853    impl Default for CellInfo {
854        fn default() -> Self {
855            Self {
856                column: 0,
857                column_span: 1,
858                row: 0,
859                row_span: 1,
860            }
861        }
862    }
863    impl CellInfo {
864        /// Compute or correct the column and row of the cell.
865        ///
866        /// The `logical_index` is the index of the cell widget in the cell node list.
867        pub fn actual(mut self, logical_index: usize, columns_len: usize) -> Self {
868            if self.column == usize::MAX {
869                self.column = logical_index % columns_len;
870            } else {
871                self.column = self.column.min(columns_len - 1);
872            }
873            if self.row == usize::MAX {
874                self.row = logical_index / columns_len
875            }
876            self
877        }
878
879        /// Get the cell info stored in the [`WIDGET`] state.
880        ///
881        /// [`WIDGET`]: zng_wgt::prelude::WIDGET
882        pub fn get() -> Self {
883            WIDGET.get_state(*INFO_ID).unwrap_or_default()
884        }
885
886        /// Get the cell info stored in the `wgt` state.
887        pub fn get_wgt(wgt: &mut UiNode) -> Self {
888            match wgt.as_widget() {
889                Some(mut wgt) => wgt.with_context(WidgetUpdateMode::Ignore, Self::get),
890                None => CellInfo::default(),
891            }
892        }
893    }
894
895    static_id! {
896        /// Id for widget state set by cell properties.
897        ///
898        /// The parent grid uses this info to position and size the cell widget.
899        pub static ref INFO_ID: StateId<CellInfo>;
900    }
901
902    /// Cell column index.
903    ///
904    /// If set to [`usize::MAX`] the cell is positioned based on the logical index.
905    ///
906    /// Is `0` by default.
907    ///
908    /// This property sets the [`INFO_ID`].
909    ///
910    /// See also the [`at`] property to bind both indexes at the same time.
911    ///
912    /// [`at`]: fn@at
913    #[property(CONTEXT, default(0), widget_impl(Cell))]
914    pub fn column(child: impl IntoUiNode, col: impl IntoVar<usize>) -> UiNode {
915        with_widget_state_modify(child, *INFO_ID, col, CellInfo::default, |i, &c| {
916            if i.column != c {
917                i.column = c;
918                WIDGET.layout();
919            }
920        })
921    }
922
923    /// Cell row index.
924    ///
925    /// If set to [`usize::MAX`] the cell is positioned based on the logical index.
926    ///
927    /// Is `0` by default.
928    ///
929    /// This property sets the [`INFO_ID`].
930    ///
931    /// See also the [`at`] property to bind both indexes at the same time.
932    ///
933    /// [`at`]: fn@at
934    #[property(CONTEXT, default(0), widget_impl(Cell))]
935    pub fn row(child: impl IntoUiNode, row: impl IntoVar<usize>) -> UiNode {
936        with_widget_state_modify(child, *INFO_ID, row, CellInfo::default, |i, &r| {
937            if i.row != r {
938                i.row = r;
939                WIDGET.layout();
940            }
941        })
942    }
943
944    /// Cell column and row indexes.
945    ///
946    /// If set to [`AT_AUTO`] the cell is positioned based on the logical index.
947    ///
948    /// Is `(0, 0)` by default.
949    ///
950    /// This property sets the [`INFO_ID`].
951    ///
952    /// See also the [`column`] or [`row`] properties to bind each index individually.
953    ///
954    /// [`column`]: fn@column
955    /// [`row`]: fn@row
956    #[property(CONTEXT, default((0, 0)), widget_impl(Cell))]
957    pub fn at(child: impl IntoUiNode, column_row: impl IntoVar<(usize, usize)>) -> UiNode {
958        with_widget_state_modify(child, *INFO_ID, column_row, CellInfo::default, |i, &(col, row)| {
959            if i.column != col || i.row != row {
960                i.column = col;
961                i.row = row;
962                WIDGET.layout();
963            }
964        })
965    }
966
967    /// Cell column span.
968    ///
969    /// Number of *cells* this one spans over horizontally, starting from the column index and spanning to the right.
970    ///
971    /// Is `1` by default, the index is clamped between `1..max` where max is the maximum number of valid columns
972    /// to the right of the cell column index.
973    ///
974    /// Note that the cell will not influence the column width if it spans over multiple columns.
975    ///
976    /// This property sets the [`INFO_ID`].
977    ///
978    /// See also the [`span`] property to bind both spans at the same time.
979    ///
980    /// [`span`]: fn@span
981    #[property(CONTEXT, default(1), widget_impl(Cell))]
982    pub fn column_span(child: impl IntoUiNode, span: impl IntoVar<usize>) -> UiNode {
983        with_widget_state_modify(child, *INFO_ID, span, CellInfo::default, |i, &s| {
984            if i.column_span != s {
985                i.column_span = s;
986                WIDGET.layout();
987            }
988        })
989    }
990
991    /// Cell row span.
992    ///
993    /// Number of *cells* this one spans over vertically, starting from the row index and spanning down.
994    ///
995    /// Is `1` by default, the index is clamped between `1..max` where max is the maximum number of valid rows
996    /// down from the cell column index.
997    ///
998    /// Note that the cell will not influence the row height if it spans over multiple rows.
999    ///
1000    /// This property sets the [`INFO_ID`].
1001    ///
1002    /// See also the [`span`] property to bind both spans at the same time.
1003    ///
1004    /// [`span`]: fn@span
1005    #[property(CONTEXT, default(1), widget_impl(Cell))]
1006    pub fn row_span(child: impl IntoUiNode, span: impl IntoVar<usize>) -> UiNode {
1007        with_widget_state_modify(child, *INFO_ID, span, CellInfo::default, |i, &s| {
1008            if i.row_span != s {
1009                i.row_span = s;
1010                WIDGET.layout();
1011            }
1012        })
1013    }
1014
1015    /// Cell column and row span.
1016    ///
1017    /// Is `(1, 1)` by default.
1018    ///
1019    /// This property sets the [`INFO_ID`].
1020    ///
1021    /// See also the [`column_span`] or [`row_span`] properties to bind each index individually.
1022    ///
1023    /// [`column_span`]: fn@column_span
1024    /// [`row_span`]: fn@row_span
1025    #[property(CONTEXT, default((1, 1)), widget_impl(Cell))]
1026    pub fn span(child: impl IntoUiNode, span: impl IntoVar<(usize, usize)>) -> UiNode {
1027        with_widget_state_modify(child, *INFO_ID, span, CellInfo::default, |i, &(cs, rs)| {
1028            if i.column_span != rs || i.row_span != rs {
1029                i.column_span = cs;
1030                i.row_span = rs;
1031                WIDGET.layout();
1032            }
1033        })
1034    }
1035
1036    /// Value for [`at`] that causes the cell to be positioned based on the logical index *i*,
1037    /// for columns *i % columns* and for rows *i / columns*.
1038    ///
1039    /// [`at`]: fn@at
1040    pub const AT_AUTO: (usize, usize) = (usize::MAX, usize::MAX);
1041}
1042
1043#[derive(Clone, Copy)]
1044struct ColRowMeta(f32);
1045impl ColRowMeta {
1046    /// `width` or `height` contains the largest cell or `Px::MIN` if cell measure is pending.
1047    fn is_default(self) -> bool {
1048        self.0.is_sign_negative() && self.0.is_infinite()
1049    }
1050
1051    /// Return the leftover factor if the column or row must be measured on a fraction of the leftover space.
1052    fn is_leftover(self) -> Option<Factor> {
1053        if self.0 >= 0.0 { Some(Factor(self.0)) } else { None }
1054    }
1055
1056    /// `width` or `height` contains the final length or is pending layout `Px::MIN`.
1057    fn is_exact(self) -> bool {
1058        self.0.is_nan()
1059    }
1060
1061    fn exact() -> Self {
1062        Self(f32::NAN)
1063    }
1064
1065    fn leftover(f: Factor) -> Self {
1066        Self(f.0.max(0.0))
1067    }
1068}
1069impl Default for ColRowMeta {
1070    fn default() -> Self {
1071        Self(f32::NEG_INFINITY)
1072    }
1073}
1074impl fmt::Debug for ColRowMeta {
1075    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1076        if self.is_default() {
1077            write!(f, "default")
1078        } else if self.is_exact() {
1079            write!(f, "exact")
1080        } else if let Some(l) = self.is_leftover() {
1081            write!(f, "leftover({l})")
1082        } else {
1083            write!(f, "ColRowMeta({})", self.0)
1084        }
1085    }
1086}
1087
1088#[derive(Clone, Copy, Debug)]
1089struct ColumnLayout {
1090    meta: ColRowMeta,
1091    was_leftover: bool,
1092    x: Px,
1093    width: Px,
1094    min_width: Px,
1095    max_width: Px,
1096}
1097impl Default for ColumnLayout {
1098    fn default() -> Self {
1099        Self {
1100            meta: ColRowMeta::default(),
1101            was_leftover: false,
1102            x: Px::MIN,
1103            width: Px::MIN,
1104            min_width: Px::MIN,
1105            max_width: Px::MAX,
1106        }
1107    }
1108}
1109#[derive(Clone, Copy, Debug)]
1110struct RowLayout {
1111    meta: ColRowMeta,
1112    was_leftover: bool,
1113    y: Px,
1114    height: Px,
1115    min_height: Px,
1116    max_height: Px,
1117}
1118impl Default for RowLayout {
1119    fn default() -> Self {
1120        Self {
1121            meta: ColRowMeta::default(),
1122            was_leftover: false,
1123            y: Px::MIN,
1124            height: Px::MIN,
1125            min_height: Px::MIN,
1126            max_height: Px::MAX,
1127        }
1128    }
1129}
1130
1131#[derive(Default)]
1132struct GridLayout {
1133    columns: Vec<ColumnLayout>,
1134    rows: Vec<RowLayout>,
1135}
1136impl GridLayout {
1137    fn is_collapse(&self) -> bool {
1138        self.columns.is_empty() || self.rows.is_empty()
1139    }
1140
1141    fn collapse(&mut self) {
1142        self.columns.clear();
1143        self.rows.clear();
1144    }
1145
1146    /// add/remove info entries, auto-grow/shrink
1147    fn update_entries(&mut self, children: &mut GridChildren, auto_mode: AutoGrowMode, auto_grow_fn: &Var<WidgetFn<AutoGrowFnArgs>>) {
1148        let mut children = GridChildrenMut(children);
1149
1150        // max needed column or row in the auto_mode axis.
1151        let mut max_custom = 0;
1152        let mut max_auto_placed_i = 0;
1153        children.cells().for_each_child(|i, c, _| {
1154            let info = cell::CellInfo::get_wgt(c);
1155
1156            let n = match auto_mode {
1157                AutoGrowMode::Rows(_) => info.row,
1158                AutoGrowMode::Columns(_) => info.column,
1159            };
1160            if n == usize::MAX {
1161                max_auto_placed_i = i;
1162            } else {
1163                max_custom = max_custom.max(n);
1164            }
1165        });
1166
1167        let mut imaginary_cols = 0;
1168        let mut imaginary_rows = 0;
1169
1170        match auto_mode {
1171            AutoGrowMode::Rows(max) => {
1172                let columns_len = children.all_columns().children_len();
1173                if columns_len == 0 {
1174                    tracing::warn!(
1175                        "grid {} has no columns and auto_grow_mode={:?}, no cell will be visible",
1176                        WIDGET.id(),
1177                        auto_mode,
1178                    );
1179                    self.collapse();
1180                    return;
1181                }
1182
1183                let max_auto_placed = max_auto_placed_i / columns_len;
1184                let max_needed_len = max_auto_placed.max(max_custom).min(max as usize) + 1;
1185
1186                let rows_len = children.all_rows().children_len();
1187
1188                if rows_len < max_needed_len {
1189                    let auto = children.auto_rows();
1190                    let mut index = rows_len;
1191
1192                    let view = auto_grow_fn.get();
1193                    if view.is_nil() {
1194                        imaginary_rows = max_needed_len - rows_len;
1195                    } else {
1196                        while index < max_needed_len {
1197                            let mut row = view(AutoGrowFnArgs { mode: auto_mode, index });
1198                            row.init();
1199                            auto.push(row);
1200                            index += 1;
1201                        }
1202                    }
1203                } else if rows_len > max_needed_len {
1204                    let remove = rows_len - max_needed_len;
1205                    let auto = children.auto_rows();
1206                    let s = auto.len().saturating_sub(remove);
1207                    for mut auto in auto.drain(s..) {
1208                        auto.deinit();
1209                    }
1210                }
1211            }
1212            AutoGrowMode::Columns(max) => {
1213                let rows_len = children.all_rows().children_len();
1214                if rows_len == 0 {
1215                    tracing::warn!(
1216                        "grid {} has no rows and auto_grow_mode={:?}, no cell will be visible",
1217                        WIDGET.id(),
1218                        auto_mode,
1219                    );
1220                    self.collapse();
1221                    return;
1222                }
1223
1224                let max_auto_placed = max_auto_placed_i / rows_len;
1225                let max_needed_len = max_auto_placed.max(max_custom).min(max as usize) + 1;
1226
1227                let cols_len = children.all_columns().children_len();
1228
1229                if cols_len < max_needed_len {
1230                    let auto = children.auto_columns();
1231                    let mut index = cols_len;
1232
1233                    let view = auto_grow_fn.get();
1234                    if view.is_nil() {
1235                        imaginary_cols = max_needed_len - cols_len;
1236                    } else {
1237                        while index < max_needed_len {
1238                            let mut column = view(AutoGrowFnArgs { mode: auto_mode, index });
1239                            column.init();
1240                            auto.push(column);
1241                            index += 1;
1242                        }
1243                    }
1244                } else if cols_len > max_needed_len {
1245                    let remove = cols_len - max_needed_len;
1246                    let auto = children.auto_columns();
1247                    let s = auto.len().saturating_sub(remove);
1248                    for mut auto in auto.drain(s..) {
1249                        auto.deinit();
1250                    }
1251                }
1252            }
1253        }
1254
1255        // Set index for column and row.
1256        let columns_len = children.all_columns().children_len() + imaginary_cols;
1257        children.all_columns_node().for_each_child(|i, c| {
1258            if let Some(mut wgt) = c.as_widget() {
1259                wgt.with_context(WidgetUpdateMode::Bubble, || {
1260                    let prev = WIDGET.set_state(*column::INDEX_ID, (i, columns_len));
1261                    if prev != Some((i, columns_len)) {
1262                        WIDGET.update();
1263                    }
1264                });
1265            }
1266        });
1267        let rows_len = children.all_rows().children_len() + imaginary_rows;
1268        children.all_rows_node().for_each_child(|i, r| {
1269            if let Some(mut wgt) = r.as_widget() {
1270                wgt.with_context(WidgetUpdateMode::Bubble, || {
1271                    let prev = WIDGET.set_state(*row::INDEX_ID, (i, rows_len));
1272                    if prev != Some((i, rows_len)) {
1273                        WIDGET.update();
1274                    }
1275                });
1276            }
1277        });
1278
1279        self.columns.resize(columns_len, ColumnLayout::default());
1280        self.rows.resize(rows_len, RowLayout::default());
1281    }
1282
1283    #[must_use]
1284    fn grid_layout(&mut self, wm: &mut WidgetMeasure, children: &mut GridChildren, spacing: &Var<GridSpacing>) -> (PxGridSpacing, PxSize) {
1285        if self.is_collapse() {
1286            return (PxGridSpacing::zero(), PxSize::zero());
1287        }
1288
1289        let spacing = spacing.layout();
1290        let constraints = LAYOUT.constraints();
1291
1292        let fill_x = constraints.x.fill_or_exact();
1293        let fill_y = constraints.y.fill_or_exact();
1294
1295        let mut children = GridChildrenMut(children);
1296        let mut children = children.children().iter_mut();
1297        let columns = children.next().unwrap();
1298        let rows = children.next().unwrap();
1299        let cells = children.next().unwrap();
1300
1301        // layout exact columns&rows, mark others for next passes.
1302
1303        let mut has_default = false;
1304        let mut has_leftover_cols = false;
1305        let mut has_leftover_rows = false;
1306        const MAX_PROBE: i32 = Px::MAX.0 - 1000;
1307
1308        columns.for_each_child(|ci, col| {
1309            let col_kind = WIDGET_SIZE.get_wgt(col).width;
1310
1311            let col_info = &mut self.columns[ci];
1312
1313            col_info.x = Px::MIN;
1314            col_info.width = Px::MIN;
1315            col_info.min_width = Px::MIN;
1316            col_info.max_width = Px::MAX;
1317
1318            match col_kind {
1319                WidgetLength::Default => {
1320                    col_info.meta = ColRowMeta::default();
1321                    has_default = true;
1322                }
1323                WidgetLength::Leftover(f) => {
1324                    col_info.meta = ColRowMeta::leftover(f);
1325                    col_info.was_leftover = true;
1326                    has_leftover_cols = true;
1327                }
1328                WidgetLength::Exact => {
1329                    col_info.width = LAYOUT.with_constraints(Align::TOP_LEFT.child_constraints(constraints), || col.measure(wm).width);
1330                    col_info.meta = ColRowMeta::exact();
1331                }
1332            }
1333            if matches!(col_kind, WidgetLength::Default | WidgetLength::Leftover(_)) {
1334                col_info.min_width = LAYOUT.with_constraints(PxConstraints2d::new_unbounded(), || col.measure(wm)).width;
1335                col_info.max_width = LAYOUT
1336                    .with_constraints(
1337                        PxConstraints2d::new_fill(Px(MAX_PROBE), Px(MAX_PROBE)).with_inner(true, true),
1338                        || col.measure(wm),
1339                    )
1340                    .width;
1341                if col_info.max_width == MAX_PROBE {
1342                    col_info.max_width = Px::MAX;
1343                }
1344            }
1345        });
1346        rows.for_each_child(|ri, row| {
1347            let row_kind = WIDGET_SIZE.get_wgt(row).height;
1348
1349            let row_info = &mut self.rows[ri];
1350
1351            row_info.y = Px::MIN;
1352            row_info.height = Px::MIN;
1353
1354            match row_kind {
1355                WidgetLength::Default => {
1356                    row_info.meta = ColRowMeta::default();
1357                    has_default = true;
1358                }
1359                WidgetLength::Leftover(f) => {
1360                    row_info.meta = ColRowMeta::leftover(f);
1361                    row_info.was_leftover = true;
1362                    has_leftover_rows = true;
1363                }
1364                WidgetLength::Exact => {
1365                    row_info.height = LAYOUT.with_constraints(Align::TOP_LEFT.child_constraints(constraints), || row.measure(wm).height);
1366                    row_info.meta = ColRowMeta::exact();
1367                }
1368            }
1369            if matches!(row_kind, WidgetLength::Default | WidgetLength::Leftover(_)) {
1370                row_info.min_height = LAYOUT.with_constraints(PxConstraints2d::new_unbounded(), || row.measure(wm)).height;
1371                row_info.max_height = LAYOUT
1372                    .with_constraints(
1373                        PxConstraints2d::new_fill(Px(MAX_PROBE), Px(MAX_PROBE)).with_inner(true, true),
1374                        || row.measure(wm),
1375                    )
1376                    .width;
1377                if row_info.max_height == MAX_PROBE {
1378                    row_info.max_height = Px::MAX;
1379                }
1380            }
1381        });
1382
1383        // reset imaginary
1384        for col in &mut self.columns[columns.children_len()..] {
1385            col.meta = ColRowMeta::default();
1386            col.x = Px::MIN;
1387            col.width = Px::MIN;
1388            col.min_width = Px::MIN;
1389            col.max_width = Px::MAX;
1390            has_default = true;
1391        }
1392        for row in &mut self.rows[rows.children_len()..] {
1393            row.meta = ColRowMeta::default();
1394            row.y = Px::MIN;
1395            row.height = Px::MIN;
1396            row.min_height = Px::MIN;
1397            row.max_height = Px::MAX;
1398            has_default = true;
1399        }
1400
1401        // Measure cells when needed, collect widest/tallest.
1402        // - For `Default` columns&rows to get their size.
1403        // - For `leftover` columns&rows when the grid is not fill or exact size, to get the `1.lft()` length.
1404        // - For leftover x default a second pass later in case the constrained leftover causes a different default.
1405        let mut has_leftover_x_default = false;
1406        let columns_len = self.columns.len();
1407        if has_default || (fill_x.is_none() && has_leftover_cols) || (fill_y.is_none() && has_leftover_rows) {
1408            cells.for_each_child(|i, cell| {
1409                let cell_info = cell::CellInfo::get_wgt(cell);
1410                if cell_info.column_span > 1 || cell_info.row_span > 1 {
1411                    return; // continue;
1412                }
1413                let cell_info = cell_info.actual(i, columns_len);
1414
1415                let col = &mut self.columns[cell_info.column];
1416                let row = &mut self.rows[cell_info.row];
1417
1418                let col_is_default = col.meta.is_default() || (fill_x.is_none() && col.meta.is_leftover().is_some());
1419                let col_is_exact = !col_is_default && col.meta.is_exact();
1420                let col_is_leftover = !col_is_default && col.meta.is_leftover().is_some();
1421
1422                let row_is_default = row.meta.is_default() || (fill_y.is_none() && row.meta.is_leftover().is_some());
1423                let row_is_exact = !row_is_default && row.meta.is_exact();
1424                let row_is_leftover = !row_is_default && row.meta.is_leftover().is_some();
1425
1426                if col_is_default {
1427                    if row_is_default {
1428                        // (default, default)
1429                        let size = LAYOUT.with_constraints(
1430                            PxConstraints2d::new_range(col.min_width, col.max_width, row.min_height, row.max_height),
1431                            || cell.measure(wm),
1432                        );
1433
1434                        col.width = col.width.max(size.width.clamp(col.min_width, col.max_width));
1435                        row.height = row.height.max(size.height);
1436                    } else if row_is_exact {
1437                        // (default, exact)
1438                        let size = LAYOUT.with_constraints(
1439                            PxConstraints2d::new_range(col.min_width, col.max_width, row.height, row.height),
1440                            || cell.measure(wm),
1441                        );
1442
1443                        col.width = col.width.max(size.width.clamp(col.min_width, col.max_width));
1444                    } else {
1445                        debug_assert!(row_is_leftover);
1446                        // (default, leftover)
1447                        let size = LAYOUT.with_constraints(
1448                            PxConstraints2d::new_range(col.min_width, col.max_width, row.min_height, row.max_height),
1449                            || cell.measure(wm),
1450                        );
1451
1452                        col.width = col.width.max(size.width.clamp(col.min_width, col.max_width));
1453
1454                        has_leftover_x_default = true;
1455                    }
1456                } else if col_is_exact {
1457                    if row_is_default {
1458                        // (exact, default)
1459                        let size = LAYOUT.with_constraints(
1460                            PxConstraints2d::new_range(col.width, col.width, row.min_height, row.max_height),
1461                            || cell.measure(wm),
1462                        );
1463
1464                        row.height = row.height.max(size.height.clamp(row.min_height, row.max_height));
1465                    }
1466                } else if row_is_default {
1467                    debug_assert!(col_is_leftover);
1468                    // (leftover, default)
1469                    let size = LAYOUT.with_constraints(
1470                        PxConstraints2d::new_range(col.min_width, col.max_width, row.min_height, row.max_height),
1471                        || cell.measure(wm),
1472                    );
1473
1474                    row.height = row.height.max(size.height.clamp(row.min_height, row.max_height));
1475
1476                    has_leftover_x_default = true;
1477                }
1478            });
1479        }
1480
1481        // distribute leftover grid space to columns
1482        if has_leftover_cols {
1483            let mut no_fill_1_lft = Px(0);
1484            let mut used_width = Px(0);
1485            let mut total_factor = Factor(0.0);
1486            let mut leftover_count = 0;
1487            let mut max_factor = 0.0_f32;
1488
1489            for col in &mut self.columns {
1490                if let Some(f) = col.meta.is_leftover() {
1491                    if fill_x.is_none() {
1492                        no_fill_1_lft = no_fill_1_lft.max(col.width);
1493                        col.width = Px::MIN;
1494                    }
1495                    max_factor = max_factor.max(f.0);
1496                    total_factor += f;
1497                    leftover_count += 1;
1498                } else if col.width > Px(0) {
1499                    used_width += col.width;
1500                }
1501            }
1502
1503            // handle big leftover factors
1504            if total_factor.0.is_infinite() {
1505                total_factor = Factor(0.0);
1506
1507                if max_factor.is_infinite() {
1508                    // +inf takes all space
1509                    for col in &mut self.columns {
1510                        if let Some(f) = col.meta.is_leftover() {
1511                            if f.0.is_infinite() {
1512                                col.meta = ColRowMeta::leftover(Factor(1.0));
1513                                total_factor.0 += 1.0;
1514                            } else {
1515                                col.meta = ColRowMeta::leftover(Factor(0.0));
1516                            }
1517                        }
1518                    }
1519                } else {
1520                    // scale down every factor to fit
1521                    let scale = f32::MAX / max_factor / leftover_count as f32;
1522                    for col in &mut self.columns {
1523                        if let Some(f) = col.meta.is_leftover() {
1524                            let f = Factor(f.0 * scale);
1525                            col.meta = ColRowMeta::leftover(f);
1526                            total_factor += f;
1527                        }
1528                    }
1529                }
1530            }
1531
1532            // individual factors under `1.0` behave like `Length::Factor`.
1533            if total_factor < Factor(1.0) {
1534                total_factor = Factor(1.0);
1535            }
1536
1537            let mut leftover_width = if let Some(w) = fill_x {
1538                let vis_columns = self.columns.iter().filter(|c| c.width != Px(0)).count() as i32;
1539                w - used_width - spacing.column * Px(vis_columns - 1).max(Px(0))
1540            } else {
1541                // grid has no width, so `1.lft()` is defined by the widest cell measured using `Default` constraints.
1542                let mut unbounded_width = used_width;
1543                for col in &self.columns {
1544                    if let Some(f) = col.meta.is_leftover() {
1545                        unbounded_width += no_fill_1_lft * f;
1546                    }
1547                }
1548                let bounded_width = constraints.x.clamp(unbounded_width);
1549                bounded_width - used_width
1550            };
1551            leftover_width = leftover_width.max(Px(0));
1552
1553            let view_columns_len = columns.children_len();
1554
1555            // find extra leftover space from columns that can't fully fill their requested leftover length.
1556            let mut settled_all = false;
1557            while !settled_all && leftover_width > Px(0) {
1558                settled_all = true;
1559
1560                for col in self.columns[..view_columns_len].iter_mut() {
1561                    let lft = if let Some(lft) = col.meta.is_leftover() {
1562                        lft
1563                    } else {
1564                        continue;
1565                    };
1566
1567                    let width = lft.0 * leftover_width.0 as f32 / total_factor.0;
1568                    let width = Px(width as i32);
1569                    col.width = width.clamp(col.min_width, col.max_width);
1570
1571                    if col.width != width {
1572                        // reached a max/min, convert this column to "exact" and remove it from
1573                        // the leftover pool.
1574                        settled_all = false;
1575
1576                        col.meta = ColRowMeta::exact();
1577
1578                        if col.width != Px(0) {
1579                            leftover_width -= col.width + spacing.column;
1580                            total_factor -= lft;
1581                            if total_factor < Factor(1.0) {
1582                                total_factor = Factor(1.0);
1583                            }
1584                        }
1585                    }
1586                }
1587            }
1588
1589            leftover_width = leftover_width.max(Px(0));
1590
1591            // finish settled leftover columns that can fill the requested leftover length.
1592            for col in &mut self.columns {
1593                let lft = if let Some(lft) = col.meta.is_leftover() {
1594                    lft
1595                } else {
1596                    continue;
1597                };
1598
1599                let width = lft.0 * leftover_width.0 as f32 / total_factor.0;
1600                col.width = Px(width as i32).clamp(col.min_width, col.max_width);
1601                col.meta = ColRowMeta::exact();
1602            }
1603        }
1604        // distribute leftover grid space to rows
1605        if has_leftover_rows {
1606            let mut no_fill_1_lft = Px(0);
1607            let mut used_height = Px(0);
1608            let mut total_factor = Factor(0.0);
1609            let mut leftover_count = 0;
1610            let mut max_factor = 0.0_f32;
1611
1612            for row in &mut self.rows {
1613                if let Some(f) = row.meta.is_leftover() {
1614                    if fill_y.is_none() {
1615                        no_fill_1_lft = no_fill_1_lft.max(row.height);
1616                        row.height = Px::MIN;
1617                    }
1618                    max_factor = max_factor.max(f.0);
1619                    total_factor += f;
1620                    leftover_count += 1;
1621                } else if row.height > Px(0) {
1622                    used_height += row.height;
1623                }
1624            }
1625
1626            // handle big leftover factors
1627            if total_factor.0.is_infinite() {
1628                total_factor = Factor(0.0);
1629
1630                if max_factor.is_infinite() {
1631                    // +inf takes all space
1632                    for row in &mut self.rows {
1633                        if let Some(f) = row.meta.is_leftover() {
1634                            if f.0.is_infinite() {
1635                                row.meta = ColRowMeta::leftover(Factor(1.0));
1636                                total_factor.0 += 1.0;
1637                            } else {
1638                                row.meta = ColRowMeta::leftover(Factor(0.0));
1639                            }
1640                        }
1641                    }
1642                } else {
1643                    // scale down every factor to fit
1644                    let scale = f32::MAX / max_factor / leftover_count as f32;
1645                    for row in &mut self.rows {
1646                        if let Some(f) = row.meta.is_leftover() {
1647                            let f = Factor(f.0 * scale);
1648                            row.meta = ColRowMeta::leftover(f);
1649                            total_factor += f;
1650                        }
1651                    }
1652                }
1653            }
1654
1655            // individual factors under `1.0` behave like `Length::Factor`.
1656            if total_factor < Factor(1.0) {
1657                total_factor = Factor(1.0);
1658            }
1659
1660            let mut leftover_height = if let Some(h) = fill_y {
1661                let vis_rows = self.rows.iter().filter(|c| c.height != Px(0)).count() as i32;
1662                h - used_height - spacing.row * Px(vis_rows - 1).max(Px(0))
1663            } else {
1664                // grid has no height, so `1.lft()` is defined by the tallest cell measured using `Default` constraints.
1665                let mut unbounded_height = used_height;
1666                for row in &self.rows {
1667                    if let Some(f) = row.meta.is_leftover() {
1668                        unbounded_height += no_fill_1_lft * f;
1669                    }
1670                }
1671                let bounded_height = constraints.x.clamp(unbounded_height);
1672                bounded_height - used_height
1673            };
1674            leftover_height = leftover_height.max(Px(0));
1675
1676            let view_rows_len = rows.children_len();
1677
1678            // find extra leftover space from leftover that can't fully fill their requested leftover length.
1679            let mut settled_all = false;
1680            while !settled_all && leftover_height > Px(0) {
1681                settled_all = true;
1682
1683                for row in self.rows[..view_rows_len].iter_mut() {
1684                    let lft = if let Some(lft) = row.meta.is_leftover() {
1685                        lft
1686                    } else {
1687                        continue;
1688                    };
1689
1690                    let height = lft.0 * leftover_height.0 as f32 / total_factor.0;
1691                    let height = Px(height as i32);
1692                    row.height = height.clamp(row.min_height, row.max_height);
1693
1694                    if row.height != height {
1695                        // reached a max/min, convert this row to "exact" and remove it from
1696                        // the leftover pool.
1697                        settled_all = false;
1698
1699                        row.meta = ColRowMeta::exact();
1700
1701                        if row.height != Px(0) {
1702                            leftover_height -= row.height + spacing.row;
1703                            total_factor -= lft;
1704                            if total_factor < Factor(1.0) {
1705                                total_factor = Factor(1.0);
1706                            }
1707                        }
1708                    }
1709                }
1710            }
1711
1712            leftover_height = leftover_height.max(Px(0));
1713
1714            // finish settled leftover rows that can fill the requested leftover length.
1715            for row in &mut self.rows {
1716                let lft = if let Some(lft) = row.meta.is_leftover() {
1717                    lft
1718                } else {
1719                    continue;
1720                };
1721
1722                let height = lft.0 * leftover_height.0 as f32 / total_factor.0;
1723                row.height = Px(height as i32).clamp(row.min_height, row.max_height);
1724                row.meta = ColRowMeta::exact();
1725            }
1726        }
1727
1728        if has_leftover_x_default {
1729            // second measure pass with constrained leftovers to get a more accurate default
1730
1731            let c = LAYOUT.constraints();
1732
1733            cells.for_each_child(|i, cell| {
1734                let cell_info = cell::CellInfo::get_wgt(cell);
1735                if cell_info.column_span > 1 || cell_info.row_span > 1 {
1736                    return; // continue;
1737                }
1738
1739                let cell_info = cell_info.actual(i, columns_len);
1740
1741                let col = &mut self.columns[cell_info.column];
1742                let row = &mut self.rows[cell_info.row];
1743
1744                let col_is_default = col.meta.is_default() || (fill_x.is_none() && col.was_leftover);
1745                let col_is_leftover = col.was_leftover;
1746
1747                let row_is_default = row.meta.is_default() || (fill_y.is_none() && row.was_leftover);
1748                let row_is_leftover = row.was_leftover;
1749
1750                if col_is_default {
1751                    if row_is_leftover {
1752                        // (default, leftover)
1753
1754                        let size = LAYOUT.with_constraints(c.with_fill(false, false).with_exact_y(row.height), || cell.measure(wm));
1755
1756                        col.width = col.width.max(size.width);
1757                    }
1758                } else if row_is_default && col_is_leftover {
1759                    // (leftover, default)
1760
1761                    let size = LAYOUT.with_constraints(c.with_fill(false, false).with_exact_x(col.width), || cell.measure(wm));
1762
1763                    row.height = row.height.max(size.height);
1764                }
1765            });
1766        }
1767
1768        // compute column&row offsets
1769        let mut x = Px(0);
1770        let mut s = Px(0);
1771        for col in &mut self.columns {
1772            if col.width <= Px(0) {
1773                // collapsed or never measured (no cells)
1774                continue;
1775            }
1776            x += s;
1777            col.x = x;
1778            s = spacing.column;
1779            x += col.width;
1780        }
1781        let mut y = Px(0);
1782        let mut s = Px(0);
1783        for row in &mut self.rows {
1784            if row.height <= Px(0) {
1785                // collapsed or never measured (no cells)
1786                continue;
1787            }
1788            y += s;
1789            row.y = y;
1790            s = spacing.row;
1791            y += row.height;
1792        }
1793
1794        let max_width = constraints.x.max().unwrap_or(Px::MAX);
1795        if max_width > Px(0) && x > max_width {
1796            // width overflow
1797
1798            let max_height = constraints.y.max().unwrap_or(Px::MAX);
1799            if y < max_height && self.columns.iter().any(|c| c.meta.is_default()) && self.rows.iter().all(|r| r.meta.is_default()) {
1800                // height has space to grow
1801                // AND has at least one column that can still change width
1802                // AND all rows can still change height
1803
1804                // find cell minimum width
1805                cells.for_each_child(|i, cell| {
1806                    let cell_info = cell::CellInfo::get_wgt(cell);
1807                    if cell_info.column_span > 1 || cell_info.row_span > 1 {
1808                        return; // continue;
1809                    }
1810
1811                    let cell_info = cell_info.actual(i, columns_len);
1812                    let col = &mut self.columns[cell_info.column];
1813
1814                    if col.meta.is_default() {
1815                        let row = &mut self.rows[cell_info.row];
1816                        debug_assert!(row.meta.is_default());
1817
1818                        // get cell minimum width (0 max constraint means collapse so we give it at least one pixel)
1819                        let min_w_size = LAYOUT.with_constraints(
1820                            PxConstraints2d::new_range(col.min_width, col.min_width.max(Px(1)), row.min_height, row.max_height)
1821                                .with_inner(true, true),
1822                            || cell.measure(wm),
1823                        );
1824
1825                        col.min_width = col.min_width.max(min_w_size.width);
1826                    }
1827                });
1828
1829                // starting with all default columns at col.min_width, distribute the available space proportionate to
1830                // the "give" that is `col.width - col.min_width`
1831
1832                // grid width if all default sized columns are set to min_width
1833                let mut min_width = Px(0);
1834                let mut s = Px(0);
1835                for col in &self.columns {
1836                    if col.width <= Px(0) {
1837                        continue;
1838                    }
1839                    min_width += s;
1840                    s = spacing.column;
1841                    min_width += if col.meta.is_default() { col.min_width } else { col.width };
1842                }
1843                let min_width = min_width;
1844
1845                // sum total of default sized columns "give"
1846                let total_give: Px = self
1847                    .columns
1848                    .iter()
1849                    .filter(|c| c.meta.is_default())
1850                    .map(|c| (c.width - c.min_width).max(Px(0)))
1851                    .sum();
1852
1853                // available grid growth
1854                let available_width = max_width - min_width;
1855
1856                if available_width > Px(0) && total_give > Px(0) {
1857                    // proportionally distribute the available growth width
1858                    // columns with a large "give" get more space
1859                    let available_width = available_width.0 as f32;
1860                    let total_give = total_give.0 as f32;
1861                    for col in &mut self.columns {
1862                        if col.meta.is_default() {
1863                            let give = (col.width - col.min_width).max(Px(0)).0 as f32;
1864                            let share = available_width * (give / total_give);
1865                            col.width = col.min_width + Px(share as i32);
1866                        }
1867                    }
1868                } else {
1869                    // sum of mins already overflows or default sized columns have no give,
1870                    // just collapse everything to minimums
1871                    for col in &mut self.columns {
1872                        if col.meta.is_default() {
1873                            col.width = col.min_width;
1874                        }
1875                    }
1876                }
1877
1878                // measure with final column widths to find final row heights
1879                for row in &mut self.rows {
1880                    row.height = row.min_height;
1881                }
1882                cells.for_each_child(|i, cell| {
1883                    let cell_info = cell::CellInfo::get_wgt(cell);
1884                    if cell_info.column_span > 1 || cell_info.row_span > 1 {
1885                        return; // continue;
1886                    }
1887
1888                    let cell_info = cell_info.actual(i, columns_len);
1889                    let col = &mut self.columns[cell_info.column];
1890
1891                    if col.meta.is_default() {
1892                        let row = &mut self.rows[cell_info.row];
1893                        let height = LAYOUT
1894                            .with_constraints(
1895                                PxConstraints2d::new_range(col.width, col.width, row.min_height, row.max_height),
1896                                || cell.measure(wm),
1897                            )
1898                            .height;
1899
1900                        row.height = row.height.max(height.clamp(row.min_height, row.max_height));
1901                    }
1902                });
1903
1904                // compute column&row offsets again
1905                x = Px(0);
1906                let mut s = Px(0);
1907                for col in &mut self.columns {
1908                    if col.width <= Px(0) {
1909                        continue;
1910                    }
1911                    x += s;
1912                    col.x = x;
1913                    s = spacing.column;
1914                    x += col.width;
1915                }
1916                y = Px(0);
1917                let mut s = Px(0);
1918                for row in &mut self.rows {
1919                    if row.height <= Px(0) {
1920                        continue;
1921                    }
1922                    y += s;
1923                    row.y = y;
1924                    s = spacing.row;
1925                    y += row.height;
1926                }
1927            }
1928        }
1929
1930        (spacing, PxSize::new(x.max(Px(0)), y.max(Px(0))))
1931    }
1932}
1933
1934/// [[columns, auto_columns], [rows, auto_rows], cells]
1935type GridChildren = UiNode;
1936struct GridChildrenMut<'a>(&'a mut GridChildren);
1937impl<'a> GridChildrenMut<'a> {
1938    fn children(&mut self) -> &mut UiVec {
1939        self.0.downcast_mut().unwrap()
1940    }
1941
1942    fn all_columns_node(&mut self) -> &mut UiNode {
1943        &mut self.children()[0]
1944    }
1945    fn all_columns(&mut self) -> &mut ChainList {
1946        self.all_columns_node().downcast_mut().unwrap()
1947    }
1948    fn auto_columns(&mut self) -> &mut UiVec {
1949        self.all_columns().0[1].downcast_mut().unwrap()
1950    }
1951
1952    fn all_rows_node(&mut self) -> &mut UiNode {
1953        &mut self.children()[1]
1954    }
1955    fn all_rows(&mut self) -> &mut ChainList {
1956        self.all_rows_node().downcast_mut().unwrap()
1957    }
1958    fn auto_rows(&mut self) -> &mut UiVec {
1959        self.all_rows().0[1].downcast_mut().unwrap()
1960    }
1961
1962    fn cells(&mut self) -> &mut PanelList {
1963        self.children()[2].downcast_mut().unwrap()
1964    }
1965}