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}