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