├── .gitignore ├── Cargo.toml ├── src ├── lib.rs ├── helper.rs ├── sizing.rs ├── grid.rs └── builder.rs ├── CHANGELOG.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /other 3 | Cargo.lock 4 | desktop.ini 5 | /grid-test 6 | /videos 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui_grid" 3 | version = "0.5.2" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | description = "Create grid-based layouts for egui" 7 | authors = ["Mythitorium"] 8 | readme = "README.md" 9 | exclude = ["other/*", "grid-test/*"] 10 | repository = "https://github.com/mythitorium/egui-grid" 11 | documentation = "https://docs.rs/egui_grid" 12 | 13 | [dependencies] 14 | egui = "^0.28" 15 | egui_extras = "^0.28" 16 | # eframe = "0.27.0" # debug & testing 17 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is my attempt at taking a stab at the *'egui complex layout'* problem, 2 | //! Built to provide dynamic grid layouts for [egui](https://github.com/emilk/egui). 3 | //! 4 | //! Relies on structs and enums from both [`egui`](https://github.com/emilk/egui) and [`egui_extras`](https://crates.io/crates/egui_extras). 5 | //! 6 | //! This crate includes 2 items, [`GridBuilder`] and [`Grid`], which are used to create grids 7 | //! with behavior similar to the StripBuilder found in eui_extras, though being much more compact and with additional features. 8 | //! 9 | mod sizing; 10 | mod grid; 11 | mod helper; 12 | mod builder; 13 | 14 | pub use crate::grid::Grid; 15 | pub use crate::builder::GridBuilder; 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [0.5.2] - 2024-07-26 6 | 7 | ### Changed 8 | 9 | - Update to `egui` 0.28 10 | - Update to `egui_extras` 0.28 11 | 12 | ## [0.5.1] - 2024-05-24 13 | 14 | ### Changed 15 | 16 | - Update to `egui` 0.27.2 17 | - Update to `egui_extras` 0.27.2 18 | 19 | ## [0.5.0] - 2024-05-24 20 | 21 | ### Changed 22 | 23 | - Update to `egui` 0.27.0 24 | - Update to `egui_extras` 0.27.0 25 | 26 | ## [0.4.0] - 2023-10-04 27 | 28 | ### Changed 29 | 30 | - Update to `egui` 0.24.1 31 | - Update to `egui_extras` 0.24.1 32 | 33 | ## [0.3.0] - 2023-10-04 34 | 35 | ### Changed 36 | 37 | - Update to `egui` 0.23.0 38 | - Update to `egui_extras` 0.23.0 39 | 40 | ## [0.2.0] - 2023-05-29 41 | 42 | ### Fixed 43 | 44 | - Fixed [`clip()`](https://docs.rs/egui_grid/latest/egui_grid/struct.GridBuilder.html#method.clip) clipping too much 45 | 46 | ### Changed 47 | 48 | - Update to `egui` 0.22.0 49 | - Update to `egui_extras` 0.22.0 50 | 51 | ## [0.1.0] - 2023-04-12 52 | 53 | - Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 mythitorium 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/helper.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | builder::{Cell, Row}, 3 | sizing::Sizing, 4 | }; 5 | use egui::{Pos2, Rect, Vec2}; 6 | 7 | // Moved code to functions so the into_real_cells method doesn't look as cluttered 8 | pub(crate) fn row_set_as_f32(rows: &[Row], spacing: &f32, whole: &f32) -> Vec { 9 | let mut row_sizes = Vec::new(); 10 | for row in rows.iter() { 11 | row_sizes.push(row.size); 12 | } 13 | Sizing::from(row_sizes).to_lengths(*whole, *spacing) 14 | } 15 | 16 | pub(crate) fn cell_set_as_f32(cells: &[Cell], spacing: &f32, whole: &f32) -> Vec { 17 | let mut row_sizes = Vec::new(); 18 | for row in cells.iter() { 19 | row_sizes.push(row.size); 20 | } 21 | Sizing::from(row_sizes).to_lengths(*whole, *spacing) 22 | } 23 | 24 | // This effectively reflects the rectangle on a line of symmetry where y=-x 25 | // input for the rect being reflected, focal for the offset to the center of symmetry 26 | pub(crate) fn reflect(input: Rect, focal: Pos2) -> Rect { 27 | let offset = input.min - focal; 28 | Rect { 29 | min: Pos2::new(offset.y + focal.x, offset.x + focal.y), 30 | max: Pos2::new( 31 | offset.y + focal.x + input.height(), 32 | offset.x + focal.y + input.width(), 33 | ), 34 | } 35 | } 36 | 37 | pub(crate) fn swap_spacing(spacing: Vec2, swap: bool) -> Vec2 { 38 | if swap { 39 | Vec2 { 40 | x: spacing.y, 41 | y: spacing.x, 42 | } 43 | } else { 44 | spacing 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/sizing.rs: -------------------------------------------------------------------------------- 1 | // I took this from egui's source 2 | // It turns Size into actual tangible numbers and I really really wasn't going to go about remaking this myself 3 | 4 | use egui_extras::Size; 5 | 6 | #[derive(Clone, Default)] 7 | pub struct Sizing { 8 | pub(crate) sizes: Vec, 9 | } 10 | 11 | impl Sizing { 12 | // threw a warning, commented it out 13 | //pub fn add(&mut self, size: Size) { 14 | // self.sizes.push(size); 15 | //} 16 | 17 | pub fn to_lengths(&self, length: f32, spacing: f32) -> Vec { 18 | if self.sizes.is_empty() { 19 | return vec![]; 20 | } 21 | 22 | let mut remainders = 0; 23 | let sum_non_remainder = self 24 | .sizes 25 | .iter() 26 | .map(|&size| match size { 27 | Size::Absolute { initial, .. } => initial, 28 | Size::Relative { fraction, range } => { 29 | assert!((0.0..=1.0).contains(&fraction)); 30 | range.clamp(length * fraction) 31 | } 32 | Size::Remainder { .. } => { 33 | remainders += 1; 34 | 0.0 35 | } 36 | }) 37 | .sum::() 38 | + spacing * (self.sizes.len() - 1) as f32; 39 | 40 | let avg_remainder_length = if remainders == 0 { 41 | 0.0 42 | } else { 43 | let mut remainder_length = length - sum_non_remainder; 44 | let avg_remainder_length = 0.0f32.max(remainder_length / remainders as f32).floor(); 45 | self.sizes.iter().for_each(|&size| { 46 | if let Size::Remainder { range } = size { 47 | if avg_remainder_length < range.min { 48 | remainder_length -= range.min; 49 | remainders -= 1; 50 | } 51 | } 52 | }); 53 | if remainders > 0 { 54 | 0.0f32.max(remainder_length / remainders as f32) 55 | } else { 56 | 0.0 57 | } 58 | }; 59 | 60 | self.sizes 61 | .iter() 62 | .map(|&size| match size { 63 | Size::Absolute { initial, .. } => initial, 64 | Size::Relative { fraction, range } => range.clamp(length * fraction), 65 | Size::Remainder { range } => range.clamp(avg_remainder_length), 66 | }) 67 | .collect() 68 | } 69 | } 70 | 71 | impl From> for Sizing { 72 | fn from(sizes: Vec) -> Self { 73 | Self { sizes } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/grid.rs: -------------------------------------------------------------------------------- 1 | use crate::builder::PureCell; 2 | use egui::{Pos2, Ui}; 3 | 4 | /// A collection of grid cells. 5 | /// 6 | /// Each cell has a fixed size. 7 | /// Cells are represented in order, as they were created (**Note**: see [`GridBuilder::rows_as_columns`](crate::builder::GridBuilder::rows_as_columns)). 8 | /// 9 | /// The cells of a nested grid will also be included in place of the cell that contained them 10 | /// (the cell holding the grid will not be represented & the nested cells will take the cell's place in line). 11 | /// 12 | pub struct Grid<'a, 'b> { 13 | ui: &'a mut Ui, 14 | cells: Vec, 15 | pointer: usize, 16 | bounds: &'b mut Pos2, 17 | } 18 | 19 | impl Grid<'_, '_> { 20 | pub(crate) fn new<'a>( 21 | ui: &'a mut Ui, 22 | cells: Vec, 23 | bounds: &'a mut Pos2, 24 | ) -> Grid<'a, 'a> { 25 | Grid { 26 | ui, 27 | cells, 28 | pointer: 0, 29 | bounds, 30 | } 31 | } 32 | 33 | /// Add contents to this cell 34 | pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) { 35 | if self.pointer > self.cells.len() - 1 { 36 | panic!( 37 | "Added more `cells` than were pre-allocated ({} pre-allocated)", 38 | self.cells.len() 39 | ); 40 | } 41 | 42 | let cell = &self.cells[self.pointer]; 43 | let cell_rect = cell.rect(); 44 | let cell_layout = cell.layout(); 45 | 46 | if cell_rect.max.y > self.bounds.y { 47 | self.bounds.y = cell_rect.max.y; 48 | } 49 | if cell_rect.max.x > self.bounds.x { 50 | self.bounds.x = cell_rect.max.x; 51 | } 52 | 53 | let mut child_ui = self.ui.child_ui(cell_rect, cell_layout, None); 54 | if cell.clip() { 55 | let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin); 56 | let margin = margin.min(0.5 * self.ui.spacing().item_spacing); 57 | let clip_rect = cell_rect.expand2(margin); 58 | child_ui.set_clip_rect(clip_rect.intersect(child_ui.clip_rect())); 59 | } 60 | add_contents(&mut child_ui); 61 | self.pointer += 1; 62 | } 63 | 64 | /// Populate this cell with nothing. It will still take up space in the grid, but will be empty. 65 | pub fn empty(&mut self) { 66 | if self.pointer > self.cells.len() - 1 { 67 | panic!( 68 | "Added more `cells` than were pre-allocated ({} pre-allocated)", 69 | self.cells.len() 70 | ); 71 | } 72 | 73 | let cell_rect = self.cells[self.pointer].rect(); 74 | 75 | if cell_rect.max.y > self.bounds.y { 76 | self.bounds.y = cell_rect.max.y; 77 | } 78 | if cell_rect.max.x > self.bounds.x { 79 | self.bounds.x = cell_rect.max.x; 80 | } 81 | 82 | self.pointer += 1; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egui_grid 2 | 3 | [![Latest version](https://img.shields.io/crates/v/egui_grid.svg)](https://crates.io/crates/egui_grid) 4 | [![Documentation](https://docs.rs/egui_grid/badge.svg)](https://docs.rs/egui_grid) 5 | 6 | Create dynamic grid layouts for [`egui`](https://github.com/emilk/egui). 7 | 8 | Grids are flexible, easy to create, with behavior similar to egui_extra's strip creation. They're compact and allow for more complex layouts using less indentation, 9 | with functionalities like nesting grids and aligning cells within a row. 10 | 11 | ## Installing 12 | 13 | Add this to your `Cargo.toml`: 14 | 15 | ```toml 16 | [dependencies] 17 | egui_grid = "0.5.2" 18 | ``` 19 | 20 | ## Example 21 | 22 | ``` rust 23 | // Quick example, by no means does it fully demo 24 | // how flexible building grids can be. 25 | use egui_grid::GridBuilder; 26 | use egui_extras::Size; 27 | 28 | GridBuilder::new() 29 | // Allocate a new row 30 | .new_row(Size::exact(200.0)) 31 | // Give this row a couple cells 32 | .cell(Size::exact(85.0)) 33 | .cell(Size::remainder()) 34 | // Allocate another row 35 | .new_row(Size::remainder()) 36 | // Batch method, allocate multiple cells at once 37 | .cells(Size::remainder(), 3) 38 | .show(ui, |mut grid| { 39 | // Cells are represented as they were allocated 40 | grid.cell(|ui| { 41 | ui.label("Top row, left cell"); 42 | }); 43 | grid.cell(|ui| { 44 | ui.label("Top row, right cell"); 45 | }); 46 | grid.cell(|ui| { 47 | ui.label("Bottom row, left cell"); 48 | }); 49 | grid.empty(); 50 | grid.cell(|ui| { 51 | ui.label("Bottom row, right cell"); 52 | }); 53 | }); 54 | ``` 55 | 56 | ### Grid Nesting Example 57 | 58 | ```rust 59 | // You can nest grids, allowing for complex layouts without much indentation 60 | use egui_grid::GridBuilder; 61 | use egui_extras::Size; 62 | 63 | // The grid which will be nested 64 | let nested_grid = GridBuilder::new() 65 | // 2 rows, with 1 cell each 66 | .new_row(Size::remainder()).cell(Size::remainder()) 67 | .new_row(Size::remainder()).cell(Size::remainder()); 68 | 69 | 70 | // The main grid, of which one cell will receive the nested grid 71 | GridBuilder::new() 72 | // One row with 3 cells 73 | .new_row(Size::remainder()) 74 | .cell(Size::remainder()) 75 | .cell(Size::remainder()) .nest(nested_grid) // Nesting the grid in the middle cell 76 | .cell(Size::remainder()) 77 | 78 | .show(ui, |mut grid| { 79 | // The nested grid replaces the cell it was nested in; 80 | // And the cells within that nested grid replace it in the order, too. 81 | // 82 | // So despite there being 5 cells allocated total 83 | // (2 from the nested grid and 3 from the main), only 4 exist. 84 | grid.cell(|ui| { 85 | ui.label("Left cell"); 86 | }); 87 | grid.cell(|ui| { 88 | ui.label("Nested grid, top cell"); 89 | }); 90 | grid.cell(|ui| { 91 | ui.label("Nested grid, bottom cell"); 92 | }); 93 | grid.cell(|ui| { 94 | ui.label("Right cell"); 95 | }); 96 | }); 97 | ``` 98 | 99 | ## Usage 100 | 101 | Check the [docs](https://docs.rs/egui_grid/latest/egui_grid/) for details and more info on usage. 102 | 103 | ## Issues 104 | 105 | Currently this is feature complete. Bug fixes, optimizations, and staying up to date with [egui](https://github.com/emilk/egui) releases are the only ways this crate will be expanding for the foreseeable future. 106 | 107 | While you are free to open an issue, contacting me (@mythit) on the [egui discord server](https://discord.gg/wdkZkEdXks) might be more worth your time. 108 | 109 | -------------------------------------------------------------------------------- /src/builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{grid::*, helper::*}; 2 | use egui::{Align, Layout, Margin, Pos2, Rect, Response, Sense, Ui, Vec2}; 3 | use egui_extras::Size; 4 | 5 | /// Builder for creating a new [`Grid`]. 6 | /// 7 | /// Used to create grid-based layouts. Uses egui_extra's [`Size`](https://docs.rs/egui_extras/latest/egui_extras/enum.Size.html) for specificizing the space taken up by rows & cells. 8 | /// 9 | /// In contrast to normal egui behavior, grid cells do not grow with its children! 10 | /// 11 | /// Allocate new rows using [`Self::new_row`], with the size given being what the row's cells will inherit. 12 | /// Then populate the row with cells using [`Self::cell`] or [`Self::cells`], each cell having it's own horizontal size and inheriting the size of the row it's being placed in. 13 | /// Since cells do not wrap, [`Self::new_row`] can be called again to allocate a new row which can be populated with more cells. 14 | /// 15 | /// Build the grid using [`Self::show`], and add it's contents to the ui using [`Grid::cell`]. 16 | /// Will panic if the number of cells called to display is more than the amount pre-allocated. 17 | /// 18 | /// One can customize how the grid gets built and how the cells are displayed using [`Self::rows_as_columns`], [`Self::spacing`], [`Self::align`], among others. 19 | /// 20 | /// ## Exmaple 21 | /// ``` 22 | /// use egui_grid::GridBuilder; 23 | /// use egui_extras::Size; 24 | /// 25 | /// GridBuilder::new() 26 | /// // Allocate a new row 27 | /// .new_row(Size::exact(200.0)) 28 | /// // Give this row a couple cells 29 | /// .cell(Size::exact(85.0)) 30 | /// .cell(Size::remainder()) 31 | /// // Allocate another row 32 | /// .new_row(Size::remainder()) 33 | /// // Batch method, allocate multiple cells at once 34 | /// .cells(Size::remainder(), 3) 35 | /// .show(ui, |mut grid| { 36 | /// // Cells are represented as they were allocated 37 | /// grid.cell(|ui| { 38 | /// ui.label("Top row, left cell"); 39 | /// }); 40 | /// grid.cell(|ui| { 41 | /// ui.label("Top row, right cell"); 42 | /// }); 43 | /// grid.cell(|ui| { 44 | /// ui.label("Bottom row, left cell"); 45 | /// }); 46 | /// grid.empty(); 47 | /// grid.cell(|ui| { 48 | /// ui.label("Bottom row, right cell"); 49 | /// }); 50 | /// }); 51 | /// ``` 52 | #[derive(Clone)] 53 | pub struct GridBuilder { 54 | units: Vec, 55 | spacing: Vec2, 56 | row_as_col: bool, 57 | creation_cache: Vec<(usize, usize)>, 58 | clip: bool, 59 | use_default_spacing: bool, 60 | default_layout: Layout, 61 | } 62 | 63 | impl Default for GridBuilder { 64 | fn default() -> Self { 65 | GridBuilder { 66 | units: Vec::new(), 67 | spacing: Vec2::ZERO, 68 | row_as_col: false, 69 | creation_cache: Vec::new(), 70 | clip: false, 71 | use_default_spacing: true, 72 | default_layout: Layout::default(), 73 | } 74 | } 75 | } 76 | 77 | impl GridBuilder { 78 | /// Create new grid builder. 79 | pub fn new() -> GridBuilder { 80 | GridBuilder::default() 81 | } 82 | 83 | /// Set cell spacing. By default spacing is 0 on both axis. 84 | /// Spacing will not effect the spacing of any nested grids. 85 | /// 86 | /// If left unset, the Ui's item spacing will be used instead. 87 | pub fn spacing(mut self, width: f32, height: f32) -> Self { 88 | self.spacing = Vec2 { 89 | x: width, 90 | y: height, 91 | }; 92 | self.use_default_spacing = false; 93 | self 94 | } 95 | 96 | /// Set cell spacing using a [`Vec2`](https://docs.rs/egui/latest/egui/struct.Vec2.html). 97 | /// 98 | /// If left unset, the Ui's item spacing will be used instead. 99 | pub fn spacing_vec2(mut self, spacing: Vec2) -> Self { 100 | self.spacing = spacing; 101 | self.use_default_spacing = false; 102 | self 103 | } 104 | 105 | /// Should we clip the contents of each cell? Default: `false`. 106 | /// 107 | /// If set to `true`, cells will hide whatever part(s) of any ui that spill outside of the cell's defined area. 108 | /// 109 | /// This setting will not propagate to nested grids. 110 | pub fn clip(mut self, clip: bool) -> Self { 111 | self.clip = clip; 112 | self 113 | } 114 | 115 | /// Allocate a new row with given [`Size`](https://docs.rs/egui_extras/latest/egui_extras/enum.Size.html). Rows are represented top-to-bottom. 116 | pub fn new_row(mut self, size: Size) -> Self { 117 | self.units.push(Row::new(size, Align::Min)); 118 | self 119 | } 120 | 121 | /// Allocate a new row with a given [`Size`](https://docs.rs/egui_extras/latest/egui_extras/enum.Size.html), with a custom Align for any cells it'll have. 122 | /// An align will not effect how cells are allocated, just which side of the row they'll align with, in the case they don't fill the entirety of a row. 123 | /// 124 | /// Default align for [`Self::new_row`] is [`Align::Min`](https://docs.rs/egui/latest/egui/enum.Align.html). 125 | pub fn new_row_align(mut self, size: Size, align: Align) -> Self { 126 | self.units.push(Row::new(size, align)); 127 | self 128 | } 129 | 130 | /// Set the cell [`Align`](https://docs.rs/egui/latest/egui/enum.Align.html) of the most recently allocated row. 131 | /// This will work regardless if the row has been populated with cells or not. 132 | /// 133 | /// Does nothing unless at least one row has been allocated. 134 | pub fn align(mut self, align: Align) -> Self { 135 | let len = self.units.len(); 136 | if len > 0 { 137 | self.units[len - 1].align(align); 138 | } 139 | self 140 | } 141 | 142 | /// Add a cell to the most recently allocated row. Cells are represented left-to-right. 143 | /// Does nothing unless at least one row has been allocated. 144 | pub fn cell(mut self, size: Size) -> Self { 145 | self.add_cells(size, 1, Margin::same(0.)); 146 | self 147 | } 148 | 149 | /// Add multiple cells all with the same size to the most recently allocated row. Cells are represented left-to-right. 150 | /// Does nothing unless at least one row has been allocated. 151 | pub fn cells(mut self, size: Size, amount: i32) -> Self { 152 | self.add_cells(size, amount, Margin::same(0.)); 153 | self 154 | } 155 | 156 | /// Give the most recently allocated cells a custom [`Margin`](https://docs.rs/egui/latest/egui/style/struct.Margin.html). 157 | /// Can be used after [`Self::cells`] to give multiple cells a margin at once. 158 | /// 159 | /// ## Example 160 | /// ``` 161 | /// let grid = GridBuilder::new() 162 | /// .new_row(Size::remainder()) 163 | /// // This 'cell' will have a custom margin 164 | /// .cell(Size::exact(100.0)) .with_margin(Margin::same(10.0)) 165 | /// // All 4 cells allocated here will have matching margins 166 | /// .cells(Size::exact(50.0), 4) .with_margin(Margin::same(6.0)) 167 | /// // This will overwrite the last 4 allocated, but not the first one 168 | /// .with_margin(Margin::symmetric(6.0, 4.0)); 169 | /// ``` 170 | pub fn with_margin(mut self, margin: Margin) -> Self { 171 | // the creation cache can only be bigger than one if cells and therefor rows have already been created. 172 | if !self.creation_cache.is_empty() { 173 | for item in self.creation_cache.iter() { 174 | self.units[item.0].cells[item.1].edit_margin(margin); 175 | } 176 | } 177 | self 178 | } 179 | 180 | /// Give the most recently allocated cells a custom [`Layout`](https://docs.rs/egui/latest/egui/struct.Layout.html). 181 | /// 182 | /// Behavior matches [`Self::with_margin`]. 183 | pub fn with_layout(mut self, layout: Layout) -> Self { 184 | if !self.creation_cache.is_empty() { 185 | for item in self.creation_cache.iter() { 186 | self.units[item.0].cells[item.1].edit_layout(layout); 187 | } 188 | } 189 | self 190 | } 191 | 192 | /// All cells allocated going forward will use this [`Layout`](https://docs.rs/egui/latest/egui/struct.Layout.html) as default. 193 | /// *Does not effect previously allocated cells*. 194 | /// 195 | /// This default will still be overridden by [`Self::with_layout`]. 196 | pub fn layout_standard(mut self, layout: Layout) -> Self { 197 | self.default_layout = layout; 198 | self 199 | } 200 | 201 | /// Nest a grid at the most recently allocated cell. 202 | /// Does nothing in the absence of any rows or the most recently allocated row being absent of any cells. 203 | /// 204 | /// ## Example 205 | /// ``` 206 | /// // A grid of 4 cells which all take up equal space 207 | /// let nested_grid = GridBuilder::new() 208 | /// .new_row(Size::remainder()).cells(Size::remainder(), 2) 209 | /// .new_row(Size::remainder()).cells(Size::remainder(), 2); 210 | /// 211 | /// let parent_grid = GridBuilder::new() 212 | /// .new_row(Size::remainder()) 213 | /// .cells(Size::remainder(), 2) 214 | /// // Despite being called after a batch cell allocation, 215 | /// // ONLY the last cell will have the grid nested 216 | /// .nest(nested_grid) 217 | /// .show(ui, |mut grid| { 218 | /// grid.cell(|ui| { 219 | /// ui.label("Left cell"); 220 | /// }); 221 | /// grid.cell(|ui| { 222 | /// ui.label("Nested cell top-left"); 223 | /// }); 224 | /// grid.cell(|ui| { 225 | /// ui.label("Nested cell top-right"); 226 | /// }); 227 | /// grid.cell(|ui| { 228 | /// ui.label("Nested cell bottom-left"); 229 | /// }); 230 | /// grid.cell(|ui| { 231 | /// ui.label("Nested cell bottom-right"); 232 | /// }); 233 | /// }); 234 | /// ``` 235 | pub fn nest(mut self, grid: GridBuilder) -> Self { 236 | let len = self.units.len(); 237 | if len > 0 { 238 | // get last 239 | let cell_len = self.units[len - 1].cells.len(); 240 | if cell_len > 0 { 241 | self.units[len - 1].cells[cell_len - 1].nest(grid); 242 | } 243 | } 244 | self 245 | } 246 | 247 | /// Nest a grid at a given row in a given cell. Nothing will happen if a cell doesn't exist at the given coordinates. 248 | pub fn nest_at(mut self, row: i32, cell: i32, grid: GridBuilder) -> Self { 249 | let u_row = row as usize; 250 | let u_cell = cell as usize; 251 | if self.units.get(u_row).is_some() && self.units[u_row].cells.get(u_cell).is_some() { 252 | self.units[u_row].cells[u_cell].nest(grid); 253 | } 254 | self 255 | } 256 | 257 | /// Rows are positioned top-to-bottom spanning horizontally, and cells within rows left-to-right. 258 | /// 259 | /// The cells of a nested grid will be represented in place of the cell that held it. 260 | pub fn show(self, ui: &mut Ui, grid: impl FnOnce(Grid)) -> Response { 261 | //if self.use_default_spacing { self.spacing = ui.style_mut().spacing.item_spacing; } 262 | let allocated_space = ui.available_rect_before_wrap(); 263 | let pure_cells = self.to_real_cells(allocated_space, ui.style().spacing.item_spacing); 264 | let mut bounds = Pos2::new(0., 0.); 265 | 266 | grid(Grid::new(ui, pure_cells, &mut bounds)); 267 | 268 | ui.allocate_rect( 269 | Rect { 270 | min: allocated_space.min, 271 | max: bounds, 272 | }, 273 | Sense::hover(), 274 | ) 275 | } 276 | 277 | /// Setting to `true` will result in rows acting as columns when [`Self::show`] is called (with the cells within being represented top-to-bottom instead of left-to-right). 278 | /// This behavior will remain consistent even if this grid becomes nested within another. 279 | /// 280 | /// Calling this method will ***NOT*** 281 | /// - Propagate to nested grids. 282 | /// - Affect the grid creation process in any way. Rows will still be top-to-bottom and cells left-to-right until [`Self::show`] is called. 283 | /// - Affect the way margins are applied to cells. 284 | /// 285 | /// Default: `false`. 286 | pub fn rows_as_columns(mut self, vertical: bool) -> Self { 287 | self.row_as_col = vertical; 288 | self 289 | } 290 | 291 | // General purpose method for adding cells 292 | fn add_cells(&mut self, size: Size, amount: i32, margin: Margin) { 293 | let len = self.units.len(); 294 | if len > 0 { 295 | let cel_len = self.units[len - 1].cells.len(); 296 | self.creation_cache = Vec::new(); 297 | for c in 1..=amount { 298 | self.units[len - 1] 299 | .cells 300 | .push(Cell::new(size, margin, self.default_layout)); 301 | self.creation_cache 302 | .push((len - 1, cel_len + (c as usize) - 1)); 303 | } 304 | } 305 | } 306 | 307 | // Turn sizes into rectangles and build PureCells 308 | fn to_real_cells(&self, whole_rect: Rect, def_spacing: Vec2) -> Vec { 309 | let mut cells_final = Vec::new(); 310 | 311 | // For row_as_col functionality 312 | let whole_h; 313 | let whole_w; 314 | if self.row_as_col { 315 | (whole_w, whole_h) = (whole_rect.height(), whole_rect.width()); 316 | } else { 317 | (whole_h, whole_w) = (whole_rect.height(), whole_rect.width()); 318 | } 319 | 320 | // Spacing 321 | let spacing = if self.use_default_spacing { 322 | swap_spacing(def_spacing, self.row_as_col) 323 | } else { 324 | swap_spacing(self.spacing, self.row_as_col) 325 | }; 326 | 327 | let row_lengths = row_set_as_f32(&self.units, &spacing.y, &whole_h); 328 | 329 | let mut pointer2d = Pos2::new(whole_rect.min.x, whole_rect.min.y); 330 | 331 | for (row_index, row) in self.units.iter().enumerate() { 332 | // Get cell sizes 333 | let cell_lengths = cell_set_as_f32(&row.cells, &spacing.x, &whole_w); 334 | 335 | // sum of the lengths + spacing 336 | let mut length_sum = -spacing.x; // minus spacing to counter balance the extra spacing added at the end of the for loop 337 | for length in cell_lengths.iter() { 338 | length_sum += length + spacing.x; 339 | } 340 | // apply align offset 341 | let grand_offset: f32 = { 342 | match &row.align { 343 | Align::Min => 0., 344 | Align::Center => (whole_w - length_sum) * 0.5, 345 | Align::Max => whole_w - length_sum, 346 | } 347 | }; 348 | pointer2d.x += grand_offset; 349 | 350 | for (cell_index, cell) in row.cells.iter().enumerate() { 351 | // Build the rect 352 | let mut rect = Rect { 353 | min: pointer2d, 354 | max: Pos2::new( 355 | pointer2d.x + cell_lengths[cell_index], 356 | pointer2d.y + row_lengths[row_index], 357 | ), 358 | }; 359 | 360 | // Apply verticality 361 | if self.row_as_col { 362 | rect = reflect(rect, whole_rect.min); 363 | } 364 | 365 | // Apply margins 366 | let margin = &(row.cells[cell_index].margin); 367 | rect.min.x += margin.left; 368 | rect.min.y += margin.top; 369 | rect.max.x -= margin.right; 370 | rect.max.y -= margin.bottom; 371 | 372 | // Check and handle nested grids 373 | match &row.cells[cell_index].group { 374 | Option::Some(grid) => { 375 | cells_final.extend(grid.to_real_cells(rect, def_spacing)); 376 | } 377 | Option::None => { 378 | cells_final.push(PureCell::new(cell.get_layout(), self.clip, rect)); 379 | } 380 | } 381 | 382 | // Update indexes 383 | pointer2d.x += cell_lengths[cell_index] + spacing.x; 384 | } 385 | 386 | // Update indexes 387 | pointer2d.x = whole_rect.min.x; 388 | pointer2d.y += row_lengths[row_index] + spacing.y; 389 | } 390 | 391 | cells_final 392 | } 393 | } 394 | 395 | // Represents a row of cells. Useless on it's own, must be given to a GridBuilder. 396 | #[derive(Clone)] 397 | pub(crate) struct Row { 398 | pub size: Size, 399 | cells: Vec, 400 | align: Align, 401 | } 402 | 403 | impl Row { 404 | pub fn new(size: Size, align: Align) -> Row { 405 | Row { 406 | size, 407 | cells: Vec::new(), 408 | align, 409 | } 410 | } 411 | 412 | fn align(&mut self, align: Align) { 413 | self.align = align; 414 | } 415 | } 416 | 417 | // Internal struct for the grid builder to keep track of the layout details 418 | #[derive(Clone)] 419 | pub(crate) struct Cell { 420 | pub size: Size, 421 | margin: Margin, 422 | layout: Layout, 423 | pub group: Option, 424 | } 425 | 426 | impl Cell { 427 | pub fn new(size: Size, margin: Margin, layout: Layout) -> Cell { 428 | Cell { 429 | size, 430 | group: None, 431 | margin, 432 | layout, 433 | } 434 | } 435 | 436 | // Nest a grid within this cell 437 | pub fn nest(&mut self, grid: GridBuilder) { 438 | self.group = Some(grid); 439 | } 440 | 441 | pub fn edit_margin(&mut self, margin: Margin) { 442 | self.margin = margin; 443 | } 444 | 445 | pub fn edit_layout(&mut self, layout: Layout) { 446 | self.layout = layout; 447 | } 448 | 449 | pub fn get_layout(&self) -> Layout { 450 | self.layout 451 | } 452 | } 453 | 454 | // A cell with prepared info for the Grid to use to display it 455 | pub(crate) struct PureCell { 456 | rect: Rect, 457 | layout: Layout, 458 | clip: bool, 459 | } 460 | 461 | impl PureCell { 462 | pub fn new(layout: Layout, clip: bool, rect: Rect) -> PureCell { 463 | PureCell { layout, clip, rect } 464 | } 465 | 466 | pub fn rect(&self) -> Rect { 467 | self.rect 468 | } 469 | pub fn layout(&self) -> Layout { 470 | self.layout 471 | } 472 | pub fn clip(&self) -> bool { 473 | self.clip 474 | } 475 | } 476 | --------------------------------------------------------------------------------