zng_wgt_grid/
lib.rs

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