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;
15use zng_wgt::prelude::*;
16use zng_wgt_access::{AccessRole, access_role};
17use zng_wgt_size_offset::*;
18
19#[doc(inline)]
20pub use column::Column;
21
22/// Column widget and properties.
23pub mod column;
24
25#[doc(inline)]
26pub use row::Row;
27
28/// Row widget and properties.
29pub mod row;
30
31#[doc(inline)]
32pub use cell::Cell;
33
34/// Cell widget and properties.
35pub mod cell;
36
37mod layout;
38
39/// Grid layout with cells of variable sizes.
40#[widget($crate::Grid)]
41pub struct Grid(WidgetBase);
42impl Grid {
43    fn widget_intrinsic(&mut self) {
44        self.widget_builder().push_build_action(|w| {
45            let child = node(
46                w.capture_ui_node_or_nil(property_id!(Self::cells)),
47                w.capture_ui_node_or_nil(property_id!(Self::columns)),
48                w.capture_ui_node_or_nil(property_id!(Self::rows)),
49                w.capture_var_or_else(property_id!(Self::auto_grow_fn), WidgetFn::nil),
50                w.capture_var_or_else(property_id!(Self::auto_grow_mode), AutoGrowMode::rows),
51                w.capture_var_or_default(property_id!(Self::spacing)),
52            );
53            w.set_child(child);
54        });
55
56        widget_set! {
57            self;
58
59            access_role = AccessRole::Grid;
60        }
61    }
62}
63
64/// Cell widget items.
65///
66/// Cells can select their own column, row, column-span and row-span using the properties in the [`Cell!`] widget.
67/// Note that you don't need to use that widget, only the [`cell`] properties.
68///
69/// If the column or row index is set to [`usize::MAX`] the widget is positioned using the
70/// logical index *i*, the column *i % columns* and the row *i / columns*.
71///
72/// [`Cell!`]: struct@Cell
73#[property(CHILD, widget_impl(Grid))]
74pub fn cells(wgt: &mut WidgetBuilding, cells: impl IntoUiNode) {
75    let _ = cells;
76    wgt.expect_property_capture();
77}
78
79/// Column definitions.
80///
81/// Columns are defined by widgets, the column widget width defines the width of cells assigned to the column,
82/// the [`Column!`] widget is recommended, but you can use any widget to define a column. The column widget is rendered
83/// as the background of the column region, behind cells and row backgrounds.
84///
85/// ### Layout Modes
86///
87/// The grid uses the [`WIDGET_SIZE`] value to select one of three layout modes for columns:
88///
89/// * *Default*, used for columns that do not set width or set it to [`Length::Default`].
90/// * *Exact*, used for columns that set the width to an unit that is exact or only depends on the grid context.
91/// * *Leftover*, used for columns that set width to a [`lft`] value.
92///
93/// The column (and row) measure follows these steps:
94///
95/// 1 - All *Exact* column widgets are measured, their final width defines the column width.
96/// 2 - All *Default* sized column widgets are measured twice to find its min and max widths.
97/// 3 - All cell widgets with span `1` in *Default* columns are measured to find the widest cell width. That defines the column width.
98/// 4 - All *Leftover* columns receive the proportional leftover grid width for each.
99///
100/// So given the columns `200 | 1.lft() | 1.lft()` and grid width of `1000` with spacing `5` the final widths are `200 | 395 | 395`,
101/// for `200 + 5 + 395 + 5 + 395 = 1000`.
102///
103/// #### Overflow Recovery
104///
105/// In case the columns width overflows and all rows are *Default* height and some columns are *Default* width these recovery steps are taken:
106///
107/// 1 - All cell widgets with span `1` in *Default* columns are measured to find the minimum width they can wrap down too.
108/// 2 - All *Default* columns are sized to the minimum width plus the extra space now available, proportionally divided.
109/// 3 - All cells widgets affected are measured again to define the row heights.
110///
111/// The proportion of each *Default* is the difference between the previous measured width with the new minimum, this is very similar to
112/// the CSS table layout, except the previous measured width is used instead of another measure pass to find the cells maximum width.
113///
114/// ### Notes
115///
116/// Note that the column widget is not the parent of the cells that match it.
117/// Properties like `padding` and `align` only affect the column visual, not the cells, similarly contextual properties like `text_color`
118/// don't affect the cells.
119///
120/// Note that the *Default* layout mode scales with the cell count, the other modes scale with the column count. This
121/// is fine for small grids (<100 cells) or for simple cell widgets, but for larger grids you should really consider using
122/// an *Exact* width or *Leftover* proportion, specially if the grid width is bounded.
123///
124/// [`Column!`]: struct@Column
125/// [`lft`]: zng_layout::unit::LengthUnits::lft
126/// [`WIDGET_SIZE`]: zng_wgt_size_offset::WIDGET_SIZE
127/// [`Length::Default`]: zng_layout::unit::Length::Default
128#[property(CHILD, widget_impl(Grid))]
129pub fn columns(wgt: &mut WidgetBuilding, columns: impl IntoUiNode) {
130    let _ = columns;
131    wgt.expect_property_capture();
132}
133
134/// Row definitions.
135///
136/// Rows are defined by widgets, the row widget height defines the height of cells assigned to the row, the [`Row!`] widget is recommended,
137/// 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
138/// of column backgrounds.
139///
140/// ## Layout Modes
141///
142/// The grid uses the [`WIDGET_SIZE`] value to select one of three layout modes for rows:
143///
144/// * *Default*, used for rows that do not set height or set it to [`Length::Default`].
145/// * *Exact*, used for rows that set the height to an unit that is exact or only depends on the grid context.
146/// * *Leftover*, used for rows that set height to a [`lft`] value.
147///
148/// The row measure follows the same steps as [`columns`], the only difference is that there is no
149/// overflow recovery for row heights exceeding the available height.
150///
151/// ### Notes
152///
153/// Note that the row widget is not the parent of the cells that match it.
154/// Properties like `padding` and `align` only affect the row visual, not the cells, similarly contextual properties like `text_color`
155/// don't affect the cells.
156///
157/// Note that the *Default* layout mode scales with the cell count, the other modes scale with the row count. This has less impact
158/// for rows, but you should consider setting a fixed row height for larger grids. Also note that you can define the [`auto_grow_fn`]
159/// instead of manually adding rows. With fixed heights a data table of up to 1000 rows with simple text cells should have good performance.
160///
161/// For massive data tables consider a paginating layout with a separate grid instance per *page*, the page grids don't need to be actually
162/// presented as pages, you can use lazy loading and a simple stack layout to seamless virtualize data loading and presentation.
163///
164/// [`columns`]: fn@columns
165/// [`auto_grow_fn`]: fn@auto_grow_fn
166/// [`Row!`]: struct@Row
167/// [`lft`]: zng_layout::unit::LengthUnits::lft
168/// [`WIDGET_SIZE`]: zng_wgt_size_offset::WIDGET_SIZE
169/// [`Length::Default`]: zng_layout::unit::Length::Default
170#[property(CHILD, widget_impl(Grid))]
171pub fn rows(wgt: &mut WidgetBuilding, rows: impl IntoUiNode) {
172    let _ = rows;
173    wgt.expect_property_capture();
174}
175
176/// Widget function used when new rows or columns are needed to cover a cell placement.
177///
178/// The function is used according to the [`auto_grow_mode`]. Note that *imaginary* rows or columns are used if
179/// the function is [`WidgetFn::nil`].
180///
181/// [`auto_grow_mode`]: fn@auto_grow_mode
182/// [`WidgetFn::nil`]: zng_wgt::prelude::WidgetFn::nil
183#[property(CONTEXT, default(WidgetFn::nil()), widget_impl(Grid))]
184pub fn auto_grow_fn(wgt: &mut WidgetBuilding, auto_grow: impl IntoVar<WidgetFn<AutoGrowFnArgs>>) {
185    let _ = auto_grow;
186    wgt.expect_property_capture();
187}
188
189/// Defines the direction the grid auto-grows and the maximum inclusive index that can be covered by auto-generated columns or rows.
190/// 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
191/// cell is also outside this max it is *collapsed*.
192///
193/// Is `AutoGrowMode::rows() by default.
194#[property(CONTEXT, default(AutoGrowMode::rows()), widget_impl(Grid))]
195pub fn auto_grow_mode(wgt: &mut WidgetBuilding, mode: impl IntoVar<AutoGrowMode>) {
196    let _ = mode;
197    wgt.expect_property_capture();
198}
199
200/// Space in-between cells.
201#[property(LAYOUT, default(GridSpacing::default()), widget_impl(Grid))]
202pub fn spacing(wgt: &mut WidgetBuilding, spacing: impl IntoVar<GridSpacing>) {
203    let _ = spacing;
204    wgt.expect_property_capture();
205}
206
207/// Grid node.
208///
209/// Can be used directly to layout widgets without declaring a grid widget info. This node is the child
210/// of the `Grid!` widget.
211pub fn node(
212    cells: impl IntoUiNode,
213    columns: impl IntoUiNode,
214    rows: impl IntoUiNode,
215    auto_grow_fn: impl IntoVar<WidgetFn<AutoGrowFnArgs>>,
216    auto_grow_mode: impl IntoVar<AutoGrowMode>,
217    spacing: impl IntoVar<GridSpacing>,
218) -> UiNode {
219    let auto_columns = ui_vec![];
220    let auto_rows = ui_vec![];
221    let children = ui_vec![
222        ChainList(ui_vec![columns.into_node().into_list(), auto_columns]),
223        ChainList(ui_vec![rows.into_node().into_list(), auto_rows]),
224        PanelList::new(cells),
225    ];
226    let spacing = spacing.into_var();
227    let auto_grow_fn = auto_grow_fn.into_var();
228    let auto_grow_mode = auto_grow_mode.into_var();
229
230    let mut grid = layout::GridLayout::default();
231    let mut is_measured = false;
232    let mut last_layout = LayoutMetrics::new(1.fct(), PxSize::zero(), Px(0));
233
234    match_node(children, move |c, op| match op {
235        UiNodeOp::Init => {
236            WIDGET.sub_var(&auto_grow_fn).sub_var(&auto_grow_mode).sub_var_layout(&spacing);
237            c.init();
238            grid.update_entries(c.node(), auto_grow_mode.get(), &auto_grow_fn);
239        }
240        UiNodeOp::Deinit => {
241            c.deinit();
242            layout::GridChildrenMut(c.node()).auto_columns().clear();
243            layout::GridChildrenMut(c.node()).auto_rows().clear();
244            is_measured = false;
245        }
246        UiNodeOp::Update { updates } => {
247            let mut any = false;
248            c.update_list(updates, &mut any);
249
250            if auto_grow_fn.is_new() || auto_grow_mode.is_new() {
251                for mut auto in layout::GridChildrenMut(c.node()).auto_columns().drain(..) {
252                    auto.deinit();
253                }
254                for mut auto in layout::GridChildrenMut(c.node()).auto_rows().drain(..) {
255                    auto.deinit();
256                }
257                any = true;
258            }
259            if any {
260                grid.update_entries(c.node(), auto_grow_mode.get(), &auto_grow_fn);
261                WIDGET.layout();
262            }
263        }
264        UiNodeOp::Measure { wm, desired_size } => {
265            c.delegated();
266
267            let constraints = LAYOUT.constraints().inner();
268
269            *desired_size = if let Some(size) = constraints.fill_or_exact() {
270                size
271            } else {
272                is_measured = true;
273                let s = grid.grid_layout(wm, c.node(), &spacing).1;
274                constraints.clamp_size(s)
275            };
276        }
277        UiNodeOp::Layout { wl, final_size } => {
278            c.delegated();
279            is_measured = false;
280            last_layout = LAYOUT.metrics();
281
282            let (spacing, grid_size) = grid.grid_layout(&mut wl.to_measure(None), c.node(), &spacing);
283            let constraints = last_layout.constraints();
284
285            if grid.is_collapse() {
286                wl.collapse_descendants();
287                *final_size = constraints.fill_or_exact().unwrap_or_default();
288                return;
289            }
290
291            let mut children = layout::GridChildrenMut(c.node());
292            let mut children = children.children().iter_mut();
293            let columns = children.next().unwrap();
294            let rows = children.next().unwrap();
295            let cells = children.next().unwrap();
296            let cells: &mut PanelList = cells.downcast_mut().unwrap();
297
298            let grid = &grid;
299
300            // layout columns
301            let _ = columns.layout_list(
302                wl,
303                |ci, col, wl| {
304                    let info = grid.columns[ci];
305                    LAYOUT.with_constraints(PxConstraints2d::new_exact(info.width, grid_size.height), || col.layout(wl))
306                },
307                |_, _| PxSize::zero(),
308            );
309            // layout rows
310            let _ = rows.layout_list(
311                wl,
312                |ri, row, wl| {
313                    let info = grid.rows[ri];
314                    LAYOUT.with_constraints(PxConstraints2d::new_exact(grid_size.width, info.height), || row.layout(wl))
315                },
316                |_, _| PxSize::zero(),
317            );
318            // layout and translate cells
319            let cells_offset = columns.children_len() + rows.children_len();
320
321            cells.layout_list(
322                wl,
323                |i, cell, o, wl| {
324                    let cell_info = cell::CellInfo::get_wgt(cell).actual(i, grid.columns.len());
325
326                    if cell_info.column >= grid.columns.len() || cell_info.row >= grid.rows.len() {
327                        wl.collapse_child(cells_offset + i);
328                        return PxSize::zero(); // continue;
329                    }
330
331                    let cell_offset = PxVector::new(grid.columns[cell_info.column].x, grid.rows[cell_info.row].y);
332                    let mut cell_size = PxSize::zero();
333
334                    for col in cell_info.column..(cell_info.column + cell_info.column_span).min(grid.columns.len()) {
335                        if grid.columns[col].width != Px(0) {
336                            cell_size.width += grid.columns[col].width + spacing.column;
337                        }
338                    }
339                    cell_size.width -= spacing.column;
340
341                    for row in cell_info.row..(cell_info.row + cell_info.row_span).min(grid.rows.len()) {
342                        if grid.rows[row].height != Px(0) {
343                            cell_size.height += grid.rows[row].height + spacing.row;
344                        }
345                    }
346                    cell_size.height -= spacing.row;
347
348                    if cell_size.is_empty() {
349                        wl.collapse_child(cells_offset + i);
350                        return PxSize::zero(); // continue;
351                    }
352
353                    let (_, define_ref_frame) =
354                        LAYOUT.with_constraints(PxConstraints2d::new_exact_size(cell_size), || wl.with_child(|wl| cell.layout(wl)));
355                    o.child_offset = cell_offset;
356                    o.define_reference_frame = define_ref_frame;
357
358                    cell_size
359                },
360                |_, _| PxSize::zero(),
361            );
362            cells.commit_data().request_render();
363
364            *final_size = constraints.inner().fill_size_or(grid_size);
365        }
366        UiNodeOp::Render { frame } => {
367            c.delegated();
368
369            if mem::take(&mut is_measured) {
370                LAYOUT.with_context(last_layout.clone(), || {
371                    let _ = grid.grid_layout(&mut WidgetMeasure::new_reuse(None), c.node(), &spacing);
372                });
373            }
374
375            let grid = &grid;
376
377            if grid.is_collapse() {
378                return;
379            }
380
381            let mut children = layout::GridChildrenMut(c.node());
382            let mut children = children.children().iter_mut();
383            let columns = children.next().unwrap();
384            let rows = children.next().unwrap();
385            let cells: &mut PanelList = children.next().unwrap().downcast_mut().unwrap();
386            let offset_key = cells.offset_key();
387
388            columns.for_each_child(|i, child| {
389                let offset = PxVector::new(grid.columns[i].x, Px(0));
390                frame.push_reference_frame(
391                    (offset_key, i as u32).into(),
392                    FrameValue::Value(offset.into()),
393                    true,
394                    true,
395                    |frame| {
396                        child.render(frame);
397                    },
398                );
399            });
400            let i_extra = columns.children_len();
401            rows.for_each_child(|i, child| {
402                let offset = PxVector::new(Px(0), grid.rows[i].y);
403                frame.push_reference_frame(
404                    (offset_key, (i + i_extra) as u32).into(),
405                    FrameValue::Value(offset.into()),
406                    true,
407                    true,
408                    |frame| {
409                        child.render(frame);
410                    },
411                );
412            });
413            let i_extra = i_extra + rows.children_len();
414            cells.for_each_z_sorted(|i, child, data| {
415                if data.define_reference_frame {
416                    frame.push_reference_frame(
417                        (offset_key, (i + i_extra) as u32).into(),
418                        FrameValue::Value(data.child_offset.into()),
419                        true,
420                        true,
421                        |frame| {
422                            child.render(frame);
423                        },
424                    );
425                } else {
426                    frame.push_child(data.child_offset, |frame| child.render(frame));
427                }
428            });
429        }
430        UiNodeOp::RenderUpdate { update } => {
431            c.delegated();
432
433            if mem::take(&mut is_measured) {
434                LAYOUT.with_context(last_layout.clone(), || {
435                    let _ = grid.grid_layout(&mut WidgetMeasure::new_reuse(None), c.node(), &spacing);
436                });
437            }
438
439            let grid = &grid;
440
441            if grid.is_collapse() {
442                return;
443            }
444
445            let mut children = layout::GridChildrenMut(c.node());
446            let mut children = children.children().iter_mut();
447            let columns = children.next().unwrap();
448            let rows = children.next().unwrap();
449            let cells: &mut PanelList = children.next().unwrap().downcast_mut().unwrap();
450
451            columns.for_each_child(|i, child| {
452                let offset = PxVector::new(grid.columns[i].x, Px(0));
453                update.with_transform_value(&offset.into(), |update| {
454                    child.render_update(update);
455                });
456            });
457            rows.for_each_child(|i, child| {
458                let offset = PxVector::new(Px(0), grid.rows[i].y);
459                update.with_transform_value(&offset.into(), |update| {
460                    child.render_update(update);
461                });
462            });
463            cells.for_each_child(|_, child, data| {
464                if data.define_reference_frame {
465                    update.with_transform_value(&data.child_offset.into(), |update| {
466                        child.render_update(update);
467                    });
468                } else {
469                    update.with_child(data.child_offset, |update| {
470                        child.render_update(update);
471                    })
472                }
473            })
474        }
475        _ => {}
476    })
477}
478
479/// Arguments for [`auto_grow_fn`].
480///
481/// [`auto_grow_fn`]: fn@auto_grow_fn
482#[derive(Clone, Debug)]
483#[non_exhaustive]
484pub struct AutoGrowFnArgs {
485    /// Auto-grow direction.
486    pub mode: AutoGrowMode,
487    /// Column index.
488    pub index: usize,
489}
490impl AutoGrowFnArgs {
491    /// New args.
492    pub fn new(mode: AutoGrowMode, index: usize) -> Self {
493        Self { mode, index }
494    }
495}
496
497/// Grid auto-grow direction.
498///
499/// The associated value is the maximum columns or rows that are allowed in the grid.
500#[derive(Clone, Copy, PartialEq, Eq)]
501pub enum AutoGrowMode {
502    /// Auto generate columns.
503    Columns(u32),
504    /// Auto generate rows.
505    Rows(u32),
506}
507impl AutoGrowMode {
508    /// Value that does not generate any new row or column.
509    pub const fn disabled() -> Self {
510        Self::Rows(0)
511    }
512
513    /// Columns, not specific maximum limit.
514    pub const fn columns() -> Self {
515        Self::Columns(u32::MAX)
516    }
517
518    /// Rows, not specific maximum limit.
519    pub const fn rows() -> Self {
520        Self::Rows(u32::MAX)
521    }
522
523    /// Set the maximum columns or rows allowed.
524    pub fn with_limit(self, limit: u32) -> Self {
525        match self {
526            AutoGrowMode::Columns(_) => AutoGrowMode::Columns(limit),
527            AutoGrowMode::Rows(_) => AutoGrowMode::Rows(limit),
528        }
529    }
530}
531impl fmt::Debug for AutoGrowMode {
532    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
533        if f.alternate() {
534            write!(f, "AutoGrowMode::")?;
535        }
536        match self {
537            AutoGrowMode::Rows(0) => write!(f, "disabled()"),
538            AutoGrowMode::Columns(u32::MAX) => write!(f, "Columns(MAX)"),
539            AutoGrowMode::Rows(u32::MAX) => write!(f, "Rows(MAX)"),
540            AutoGrowMode::Columns(l) => write!(f, "Columns({l})"),
541            AutoGrowMode::Rows(l) => write!(f, "Rows({l})"),
542        }
543    }
544}