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#![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#[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#[property(CHILD, capture, widget_impl(Grid))]
54pub fn cells(cells: impl UiNodeList) {}
55
56#[property(CHILD, capture, widget_impl(Grid))]
87pub fn columns(cells: impl UiNodeList) {}
88
89#[property(CHILD, capture, widget_impl(Grid))]
95pub fn rows(cells: impl UiNodeList) {}
96
97#[property(CONTEXT, capture, default(WidgetFn::nil()), widget_impl(Grid))]
105pub fn auto_grow_fn(auto_grow: impl IntoVar<WidgetFn<AutoGrowFnArgs>>) {}
106
107#[property(CONTEXT, capture, default(AutoGrowMode::rows()), widget_impl(Grid))]
113pub fn auto_grow_mode(mode: impl IntoVar<AutoGrowMode>) {}
114
115#[property(LAYOUT, capture, default(GridSpacing::default()), widget_impl(Grid))]
117pub fn spacing(spacing: impl IntoVar<GridSpacing>) {}
118
119pub 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 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 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 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(); }
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(); }
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#[derive(Clone, Debug)]
389pub struct AutoGrowFnArgs {
390 pub mode: AutoGrowMode,
392 pub index: usize,
394}
395
396#[derive(Clone, Copy, PartialEq, Eq)]
400pub enum AutoGrowMode {
401 Columns(u32),
403 Rows(u32),
405}
406impl AutoGrowMode {
407 pub const fn disabled() -> Self {
409 Self::Rows(0)
410 }
411
412 pub const fn columns() -> Self {
414 Self::Columns(u32::MAX)
415 }
416
417 pub const fn rows() -> Self {
419 Self::Rows(u32::MAX)
420 }
421
422 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
448pub mod column {
450 use super::*;
451
452 #[widget($crate::Column {
464 ($width:expr) => {
465 width = $width;
466 };
467 })]
468 pub struct Column(WidgetBase);
469 impl Column {
470 widget_impl! {
471 pub max_width(max: impl IntoVar<Length>);
473
474 pub min_width(min: impl IntoVar<Length>);
476
477 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 pub(super) static ref INDEX_ID: StateId<(usize, usize)>;
492 }
493
494 #[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 #[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 #[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 #[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 #[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 #[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 #[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
591pub mod row {
593 use super::*;
594
595 #[widget($crate::Row {
607 ($height:expr) => {
608 height = $height;
609 };
610 })]
611 pub struct Row(WidgetBase);
612 impl Row {
613 widget_impl! {
614 pub max_height(max: impl IntoVar<Length>);
616
617 pub min_height(max: impl IntoVar<Length>);
619
620 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 pub(super) static ref INDEX_ID: StateId<(usize, usize)>;
635 }
636
637 #[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 #[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 #[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 #[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 #[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 #[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 #[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
734pub mod cell {
736 use super::*;
737
738 #[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 #[derive(Clone, Copy, Debug)]
758 pub struct CellInfo {
759 pub column: usize,
763
764 pub column_span: usize,
768
769 pub row: usize,
773
774 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 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 pub fn get() -> Self {
809 WIDGET.get_state(*INFO_ID).unwrap_or_default()
810 }
811
812 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 pub static ref INFO_ID: StateId<CellInfo>;
823 }
824
825 #[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 #[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 #[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 #[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 #[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 #[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 pub const AT_AUTO: (usize, usize) = (usize::MAX, usize::MAX);
964}
965
966#[derive(Clone, Copy)]
967struct ColRowMeta(f32);
968impl ColRowMeta {
969 fn is_default(self) -> bool {
971 self.0.is_sign_negative() && self.0.is_infinite()
972 }
973
974 fn is_leftover(self) -> Option<Factor> {
976 if self.0 >= 0.0 { Some(Factor(self.0)) } else { None }
977 }
978
979 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 fn update_entries(&mut self, children: &mut GridChildren, auto_mode: AutoGrowMode, auto_grow_fn: &impl Var<WidgetFn<AutoGrowFnArgs>>) {
1063 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 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 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 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 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; }
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 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 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 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 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 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 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 if total_factor.0.is_infinite() {
1374 total_factor = Factor(0.0);
1375
1376 if max_factor.is_infinite() {
1377 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 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 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 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 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 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 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 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 if total_factor.0.is_infinite() {
1503 total_factor = Factor(0.0);
1504
1505 if max_factor.is_infinite() {
1506 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 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 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 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 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 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 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 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; }
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 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 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 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
1669fn 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
1677type GridChildren = Vec<BoxedUiNodeList>;