fyrox_ui/
grid.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Grid widget is able to position children widgets into a grid of specifically sized rows and columns. See
22//! [`Grid`] doc for more info and usage examples.
23
24#![warn(missing_docs)]
25
26use crate::{
27    core::{
28        algebra::Vector2, log::Log, math::Rect, pool::Handle, reflect::prelude::*,
29        type_traits::prelude::*, uuid_provider, variable::InheritableVariable, visitor::prelude::*,
30    },
31    define_constructor,
32    draw::{CommandTexture, Draw, DrawingContext},
33    message::{MessageDirection, UiMessage},
34    widget::{Widget, WidgetBuilder},
35    BuildContext, Control, UiNode, UserInterface,
36};
37use core::f32;
38
39use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
40use fyrox_graph::BaseSceneGraph;
41use std::{
42    cell::RefCell,
43    ops::{Deref, DerefMut},
44};
45use strum_macros::{AsRefStr, EnumString, VariantNames};
46
47/// A set of messages that can be used to modify [`Grid`] widget state.
48#[derive(Debug, PartialEq, Clone)]
49pub enum GridMessage {
50    /// Sets new rows for the grid widget.
51    Rows(Vec<Row>),
52    /// Sets new columns for the grid widget.
53    Columns(Vec<Column>),
54    /// Sets whether the grid should draw its border or not.
55    DrawBorder(bool),
56    /// Sets new border thickness for the grid.
57    BorderThickness(f32),
58}
59
60impl GridMessage {
61    define_constructor!(
62        /// Creates a new [`Self::Rows`] message.
63        GridMessage:Rows => fn rows(Vec<Row>), layout: false
64    );
65
66    define_constructor!(
67        /// Creates a new [`Self::Columns`] message.
68        GridMessage:Columns => fn columns(Vec<Column>), layout: false
69    );
70
71    define_constructor!(
72        /// Creates a new [`Self::DrawBorder`] message.
73        GridMessage:DrawBorder => fn draw_border(bool), layout: false
74    );
75
76    define_constructor!(
77        /// Creates a new [`Self::BorderThickness`] message.
78        GridMessage:BorderThickness => fn border_thickness(f32), layout: false
79    );
80}
81
82/// Size mode defines how grid's dimension (see [`GridDimension`]) will behave on layout step.
83#[derive(
84    Clone, Copy, PartialEq, Eq, Debug, Reflect, Visit, Default, AsRefStr, EnumString, VariantNames,
85)]
86pub enum SizeMode {
87    /// The desired size of this dimension must be provided in advance,
88    /// and it will always be rendered with exactly that size, regardless of what nodes it contains.
89    #[default]
90    Strict,
91    /// The desired size of this dimension is the maximum of the desired sizes of all the nodes when
92    /// they are measured with infinite available size.
93    Auto,
94    /// The size of this dimension is determined by subtracting the desired size of the other rows/columns
95    /// from the total available size, if the available size is finite.
96    /// If the total available size is infinite, then Stretch is equivalent to Auto.
97    Stretch,
98}
99
100uuid_provider!(SizeMode = "9c5dfbce-5df2-4a7f-8c57-c4473743a718");
101
102/// Grid dimension defines sizing rules and constraints for [`Grid`]'s rows and columns.
103#[derive(Clone, Copy, PartialEq, Debug, Reflect, Visit, Default)]
104pub struct GridDimension {
105    /// Current size mode of the dimension.
106    pub size_mode: SizeMode,
107    /// Desired size of the dimension. This must be supplied if [`SizeMode::Strict`],
108    /// and it is automatically calculated if [`SizeMode::Auto`].
109    /// If [`SizeMode::Stretch`]. this represents the size of the dimension before excess space is added.
110    pub desired_size: f32,
111    /// Measured size of the dimension. It could be considered as "output" parameter of the dimension
112    /// that will be filled after measurement layout step. It is used to calculate the grid's desired size.
113    pub actual_size: f32,
114    /// Local position along the axis of the dimension after arrangement step.
115    pub location: f32,
116    /// The number of children in this dimension that still need to be measured before the size is known.
117    /// For Auto rows and columns this is initially the number of nodes in that row or column,
118    /// and then it is reduced as nodes are measured.
119    /// This is zero for all non-Auto rows and columns.
120    #[visit(skip)]
121    #[reflect(hidden)]
122    unmeasured_node_count: usize,
123}
124
125uuid_provider!(GridDimension = "5e894900-c14a-4eb6-acb9-1636efead4b4");
126
127impl GridDimension {
128    /// Generic constructor for [`GridDimension`].
129    pub fn generic(size_mode: SizeMode, desired_size: f32) -> Self {
130        Self {
131            size_mode,
132            desired_size,
133            actual_size: 0.0,
134            location: 0.0,
135            unmeasured_node_count: 0,
136        }
137    }
138
139    /// Creates new [`GridDimension`] with [`SizeMode::Strict`] and the specified size constraint.
140    pub fn strict(desired_size: f32) -> Self {
141        Self::generic(SizeMode::Strict, desired_size)
142    }
143
144    /// Creates new [`GridDimension`] with [`SizeMode::Stretch`].
145    pub fn stretch() -> Self {
146        Self::generic(SizeMode::Stretch, 0.0)
147    }
148
149    /// Creates new [`GridDimension`] with [`SizeMode::Auto`].
150    pub fn auto() -> Self {
151        Self::generic(SizeMode::Auto, 0.0)
152    }
153
154    fn update_size(&mut self, node_size: f32, available_size: f32) {
155        match self.size_mode {
156            SizeMode::Strict => (),
157            SizeMode::Auto => {
158                self.desired_size = self.desired_size.max(node_size);
159                self.actual_size = self.desired_size;
160            }
161            SizeMode::Stretch => {
162                if available_size.is_finite() {
163                    self.actual_size = self.desired_size + available_size;
164                } else {
165                    self.actual_size = node_size;
166                }
167            }
168        }
169    }
170}
171
172/// Type alias for grid columns.
173pub type Column = GridDimension;
174
175/// Type alias for grid rows.
176pub type Row = GridDimension;
177
178/// Grids are one of several methods to position multiple widgets in relation to each other. A Grid widget, as the name
179/// implies, is able to position children widgets into a grid of specifically sized rows and columns.
180///
181/// Here is a simple example that positions several text widgets into a 2 by 2 grid:
182///
183/// ```rust,no_run
184/// # use fyrox_ui::{
185/// #     UiNode, core::pool::Handle,
186/// #     BuildContext,
187/// #     widget::WidgetBuilder,
188/// #     text::TextBuilder,
189/// #     grid::{GridBuilder, GridDimension},
190/// # };
191/// fn create_text_grid(ctx: &mut BuildContext) -> Handle<UiNode> {
192///     GridBuilder::new(
193///         WidgetBuilder::new()
194///             .with_child(
195///                 TextBuilder::new(WidgetBuilder::new())
196///                     .with_text("top left ")
197///                     .build(ctx),
198///             )
199///             .with_child(
200///                 TextBuilder::new(WidgetBuilder::new().on_column(1))
201///                     .with_text(" top right")
202///                     .build(ctx),
203///             )
204///             .with_child(
205///                 TextBuilder::new(WidgetBuilder::new().on_row(1))
206///                     .with_text("bottom left ")
207///                     .build(ctx),
208///             )
209///             .with_child(
210///                 TextBuilder::new(WidgetBuilder::new().on_row(1).on_column(1))
211///                     .with_text(" bottom right")
212///                     .build(ctx),
213///             ),
214///     )
215///     .add_row(GridDimension::auto())
216///     .add_row(GridDimension::auto())
217///     .add_column(GridDimension::auto())
218///     .add_column(GridDimension::auto())
219///     .build(ctx)
220/// }
221/// ```
222///
223/// As with other UI widgets, Grids are created via the [`GridBuilder`] struct. Each widget whose position should be controlled
224/// by the Grid should be added as a child of the [`GridBuilder`]'s base widget.
225///
226/// You then need to tell each child what row and column it belongs to via the [`WidgetBuilder::on_column`] and [`WidgetBuilder::on_row`]
227/// functions of their base widget. By default, all children will be placed into row 0, column 0.
228///
229/// After that you need to provide sizing constraints for each row and column to the [`GridBuilder`] by using the [`GridBuilder::add_row`]
230/// and [`GridBuilder::add_column`] functions while providing a [`GridDimension`] instance to the call. [`GridDimension`] can be
231/// constructed with the following functions:
232///
233/// * [`GridDimension::auto`] - Sizes the row or column so it's just large enough to fit the largest child's size.
234/// * [`GridDimension::stretch`] - Stretches the row or column to fill the parent's available space, if multiple rows or
235/// columns have this option the size is evenly distributed between them.
236/// * [`GridDimension::strict`] - Sets the row or column to be exactly the given value of pixels long. So a row will only
237/// be the given number of pixels wide, while a column will be that many pixels tall.
238///
239/// You can add any number of rows and columns to a grid widget, and each grid cell does **not** need to have a UI widget
240/// in it to be valid. For example you can add a column and set it to a specific size via strict to provide spacing between
241/// two other columns.
242#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
243#[reflect(derived_type = "UiNode")]
244pub struct Grid {
245    /// Base widget of the grid.
246    pub widget: Widget,
247    /// A set of rows of the grid.
248    pub rows: InheritableVariable<RefCell<Vec<Row>>>,
249    /// A set of columns of the grid.
250    pub columns: InheritableVariable<RefCell<Vec<Column>>>,
251    /// Defines whether to draw grid's border or not. It could be useful for debugging purposes.
252    pub draw_border: InheritableVariable<bool>,
253    /// Defines border thickness when `draw_border` is on.
254    pub border_thickness: InheritableVariable<f32>,
255    /// Current set of cells of the grid.
256    #[visit(skip)]
257    #[reflect(hidden)]
258    pub cells: RefCell<Vec<Cell>>,
259    /// A set of four groups, where each group contains cell indices. It is used for measurement
260    /// purposes to group the cells in specific way, so it can be measured in the correct order
261    /// later.
262    #[visit(skip)]
263    #[reflect(hidden)]
264    pub groups: RefCell<[Vec<usize>; 4]>,
265}
266
267impl ConstructorProvider<UiNode, UserInterface> for Grid {
268    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
269        GraphNodeConstructor::new::<Self>()
270            .with_variant("Grid", |ui| {
271                GridBuilder::new(WidgetBuilder::new().with_name("Grid"))
272                    .build(&mut ui.build_ctx())
273                    .into()
274            })
275            .with_group("Layout")
276    }
277}
278
279crate::define_widget_deref!(Grid);
280
281/// Cell of the grid, that contains additional information for layout purposes. It does not have any
282/// particular use outside of grid's internals.
283#[derive(Clone, Debug)]
284pub struct Cell {
285    /// A set of nodes of the cell.
286    pub nodes: Vec<Handle<UiNode>>,
287    /// Vertical location of the cell (row number).
288    pub row_index: usize,
289    /// Horizontal location of the cell (column number).
290    pub column_index: usize,
291}
292
293/// ```text
294///                Strict   Auto   Stretch
295///               +-------+-------+-------+
296///               |       |       |       |
297///        Strict |   0   |   0   |   2   |
298///               |       |       |       |
299///               +-------+-------+-------+
300///               |       |       |       |
301///          Auto |   0   |   0   |   2   |
302///               |       |       |       |
303///               +-------+-------+-------+
304///               |       |       |       |
305///       Stretch |   3   |   1   |   3   |
306///               |       |       |       |
307///               +-------+-------+-------+
308/// ```
309/// Group 0 represents all nodes with no stretch. They can be measured without needing any
310/// desired size information from other nodes, and so they are always measured first.
311///
312/// Group 1 is special because it contains all the remaining auto-width nodes
313/// after group 0 has been measured, and group 1 may blocked from being measured
314/// due to group 2 not yet being measured to provide the desired size of the
315/// remaining auto rows.
316///
317/// In order to allow measurement to proceed in that situation, group 1 may be forced
318/// to measure despite not yet knowing its true vertical available size.
319/// The width information gained from the measurement of group 1 makes it possible to
320/// measure group 2, and then group 1 will be measured a second time to get its
321/// correct desired height. Group 1 is the only group that is ever measured twice.
322fn group_index(row_size_mode: SizeMode, column_size_mode: SizeMode) -> usize {
323    match (row_size_mode, column_size_mode) {
324        (SizeMode::Strict, SizeMode::Strict)
325        | (SizeMode::Strict, SizeMode::Auto)
326        | (SizeMode::Auto, SizeMode::Strict)
327        | (SizeMode::Auto, SizeMode::Auto) => 0,
328        (SizeMode::Stretch, SizeMode::Auto) => 1,
329        (SizeMode::Strict, SizeMode::Stretch) | (SizeMode::Auto, SizeMode::Stretch) => 2,
330        (SizeMode::Stretch, SizeMode::Strict) | (SizeMode::Stretch, SizeMode::Stretch) => 3,
331    }
332}
333
334fn choose_constraint(dimension: &GridDimension, available_size: f32) -> f32 {
335    match dimension.size_mode {
336        // Strict always has a constraint of its desired size.
337        SizeMode::Strict => dimension.desired_size,
338        // For Stretch rows and columns, the available size is whatever size is not used up
339        // by the other rows and columns.
340        // First we give the node the desired size, which is most likely zero for a Stretch row/column,
341        // then we expand it to include the available size.
342        SizeMode::Stretch => dimension.desired_size + available_size,
343        // Auto means being free to choose whatever size the widget pleases.
344        // If the constraint were set to `available_size` then the widget might choose
345        // to use all of that size and crowd out all other cells of the grid.
346        // A constraint of infinity encourages the node to pick a more reasonable size.
347        SizeMode::Auto => f32::INFINITY,
348    }
349}
350
351fn calc_total_size_of_non_stretch_dims(dims: &[GridDimension]) -> Option<f32> {
352    if dims.iter().all(|d| d.size_mode != SizeMode::Stretch) {
353        // If there are no stretch rows/columns, then the value we return will never be used.
354        Some(0.0) // Arbitrarily choose 0.0, but it should not matter.
355    } else if dims.iter().all(|d| d.unmeasured_node_count == 0) {
356        // We have at least one stretch, so seriously calculate the size
357        // This requires that all the autos be already measured.
358        Some(dims.iter().map(|d| d.desired_size).sum())
359    } else {
360        // We have at least one stretch but not all the autos are measured
361        // so we fail.
362        None
363    }
364}
365
366fn count_stretch_dims(dims: &[GridDimension]) -> usize {
367    let mut stretch_sized_dims = 0;
368    for dim in dims.iter() {
369        if dim.size_mode == SizeMode::Stretch {
370            stretch_sized_dims += 1;
371        }
372    }
373    stretch_sized_dims
374}
375
376fn calc_avg_size_for_stretch_dim(
377    dims: &RefCell<Vec<GridDimension>>,
378    available_size: f32,
379) -> Option<f32> {
380    if available_size.is_infinite() {
381        // If we have limitless available size, then short-circuit to avoid the possibility
382        // of returning None due to missing Auto measurements. Measuring Auto nodes does not matter
383        // when available_size is infinite, and returning None might force an unnecessary double-measure.
384        return Some(available_size);
385    }
386    let dims = dims.borrow();
387    let stretch_sized_dims = count_stretch_dims(&dims);
388    if stretch_sized_dims > 0 {
389        let rest_size = available_size - calc_total_size_of_non_stretch_dims(&dims)?;
390        Some(rest_size / stretch_sized_dims as f32)
391    } else {
392        // If there are no stretch nodes in this row/column, then this result will never be used.
393        Some(0.0) // Choose 0.0 arbitrarily.
394    }
395}
396
397fn arrange_dims(dims: &mut [GridDimension], final_size: f32) {
398    // Every row/column has a desired size, so summing all the desired sizes is correct.
399    // Strict rows/columns have their desired size set when building the grid.
400    // Auto rows/columns are calculated in the measure step.
401    // Stretch rows/columns default to zero.
402    let preset_width: f32 = dims.iter().map(|d| d.desired_size).sum();
403
404    let stretch_count = count_stretch_dims(dims);
405    let avg_stretch = if stretch_count > 0 {
406        (final_size - preset_width) / stretch_count as f32
407    } else {
408        // Since stretch_count is zero, this value will never be used.
409        0.0
410    };
411
412    let mut location = 0.0;
413    for dim in dims.iter_mut() {
414        dim.location = location;
415        dim.actual_size = match dim.size_mode {
416            SizeMode::Strict | SizeMode::Auto => dim.desired_size,
417            SizeMode::Stretch => dim.desired_size + avg_stretch,
418        };
419        location += dim.actual_size;
420    }
421}
422
423uuid_provider!(Grid = "98ce15e2-bd62-497d-a37b-9b1cb4a1918c");
424
425impl Grid {
426    fn initialize_measure(&self, ui: &UserInterface) {
427        self.calc_needed_measurements(ui);
428
429        let mut groups = self.groups.borrow_mut();
430        for group in groups.iter_mut() {
431            group.clear();
432        }
433
434        let mut cells = self.cells.borrow_mut();
435        cells.clear();
436
437        let rows = self.rows.borrow();
438        let columns = self.columns.borrow();
439        for (column_index, column) in columns.iter().enumerate() {
440            for (row_index, row) in rows.iter().enumerate() {
441                groups[group_index(row.size_mode, column.size_mode)].push(cells.len());
442
443                cells.push(Cell {
444                    nodes: self
445                        .children()
446                        .iter()
447                        .copied()
448                        .filter(|&c| {
449                            let Some(child_ref) = ui.try_get_node(c) else {
450                                return false;
451                            };
452                            child_ref.row() == row_index && child_ref.column() == column_index
453                        })
454                        .collect(),
455                    row_index,
456                    column_index,
457                })
458            }
459        }
460    }
461    fn calc_needed_measurements(&self, ui: &UserInterface) {
462        let mut rows = self.rows.borrow_mut();
463        let mut cols = self.columns.borrow_mut();
464        for dim in rows.iter_mut().chain(cols.iter_mut()) {
465            dim.unmeasured_node_count = 0;
466            match dim.size_mode {
467                SizeMode::Auto => dim.desired_size = 0.0,
468                SizeMode::Strict => dim.actual_size = dim.desired_size,
469                SizeMode::Stretch => (),
470            }
471        }
472        for handle in self.children() {
473            let Some(node) = ui.try_get_node(*handle) else {
474                continue;
475            };
476            let Some(row) = rows.get_mut(node.row()) else {
477                Log::err(format!(
478                    "Node row out of bounds: {} row:{}, column:{}",
479                    Reflect::type_name(node),
480                    node.row(),
481                    node.column()
482                ));
483                continue;
484            };
485            let Some(col) = cols.get_mut(node.column()) else {
486                Log::err(format!(
487                    "Node column out of bounds: {} row:{}, column:{}",
488                    Reflect::type_name(node),
489                    node.row(),
490                    node.column()
491                ));
492                continue;
493            };
494            if col.size_mode == SizeMode::Auto {
495                col.unmeasured_node_count += 1
496            }
497            if row.size_mode == SizeMode::Auto {
498                row.unmeasured_node_count += 1
499            }
500        }
501    }
502    fn measure_width_and_height(
503        &self,
504        child: Handle<UiNode>,
505        ui: &UserInterface,
506        available_size: Vector2<f32>,
507        measure_width: bool,
508        measure_height: bool,
509    ) {
510        let Some(node) = ui.try_get_node(child) else {
511            return;
512        };
513        let mut rows = self.rows.borrow_mut();
514        let mut cols = self.columns.borrow_mut();
515        let Some(row) = rows.get_mut(node.row()) else {
516            return;
517        };
518        let Some(col) = cols.get_mut(node.column()) else {
519            return;
520        };
521        let constraint = Vector2::new(
522            choose_constraint(col, available_size.x),
523            choose_constraint(row, available_size.y),
524        );
525        ui.measure_node(child, constraint);
526        if measure_width {
527            col.update_size(node.desired_size().x, available_size.x);
528            if col.size_mode == SizeMode::Auto {
529                col.unmeasured_node_count -= 1;
530            }
531        }
532        if measure_height {
533            row.update_size(node.desired_size().y, available_size.y);
534            if row.size_mode == SizeMode::Auto {
535                row.unmeasured_node_count -= 1;
536            }
537        }
538    }
539    fn measure_group_width(
540        &self,
541        group: &[usize],
542        ui: &UserInterface,
543        available_size: Vector2<f32>,
544    ) {
545        let cells = self.cells.borrow();
546        for cell in group.iter().map(|&i| &cells[i]) {
547            for n in cell.nodes.iter() {
548                self.measure_width_and_height(*n, ui, available_size, true, false);
549            }
550        }
551    }
552    fn measure_group_height(
553        &self,
554        group: &[usize],
555        ui: &UserInterface,
556        available_size: Vector2<f32>,
557    ) {
558        let cells = self.cells.borrow();
559        for cell in group.iter().map(|&i| &cells[i]) {
560            for n in cell.nodes.iter() {
561                self.measure_width_and_height(*n, ui, available_size, false, true);
562            }
563        }
564    }
565    fn measure_group(&self, group: &[usize], ui: &UserInterface, available_size: Vector2<f32>) {
566        let cells = self.cells.borrow();
567        for cell in group.iter().map(|&i| &cells[i]) {
568            for n in cell.nodes.iter() {
569                self.measure_width_and_height(*n, ui, available_size, true, true);
570            }
571        }
572    }
573}
574
575impl Control for Grid {
576    fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
577        // In case of no rows or columns, grid acts like default panel.
578        if self.columns.borrow().is_empty() || self.rows.borrow().is_empty() {
579            return self.widget.measure_override(ui, available_size);
580        }
581
582        self.initialize_measure(ui);
583
584        let groups = self.groups.borrow_mut();
585
586        // Start by measuring all the nodes with no stretch in either dimension: group 0
587        self.measure_group(&groups[0], ui, available_size);
588
589        if let Some(space_y) = calc_avg_size_for_stretch_dim(&self.rows, available_size.y) {
590            // Measuring group 0 was enough to allow us to calculate the needed stretch along the height of the grid,
591            // so use that stretch to measure group 1 (auto width, stretch height).
592            self.measure_group(&groups[1], ui, Vector2::new(available_size.x, space_y));
593            // Measuring group 0 and group 1 guarantees that we have measured all the auto-width nodes, so this is safe to unwrap.
594            let space_x = calc_avg_size_for_stretch_dim(&self.columns, available_size.x).unwrap();
595            // Use the calculated horizontal stretch to measure all the remaining nodes.
596            self.measure_group(&groups[2], ui, Vector2::new(space_x, available_size.y));
597            self.measure_group(&groups[3], ui, Vector2::new(space_x, space_y));
598        } else if let Some(space_x) = calc_avg_size_for_stretch_dim(&self.columns, available_size.x)
599        {
600            // We were unable to calculate the vertical stretch, but we can calculate the horizontal stretch,
601            // so use the horizontal stretch to measure group 2 (stretch width, strict/auto height).
602            // We know that group 1 is empty, since group 1 has auto width and we have not yet measured group 1.
603            self.measure_group(&groups[2], ui, Vector2::new(space_x, available_size.y));
604            // Measuring group 0 and group 2 guarantees that we have measured all the auto-height nodes, so this is safe to unwrap.
605            let space_y = calc_avg_size_for_stretch_dim(&self.rows, available_size.y).unwrap();
606            // Use the calculated vertical stretch to measure the remaining nodes.
607            self.measure_group(&groups[3], ui, Vector2::new(space_x, space_y));
608        } else {
609            // We could not calculate either the vertical stretch or the horizontal stretch.
610            // The only horizontal autos we have not measured are in group 1 (auto width, stretch height),
611            // so we are forced to measure group 1 as it if had auto height, just so it can provide its width to its column.
612            // The desired height provided by this measurement is ignored.
613            self.measure_group_width(&groups[1], ui, Vector2::new(f32::INFINITY, f32::INFINITY));
614            // Measuring group 0 and group 1 guarantees that we have measured all the auto-width nodes, so this is safe to unwrap.
615            let space_x = calc_avg_size_for_stretch_dim(&self.columns, available_size.x).unwrap();
616            // Use the calculated horizontal stretch to measure group 2 (stretch width, strict/auto height).
617            self.measure_group(&groups[2], ui, Vector2::new(space_x, available_size.y));
618            // Measuring group 0 and group 2 guarantees that we have measured all the auto-height nodes, so this is safe to unwrap.
619            let space_y = calc_avg_size_for_stretch_dim(&self.rows, available_size.y).unwrap();
620            // Now that we finally have the vertical stretch amount, we can properly measure group 1 (auto width, stretch height).
621            // This is the only time we measure a node twice. The first time was just to discover the width.
622            // This measurement is just for height, now that we can give the node the true available vertical size.
623            self.measure_group_height(&groups[1], ui, Vector2::new(available_size.x, space_y));
624            self.measure_group(&groups[3], ui, Vector2::new(space_x, space_y));
625        }
626
627        let desired_size = Vector2::<f32>::new(
628            self.columns.borrow().iter().map(|c| c.actual_size).sum(),
629            self.rows.borrow().iter().map(|r| r.actual_size).sum(),
630        );
631        desired_size
632    }
633
634    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
635        let mut columns = self.columns.borrow_mut();
636        let mut rows = self.rows.borrow_mut();
637
638        if columns.is_empty() || rows.is_empty() {
639            let rect = Rect::new(0.0, 0.0, final_size.x, final_size.y);
640            for child_handle in self.widget.children() {
641                ui.arrange_node(*child_handle, &rect);
642            }
643            return final_size;
644        }
645
646        arrange_dims(&mut columns, final_size.x);
647        arrange_dims(&mut rows, final_size.y);
648
649        for child_handle in self.widget.children() {
650            let child = ui.nodes.borrow(*child_handle);
651            if let Some(column) = columns.get(child.column()) {
652                if let Some(row) = rows.get(child.row()) {
653                    ui.arrange_node(
654                        *child_handle,
655                        &Rect::new(
656                            column.location,
657                            row.location,
658                            column.actual_size,
659                            row.actual_size,
660                        ),
661                    );
662                }
663            }
664        }
665
666        final_size
667    }
668
669    fn draw(&self, drawing_context: &mut DrawingContext) {
670        if *self.draw_border {
671            let bounds = self.widget.bounding_rect();
672
673            let left_top = Vector2::new(bounds.x(), bounds.y());
674            let right_top = Vector2::new(bounds.x() + bounds.w(), bounds.y());
675            let right_bottom = Vector2::new(bounds.x() + bounds.w(), bounds.y() + bounds.h());
676            let left_bottom = Vector2::new(bounds.x(), bounds.y() + bounds.h());
677
678            drawing_context.push_line(left_top, right_top, *self.border_thickness);
679            drawing_context.push_line(right_top, right_bottom, *self.border_thickness);
680            drawing_context.push_line(right_bottom, left_bottom, *self.border_thickness);
681            drawing_context.push_line(left_bottom, left_top, *self.border_thickness);
682
683            for column in self.columns.borrow().iter() {
684                let a = Vector2::new(bounds.x() + column.location, bounds.y());
685                let b = Vector2::new(bounds.x() + column.location, bounds.y() + bounds.h());
686                drawing_context.push_line(a, b, *self.border_thickness);
687            }
688            for row in self.rows.borrow().iter() {
689                let a = Vector2::new(bounds.x(), bounds.y() + row.location);
690                let b = Vector2::new(bounds.x() + bounds.w(), bounds.y() + row.location);
691                drawing_context.push_line(a, b, *self.border_thickness);
692            }
693
694            drawing_context.commit(
695                self.clip_bounds(),
696                self.widget.foreground(),
697                CommandTexture::None,
698                &self.material,
699                None,
700            );
701        }
702    }
703
704    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
705        self.widget.handle_routed_message(ui, message);
706
707        if let Some(msg) = message.data::<GridMessage>() {
708            if message.direction() == MessageDirection::ToWidget
709                && message.destination() == self.handle
710            {
711                match msg {
712                    GridMessage::Rows(rows) => {
713                        if &*self.rows.borrow() != rows {
714                            self.rows
715                                .set_value_and_mark_modified(RefCell::new(rows.clone()));
716                            self.invalidate_layout();
717                        }
718                    }
719                    GridMessage::Columns(columns) => {
720                        if &*self.columns.borrow() != columns {
721                            self.columns
722                                .set_value_and_mark_modified(RefCell::new(columns.clone()));
723                            self.invalidate_layout();
724                        }
725                    }
726                    GridMessage::DrawBorder(draw_border) => {
727                        self.draw_border.set_value_and_mark_modified(*draw_border);
728                    }
729                    GridMessage::BorderThickness(border_thickness) => {
730                        self.border_thickness
731                            .set_value_and_mark_modified(*border_thickness);
732                    }
733                }
734            }
735        }
736    }
737}
738
739/// Grid builder creates [`Grid`] instances and adds it to the user interface.
740pub struct GridBuilder {
741    widget_builder: WidgetBuilder,
742    rows: Vec<Row>,
743    columns: Vec<Column>,
744    draw_border: bool,
745    border_thickness: f32,
746}
747
748impl GridBuilder {
749    /// Creates new grid builder with the base widget builder.
750    pub fn new(widget_builder: WidgetBuilder) -> Self {
751        Self {
752            widget_builder,
753            rows: Vec::new(),
754            columns: Vec::new(),
755            draw_border: false,
756            border_thickness: 1.0,
757        }
758    }
759
760    /// Adds a new row to the grid builder. Number of rows is unlimited.
761    pub fn add_row(mut self, row: Row) -> Self {
762        self.rows.push(row);
763        self
764    }
765
766    /// Adds a new column to the grid builder. Number of columns is unlimited.
767    pub fn add_column(mut self, column: Column) -> Self {
768        self.columns.push(column);
769        self
770    }
771
772    /// Adds a set of rows to the grid builder. Number of rows is unlimited.
773    pub fn add_rows(mut self, mut rows: Vec<Row>) -> Self {
774        self.rows.append(&mut rows);
775        self
776    }
777
778    /// Adds a set of columnds to the grid builder. Number of columnds is unlimited.
779    pub fn add_columns(mut self, mut columns: Vec<Column>) -> Self {
780        self.columns.append(&mut columns);
781        self
782    }
783
784    /// Specifies whether the grid should draw its border or not.
785    pub fn draw_border(mut self, value: bool) -> Self {
786        self.draw_border = value;
787        self
788    }
789
790    /// Specifies grid's border thickness.
791    pub fn with_border_thickness(mut self, value: f32) -> Self {
792        self.border_thickness = value;
793        self
794    }
795
796    /// Creates new [`Grid`] widget instance and adds it to the user interface.
797    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
798        let grid = Grid {
799            widget: self.widget_builder.build(ctx),
800            rows: RefCell::new(self.rows).into(),
801            columns: RefCell::new(self.columns).into(),
802            draw_border: self.draw_border.into(),
803            border_thickness: self.border_thickness.into(),
804            cells: Default::default(),
805            groups: Default::default(),
806        };
807        ctx.add_node(UiNode::new(grid))
808    }
809}
810
811#[cfg(test)]
812mod test {
813    use crate::grid::GridBuilder;
814    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
815
816    #[test]
817    fn test_deletion() {
818        test_widget_deletion(|ctx| GridBuilder::new(WidgetBuilder::new()).build(ctx));
819    }
820}