diff --git a/Cargo.lock b/Cargo.lock index f553ff0d..4a8c133a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1091,6 +1091,7 @@ dependencies = [ "resvg", "serde", "tiny-skia", + "tracing", "usvg", ] diff --git a/egui_extras/Cargo.toml b/egui_extras/Cargo.toml index 5b3dcb31..c2b809c0 100644 --- a/egui_extras/Cargo.toml +++ b/egui_extras/Cargo.toml @@ -35,6 +35,10 @@ datepicker = ["chrono"] # Persistence persistence = ["serde"] +# Log warnings using `tracing` crate +tracing = ["dep:tracing", "egui/tracing"] + + [dependencies] egui = { version = "0.17.0", path = "../egui", default-features = false } @@ -55,3 +59,6 @@ usvg = { version = "0.22", optional = true } # feature "persistence": serde = { version = "1", features = ["derive"], optional = true } + +# feature "tracing" +tracing = { version = "0.1", optional = true } diff --git a/egui_extras/src/sizing.rs b/egui_extras/src/sizing.rs index 492aea23..150d913d 100644 --- a/egui_extras/src/sizing.rs +++ b/egui_extras/src/sizing.rs @@ -76,16 +76,12 @@ impl Size { } } -#[derive(Clone)] +#[derive(Clone, Default)] pub struct Sizing { pub(crate) sizes: Vec, } impl Sizing { - pub fn new() -> Self { - Self { sizes: vec![] } - } - pub fn add(&mut self, size: Size) { self.sizes.push(size); } diff --git a/egui_extras/src/strip.rs b/egui_extras/src/strip.rs index 2f150877..235a2a1e 100644 --- a/egui_extras/src/strip.rs +++ b/egui_extras/src/strip.rs @@ -11,16 +11,23 @@ use egui::{Response, Ui}; /// /// In contrast to normal egui behavior, strip cells do *not* grow with its children! /// -/// After adding size hints with `[Self::column]`/`[Self::columns]` the strip can be build with `[Self::horizontal]`/`[Self::vertical]`. +/// First use [`Self::size`] and [`Self::sizes`] to allocate space for the rows or columns will follow. +/// Then build the strip with `[Self::horizontal]`/`[Self::vertical]`, and add 'cells' +/// to it using [`Strip::cell`]. The number of cells MUST match the number of pre-allocated sizes. /// /// ### Example /// ``` /// # egui::__run_test_ui(|ui| { /// use egui_extras::{StripBuilder, Size}; /// StripBuilder::new(ui) -/// .size(Size::remainder().at_least(100.0)) -/// .size(Size::exact(40.0)) +/// .size(Size::remainder().at_least(100.0)) // top cell +/// .size(Size::exact(40.0)) // bottom cell /// .vertical(|mut strip| { +/// // Add the top 'cell' +/// strip.cell(|ui| { +/// ui.label("Fixed"); +/// }); +/// // We add a nested strip in the bottom cell: /// strip.strip(|builder| { /// builder.sizes(Size::remainder(), 2).horizontal(|mut strip| { /// strip.cell(|ui| { @@ -31,9 +38,6 @@ use egui::{Response, Ui}; /// }); /// }); /// }); -/// strip.cell(|ui| { -/// ui.label("Fixed"); -/// }); /// }); /// # }); /// ``` @@ -46,11 +50,9 @@ pub struct StripBuilder<'a> { impl<'a> StripBuilder<'a> { /// Create new strip builder. pub fn new(ui: &'a mut Ui) -> Self { - let sizing = Sizing::new(); - Self { ui, - sizing, + sizing: Default::default(), clip: true, } } @@ -61,13 +63,13 @@ impl<'a> StripBuilder<'a> { self } - /// Add size hint for one column/row. + /// Allocate space for for one column/row. pub fn size(mut self, size: Size) -> Self { self.sizing.add(size); self } - /// Add size hint for several columns/rows at once. + /// Allocate space for for several columns/rows at once. pub fn sizes(mut self, size: Size, count: usize) -> Self { for _ in 0..count { self.sizing.add(size); @@ -128,12 +130,21 @@ pub struct Strip<'a, 'b> { impl<'a, 'b> Strip<'a, 'b> { fn next_cell_size(&mut self) -> (CellSize, CellSize) { - assert!( - !self.sizes.is_empty(), - "Tried using more strip cells than available." - ); - let size = self.sizes[0]; - self.sizes = &self.sizes[1..]; + let size = if self.sizes.is_empty() { + if cfg!(debug_assertions) { + panic!("Added more `Strip` cells than were allocated."); + } else { + #[cfg(feature = "tracing")] + tracing::error!("Added more `Strip` cells than were allocated"); + #[cfg(not(feature = "tracing"))] + eprintln!("egui_extras: Added more `Strip` cells than were allocated"); + 8.0 // anything will look wrong, so pick something that is obviously wrong + } + } else { + let size = self.sizes[0]; + self.sizes = &self.sizes[1..]; + size + }; match self.direction { CellDirection::Horizontal => (CellSize::Absolute(size), CellSize::Remainder), @@ -141,19 +152,19 @@ impl<'a, 'b> Strip<'a, 'b> { } } - /// Add empty cell - pub fn empty(&mut self) { - let (width, height) = self.next_cell_size(); - self.layout.empty(width, height); - } - /// Add cell contents. pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) { let (width, height) = self.next_cell_size(); self.layout.add(width, height, add_contents); } - /// Add strip as cell + /// Add an empty cell. + pub fn empty(&mut self) { + let (width, height) = self.next_cell_size(); + self.layout.empty(width, height); + } + + /// Add a strip as cell. pub fn strip(&mut self, strip_builder: impl FnOnce(StripBuilder<'_>)) { let clip = self.layout.clip; self.cell(|ui| { diff --git a/egui_extras/src/table.rs b/egui_extras/src/table.rs index 4cc6fa4c..561ae66c 100644 --- a/egui_extras/src/table.rs +++ b/egui_extras/src/table.rs @@ -13,13 +13,15 @@ use egui::{Rect, Response, Ui, Vec2}; /// Builder for a [`Table`] with (optional) fixed header and scrolling body. /// -/// Cell widths are precalculated with given size hints so we can have tables like this: +/// Cell widths are precalculated so we can have tables like this: /// /// | fixed size | all available space/minimum | 30% of available width | fixed size | /// /// In contrast to normal egui behavior, columns/rows do *not* grow with its children! /// Takes all available height, so if you want something below the table, put it in a strip. /// +/// You must pre-allocate all columns with [`Self::column`]/[`Self::columns`]. +/// /// ### Example /// ``` /// # egui::__run_test_ui(|ui| { @@ -58,11 +60,9 @@ pub struct TableBuilder<'a> { impl<'a> TableBuilder<'a> { pub fn new(ui: &'a mut Ui) -> Self { - let sizing = Sizing::new(); - Self { ui, - sizing, + sizing: Default::default(), scroll: true, striped: false, resizable: false, @@ -102,13 +102,13 @@ impl<'a> TableBuilder<'a> { self } - /// Add size hint for column + /// Allocate space for one column. pub fn column(mut self, width: Size) -> Self { self.sizing.add(width); self } - /// Add size hint for several columns at once. + /// Allocate space for several columns at once. pub fn columns(mut self, size: Size, count: usize) -> Self { for _ in 0..count { self.sizing.add(size); @@ -367,6 +367,76 @@ impl<'a> TableBody<'a> { &self.widths } + /// Add a single row with the given height. + /// + /// If you have many thousands of row it can be more performant to instead use [`Self::rows]` or [`Self::heterogeneous_rows`]. + pub fn row(&mut self, height: f32, row: impl FnOnce(TableRow<'a, '_>)) { + row(TableRow { + layout: &mut self.layout, + widths: &self.widths, + striped: self.striped && self.row_nr % 2 == 0, + height, + }); + + self.row_nr += 1; + } + + /// Add many rows with same height. + /// + /// Is a lot more performant than adding each individual row as non visible rows must not be rendered. + /// + /// If you need many rows with different heights, use [`Self::heterogeneous_rows`] instead. + /// + /// ### Example + /// ``` + /// # egui::__run_test_ui(|ui| { + /// use egui_extras::{TableBuilder, Size}; + /// TableBuilder::new(ui) + /// .column(Size::remainder().at_least(100.0)) + /// .body(|mut body| { + /// let row_height = 18.0; + /// let num_rows = 10_000; + /// body.rows(row_height, num_rows, |row_index, mut row| { + /// row.col(|ui| { + /// ui.label("First column"); + /// }); + /// }); + /// }); + /// # }); + /// ``` + pub fn rows(mut self, height: f32, rows: usize, mut row: impl FnMut(usize, TableRow<'_, '_>)) { + let y_progress = self.y_progress(); + let mut start = 0; + + if y_progress > 0.0 { + start = (y_progress / height).floor() as usize; + + self.add_buffer(y_progress); + } + + let max_height = self.end_y - self.start_y; + let count = (max_height / height).ceil() as usize; + let end = rows.min(start + count); + + for idx in start..end { + row( + idx, + TableRow { + layout: &mut self.layout, + widths: &self.widths, + striped: self.striped && idx % 2 == 0, + height, + }, + ); + } + + if rows - end > 0 { + let skip_height = (rows - end) as f32 * height; + + self.add_buffer(skip_height); + } + } + /// Add rows with varying heights. /// /// This takes a very slight performance hit compared to [`TableBody::rows`] due to the need to @@ -395,7 +465,7 @@ impl<'a> TableBody<'a> { /// # }); /// ``` pub fn heterogeneous_rows( - &mut self, + mut self, heights: impl Iterator, mut populate_row: impl FnMut(usize, TableRow<'_, '_>), ) { @@ -463,72 +533,6 @@ impl<'a> TableBody<'a> { } } - /// Add rows with same height. - /// - /// Is a lot more performant than adding each individual row as non visible rows must not be rendered - /// - /// ### Example - /// ``` - /// # egui::__run_test_ui(|ui| { - /// use egui_extras::{TableBuilder, Size}; - /// TableBuilder::new(ui) - /// .column(Size::remainder().at_least(100.0)) - /// .body(|mut body| { - /// let row_height = 18.0; - /// let num_rows = 10_000; - /// body.rows(row_height, num_rows, |row_index, mut row| { - /// row.col(|ui| { - /// ui.label("First column"); - /// }); - /// }); - /// }); - /// # }); - /// ``` - pub fn rows(mut self, height: f32, rows: usize, mut row: impl FnMut(usize, TableRow<'_, '_>)) { - let y_progress = self.y_progress(); - let mut start = 0; - - if y_progress > 0.0 { - start = (y_progress / height).floor() as usize; - - self.add_buffer(y_progress); - } - - let max_height = self.end_y - self.start_y; - let count = (max_height / height).ceil() as usize; - let end = rows.min(start + count); - - for idx in start..end { - row( - idx, - TableRow { - layout: &mut self.layout, - widths: &self.widths, - striped: self.striped && idx % 2 == 0, - height, - }, - ); - } - - if rows - end > 0 { - let skip_height = (rows - end) as f32 * height; - - self.add_buffer(skip_height); - } - } - - /// Add row with individual height - pub fn row(&mut self, height: f32, row: impl FnOnce(TableRow<'a, '_>)) { - row(TableRow { - layout: &mut self.layout, - widths: &self.widths, - striped: self.striped && self.row_nr % 2 == 0, - height, - }); - - self.row_nr += 1; - } - // Create a table row buffer of the given height to represent the non-visible portion of the // table. fn add_buffer(&mut self, height: f32) { @@ -564,9 +568,22 @@ impl<'a, 'b> TableRow<'a, 'b> { !self.widths.is_empty(), "Tried using more table columns than available." ); + let width = if self.widths.is_empty() { + if cfg!(debug_assertions) { + panic!("Added more `Table` columns than were allocated."); + } else { + #[cfg(feature = "tracing")] + tracing::error!("Added more `Table` columns than were allocated"); + #[cfg(not(feature = "tracing"))] + eprintln!("egui_extras: Added more `Table` columns than were allocated"); + 8.0 // anything will look wrong, so pick something that is obviously wrong + } + } else { + let width = self.widths[0]; + self.widths = &self.widths[1..]; + width + }; - let width = self.widths[0]; - self.widths = &self.widths[1..]; let width = CellSize::Absolute(width); let height = CellSize::Absolute(self.height);