From 2a630db4727b3b49c9c3eb3bcdea0cb72949ce6e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 5 Sep 2024 13:02:10 +0200 Subject: [PATCH] More realistic demo with various fixes and tweaks (#6) It's starting to get ready for production! --- Cargo.lock | 16 ++++---- Cargo.toml | 8 +--- README.md | 2 - demo/src/table_demo.rs | 85 ++++++++++++++++++++++++++++++----------- egui_table/src/table.rs | 33 ++++++++++++---- 5 files changed, 99 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abc3445..399662a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,7 +397,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" version = "0.28.1" -source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba" +source = "git+https://github.com/emilk/egui?rev=7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c#7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c" dependencies = [ "bytemuck", "emath", @@ -407,7 +407,7 @@ dependencies = [ [[package]] name = "eframe" version = "0.28.1" -source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba" +source = "git+https://github.com/emilk/egui?rev=7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c#7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c" dependencies = [ "ahash", "bytemuck", @@ -443,7 +443,7 @@ dependencies = [ [[package]] name = "egui" version = "0.28.1" -source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba" +source = "git+https://github.com/emilk/egui?rev=7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c#7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c" dependencies = [ "accesskit", "ahash", @@ -458,7 +458,7 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.28.1" -source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba" +source = "git+https://github.com/emilk/egui?rev=7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c#7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c" dependencies = [ "ahash", "arboard", @@ -475,7 +475,7 @@ dependencies = [ [[package]] name = "egui_glow" version = "0.28.1" -source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba" +source = "git+https://github.com/emilk/egui?rev=7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c#7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c" dependencies = [ "ahash", "bytemuck", @@ -499,7 +499,7 @@ dependencies = [ [[package]] name = "emath" version = "0.28.1" -source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba" +source = "git+https://github.com/emilk/egui?rev=7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c#7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c" dependencies = [ "bytemuck", "serde", @@ -532,7 +532,7 @@ dependencies = [ [[package]] name = "epaint" version = "0.28.1" -source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba" +source = "git+https://github.com/emilk/egui?rev=7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c#7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c" dependencies = [ "ab_glyph", "ahash", @@ -549,7 +549,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" version = "0.28.1" -source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba" +source = "git+https://github.com/emilk/egui?rev=7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c#7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c" [[package]] name = "equivalent" diff --git a/Cargo.toml b/Cargo.toml index 10db935..a7050ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,6 @@ version = "0.28.1" [profile.release] opt-level = 2 # fast and small wasm -[profile.dev.package."*"] -# Optimize all dependencies even in debug builds (does not affect workspace packages): -opt-level = 2 - [workspace.dependencies] egui_table = { version = "0.28.1", path = "egui_table", default-features = false } @@ -31,8 +27,8 @@ vec1 = { version = "1.12.1", default-features = false } [patch.crates-io] # If you want to use the bleeding edge version of egui and eframe: -eframe = { git = "https://github.com/emilk/egui", rev = "2be93aca5d0db973ab163267fb4162363786ffba" } # egui master 2024-09-05 -egui = { git = "https://github.com/emilk/egui", rev = "2be93aca5d0db973ab163267fb4162363786ffba" } # egui master 2024-09-05 +eframe = { git = "https://github.com/emilk/egui", rev = "7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c" } # egui master 2024-09-05 +egui = { git = "https://github.com/emilk/egui", rev = "7cb61f8031d2d7ccfa8cfb2442a52a2a31ec923c" } # egui master 2024-09-05 # If you fork https://github.com/emilk/egui you can test with: # egui = { path = "../../egui/crates/egui" } diff --git a/README.md b/README.md index c0abdd3..13918f8 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ Work-in-progress. Pre-release. ### TODO/Bugs * Hierarchical headers * Fix auto-sizing on parent resize -* Test with `egui::Sides` -* Test with truncating content * Go through TODOs diff --git a/demo/src/table_demo.rs b/demo/src/table_demo.rs index 340fd9a..f2cf266 100644 --- a/demo/src/table_demo.rs +++ b/demo/src/table_demo.rs @@ -1,5 +1,6 @@ use std::ops::Range; +use egui::Margin; use egui_table::{AutoSizeMode, CellInfo, Column, Table, TableDelegate, TableState}; #[derive(serde::Deserialize, serde::Serialize)] @@ -9,17 +10,21 @@ pub struct TableDemo { num_sticky_cols: usize, default_column: Column, auto_size_mode: AutoSizeMode, + top_row_height: f32, + row_height: f32, prefetched_row_ranges: Vec>, } impl Default for TableDemo { fn default() -> Self { Self { - num_columns: 10, - num_rows: 100, + num_columns: 20, + num_rows: 10_000, num_sticky_cols: 1, default_column: Column::new(100.0, 10.0..=500.0), auto_size_mode: AutoSizeMode::default(), + top_row_height: 24.0, + row_height: 20.0, prefetched_row_ranges: vec![], } } @@ -41,27 +46,55 @@ impl TableDelegate for TableDemo { fn cell_ui(&mut self, ui: &mut egui::Ui, cell: &CellInfo) { let CellInfo { row_nr, col_nr, .. } = *cell; - ui.add_space(4.0); - - if !self.was_prefetched(row_nr) { + if row_nr % 2 == 1 { ui.painter() - .rect_filled(ui.max_rect(), 0.0, ui.visuals().error_fg_color); - ui.label("ERROR: row not prefetched"); - log::warn!("Was asked to show row {row_nr} which was not prefetched! This is a bug."); - return; + .rect_filled(ui.max_rect(), 0.0, ui.visuals().faint_bg_color); } - if row_nr == 0 { - ui.heading(format!("Column {col_nr}")); - } else { - if row_nr % 2 == 1 { - ui.painter() - .rect_filled(ui.max_rect(), 0.0, ui.visuals().faint_bg_color); - } - ui.label(format!("({row_nr}, {col_nr})")); - } - - ui.add_space(4.0); + egui::Frame::none() + .inner_margin(Margin::symmetric(4.0, 0.0)) + .show(ui, |ui| { + if !self.was_prefetched(row_nr) { + ui.painter() + .rect_filled(ui.max_rect(), 0.0, ui.visuals().error_fg_color); + ui.label("ERROR: row not prefetched"); + log::warn!( + "Was asked to show row {row_nr} which was not prefetched! This is a bug." + ); + return; + } + + #[allow(clippy::collapsible_else_if)] + if row_nr == 0 { + if col_nr == 0 { + egui::Sides::new().height(ui.available_height()).show( + ui, + |ui| { + ui.heading("Row"); + }, + |ui| { + ui.label("⬇"); + }, + ); + } else { + ui.heading(format!("Column {col_nr}")); + } + } else { + if col_nr == 0 { + ui.label(row_nr.to_string()); + } else { + ui.label(format!("({row_nr}, {col_nr})")); + + if (row_nr + col_nr as u64) % 27 == 0 { + if !ui.is_sizing_pass() { + // During a sizing pass we don't truncate! + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate); + } + ui.label("Extra long cell!"); + } + } + } + }); } } @@ -84,6 +117,14 @@ impl TableDemo { }); ui.end_row(); + ui.label("Height of top row"); + ui.add(egui::DragValue::new(&mut self.top_row_height).range(0.0..=100.0)); + ui.end_row(); + + ui.label("Height of other rows"); + ui.add(egui::DragValue::new(&mut self.row_height).range(0.0..=100.0)); + ui.end_row(); + ui.label("Sticky columns"); ui.add(egui::DragValue::new(&mut self.num_sticky_cols)); ui.end_row(); @@ -146,8 +187,8 @@ impl TableDemo { columns: vec![self.default_column; self.num_columns], id_salt, num_sticky_cols: self.num_sticky_cols, - sticky_row_heights: vec![20.0; 1], - row_height: 16.0, + sticky_row_heights: vec![self.top_row_height; 1], + row_height: self.row_height, num_rows: self.num_rows, auto_size_mode: self.auto_size_mode, } diff --git a/egui_table/src/table.rs b/egui_table/src/table.rs index 359b3ad..ac8ecd7 100644 --- a/egui_table/src/table.rs +++ b/egui_table/src/table.rs @@ -62,7 +62,7 @@ impl TableState { /// /// ## Batteries not included /// * You need to specify its size beforehand -/// * Does not add any margins to cells. Add it yourself. +/// * Does not add any margins to cells. Add it yourself with [`egui::Frame`]. /// * Does not clip cells, or wrap them in scroll areas. Do that yourself. /// * Doesn't paint any guide-lines for the rows. Paint them yourself. /// * There is not special header rows. Use sticky rows for that. @@ -128,7 +128,10 @@ impl Table { let num_scroll_rows = self.num_rows - self.sticky_row_heights.len() as u64; let id = TableState::id(ui, self.id_salt); - let mut state: TableState = TableState::load(ui.ctx(), id).unwrap_or_default(); + let state = TableState::load(ui.ctx(), id); + let is_new = state.is_none(); + let do_full_sizing_pass = is_new; + let mut state = state.unwrap_or_default(); for (i, column) in self.columns.iter_mut().enumerate() { let column_id = column.id(i); @@ -136,6 +139,10 @@ impl Table { column.current = *existing_width; } column.current = column.range.clamp(column.current); + + if do_full_sizing_pass { + column.auto_size_this_frame = true; + } } let parent_width = ui.available_width(); @@ -177,7 +184,11 @@ impl Table { self.sticky_row_heights.iter().sum(), ); - ui.scope(|ui| { + let mut ui_builder = UiBuilder::new(); + if do_full_sizing_pass { + ui_builder = ui_builder.sizing_pass().invisible(); + } + ui.scope_builder(ui_builder, |ui| { // Don't wrap text in the table cells. ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); // TODO: I think this is default for horizontal layouts anyway? @@ -319,6 +330,7 @@ impl<'a> TableSplitScrollDelegate<'a> { }; let mut cell_rect = self.cell_rect(col_nr, row_nr).translate(-offset); + let clip_rect = cell_rect; // Note: we shrink the cell rect when auto-sizing, but not the clip rect! This is to avoid flicker. if column.auto_size_this_frame { cell_rect.max.x = cell_rect.min.x + column.range.min; } @@ -331,7 +343,7 @@ impl<'a> TableSplitScrollDelegate<'a> { ui_builder = ui_builder.sizing_pass(); } let mut cell_ui = ui.new_child(ui_builder); - cell_ui.shrink_clip_rect(cell_rect); + cell_ui.shrink_clip_rect(clip_rect); self.table_delegate .cell_ui(&mut cell_ui, &CellInfo { col_nr, row_nr }); @@ -420,18 +432,25 @@ impl<'a> SplitScrollDelegate for TableSplitScrollDelegate<'a> { if resize_response.dragged() { if let Some(pointer) = ui.ctx().pointer_latest_pos() { - let mut new_width = *column_width + pointer.x - x; + let desired_new_width = *column_width + pointer.x - x; + let desired_new_width = column.range.clamp(desired_new_width); // We don't want to shrink below the size that was actually used. // However, we still want to allow content that shrinks when you try // to make the column less wide, so we allow some small shrinkage each frame: // big enough to allow shrinking over time, small enough not to look ugly when // shrinking fails. This is a bit of a HACK around immediate mode. + // TODO: do something smarter by remembering success/failure to resize from one frame to the next. let max_shrinkage_per_frame = 8.0; - new_width = new_width.at_least(used_width - max_shrinkage_per_frame); - new_width = column.range.clamp(new_width); + let new_width = + desired_new_width.at_least(used_width - max_shrinkage_per_frame); + let new_width = column.range.clamp(new_width); x += new_width - *column_width; *column_width = new_width; + + if new_width != desired_new_width { + ui.ctx().request_repaint(); // Get there faster + } } }