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