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#![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
22pub mod column;
24
25#[doc(inline)]
26pub use row::Row;
27
28pub mod row;
30
31#[doc(inline)]
32pub use cell::Cell;
33
34pub mod cell;
36
37mod layout;
38
39#[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#[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#[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#[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#[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#[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#[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
207pub 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 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 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 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(); }
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(); }
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#[derive(Clone, Debug)]
483#[non_exhaustive]
484pub struct AutoGrowFnArgs {
485 pub mode: AutoGrowMode,
487 pub index: usize,
489}
490impl AutoGrowFnArgs {
491 pub fn new(mode: AutoGrowMode, index: usize) -> Self {
493 Self { mode, index }
494 }
495}
496
497#[derive(Clone, Copy, PartialEq, Eq)]
501pub enum AutoGrowMode {
502 Columns(u32),
504 Rows(u32),
506}
507impl AutoGrowMode {
508 pub const fn disabled() -> Self {
510 Self::Rows(0)
511 }
512
513 pub const fn columns() -> Self {
515 Self::Columns(u32::MAX)
516 }
517
518 pub const fn rows() -> Self {
520 Self::Rows(u32::MAX)
521 }
522
523 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}