diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 3d4eac39..b0b8747d 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -107,12 +107,12 @@ impl Default for Tests { fn default() -> Self { Self::from_demos(vec![ Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), Box::::default(), Box::::default(), - Box::::default(), Box::::default(), ]) } diff --git a/crates/egui_demo_lib/src/demo/tests/table_test.rs b/crates/egui_demo_lib/src/demo/tests/grid_test.rs similarity index 96% rename from crates/egui_demo_lib/src/demo/tests/table_test.rs rename to crates/egui_demo_lib/src/demo/tests/grid_test.rs index 72e39ff0..511aa428 100644 --- a/crates/egui_demo_lib/src/demo/tests/table_test.rs +++ b/crates/egui_demo_lib/src/demo/tests/grid_test.rs @@ -1,5 +1,5 @@ #[derive(PartialEq)] -pub struct TableTest { +pub struct GridTest { num_cols: usize, num_rows: usize, min_col_width: f32, @@ -7,7 +7,7 @@ pub struct TableTest { text_length: usize, } -impl Default for TableTest { +impl Default for GridTest { fn default() -> Self { Self { num_cols: 4, @@ -19,9 +19,9 @@ impl Default for TableTest { } } -impl crate::Demo for TableTest { +impl crate::Demo for GridTest { fn name(&self) -> &'static str { - "Table Test" + "Grid Test" } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { @@ -32,7 +32,7 @@ impl crate::Demo for TableTest { } } -impl crate::View for TableTest { +impl crate::View for GridTest { fn ui(&mut self, ui: &mut egui::Ui) { ui.add( egui::Slider::new(&mut self.min_col_width, 0.0..=400.0).text("Minimum column width"), diff --git a/crates/egui_demo_lib/src/demo/tests/mod.rs b/crates/egui_demo_lib/src/demo/tests/mod.rs index 497f384c..78ad0a01 100644 --- a/crates/egui_demo_lib/src/demo/tests/mod.rs +++ b/crates/egui_demo_lib/src/demo/tests/mod.rs @@ -1,17 +1,17 @@ mod cursor_test; +mod grid_test; mod id_test; mod input_event_history; mod input_test; mod layout_test; mod manual_layout_test; -mod table_test; mod window_resize_test; pub use cursor_test::CursorTest; +pub use grid_test::GridTest; pub use id_test::IdTest; pub use input_event_history::InputEventHistory; pub use input_test::InputTest; pub use layout_test::LayoutTest; pub use manual_layout_test::ManualLayoutTest; -pub use table_test::TableTest; pub use window_resize_test::WindowResizeTest; diff --git a/crates/egui_extras/src/datepicker/popup.rs b/crates/egui_extras/src/datepicker/popup.rs index d75d638e..1252f209 100644 --- a/crates/egui_extras/src/datepicker/popup.rs +++ b/crates/egui_extras/src/datepicker/popup.rs @@ -58,6 +58,9 @@ impl<'a> DatePickerPopup<'a> { let height = 20.0; let spacing = 2.0; ui.spacing_mut().item_spacing = Vec2::splat(spacing); + + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); // Don't wrap any text + StripBuilder::new(ui) .clip(false) .sizes( diff --git a/crates/egui_extras/src/layout.rs b/crates/egui_extras/src/layout.rs index cc51e05d..db3b5f20 100644 --- a/crates/egui_extras/src/layout.rs +++ b/crates/egui_extras/src/layout.rs @@ -191,12 +191,12 @@ impl<'l> StripLayout<'l> { fn cell( &mut self, flags: StripLayoutFlags, - rect: Rect, + max_rect: Rect, child_ui_id_source: egui::Id, add_cell_contents: impl FnOnce(&mut Ui), ) -> Ui { let mut child_ui = self.ui.child_ui_with_id_source( - rect, + max_rect, self.cell_layout, child_ui_id_source, Some(egui::UiStackInfo::new(egui::UiKind::TableCell)), @@ -205,7 +205,7 @@ impl<'l> StripLayout<'l> { if flags.clip { let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin); let margin = margin.min(0.5 * self.ui.spacing().item_spacing); - let clip_rect = rect.expand2(margin); + let clip_rect = max_rect.expand2(margin); child_ui.set_clip_rect(clip_rect.intersect(child_ui.clip_rect())); } diff --git a/crates/egui_extras/src/sizing.rs b/crates/egui_extras/src/sizing.rs index 2c380ae6..77030677 100644 --- a/crates/egui_extras/src/sizing.rs +++ b/crates/egui_extras/src/sizing.rs @@ -49,26 +49,20 @@ impl Size { /// Won't shrink below this size (in points). #[inline] pub fn at_least(mut self, minimum: f32) -> Self { - match &mut self { - Self::Absolute { range, .. } - | Self::Relative { range, .. } - | Self::Remainder { range, .. } => { - range.min = minimum; - } - } + self.range_mut().min = minimum; self } /// Won't grow above this size (in points). #[inline] pub fn at_most(mut self, maximum: f32) -> Self { - match &mut self { - Self::Absolute { range, .. } - | Self::Relative { range, .. } - | Self::Remainder { range, .. } => { - range.max = maximum; - } - } + self.range_mut().max = maximum; + self + } + + #[inline] + pub fn with_range(mut self, range: Rangef) -> Self { + *self.range_mut() = range; self } @@ -80,6 +74,29 @@ impl Size { | Self::Remainder { range, .. } => range, } } + + pub fn range_mut(&mut self) -> &mut Rangef { + match self { + Self::Absolute { range, .. } + | Self::Relative { range, .. } + | Self::Remainder { range, .. } => range, + } + } + + #[inline] + pub fn is_absolute(&self) -> bool { + matches!(self, Self::Absolute { .. }) + } + + #[inline] + pub fn is_relative(&self) -> bool { + matches!(self, Self::Relative { .. }) + } + + #[inline] + pub fn is_remainder(&self) -> bool { + matches!(self, Self::Remainder { .. }) + } } #[derive(Clone, Default)] @@ -97,7 +114,7 @@ impl Sizing { return vec![]; } - let mut remainders = 0; + let mut num_remainders = 0; let sum_non_remainder = self .sizes .iter() @@ -108,28 +125,28 @@ impl Sizing { range.clamp(length * fraction) } Size::Remainder { .. } => { - remainders += 1; + num_remainders += 1; 0.0 } }) .sum::() + spacing * (self.sizes.len() - 1) as f32; - let avg_remainder_length = if remainders == 0 { + let avg_remainder_length = if num_remainders == 0 { 0.0 } else { let mut remainder_length = length - sum_non_remainder; - let avg_remainder_length = 0.0f32.max(remainder_length / remainders as f32).floor(); - self.sizes.iter().for_each(|&size| { + let avg_remainder_length = 0.0f32.max(remainder_length / num_remainders as f32).floor(); + for &size in &self.sizes { if let Size::Remainder { range } = size { if avg_remainder_length < range.min { remainder_length -= range.min; - remainders -= 1; + num_remainders -= 1; } } - }); - if remainders > 0 { - 0.0f32.max(remainder_length / remainders as f32) + } + if num_remainders > 0 { + 0.0f32.max(remainder_length / num_remainders as f32) } else { 0.0 } diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 0c57831f..92ac9256 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -401,13 +401,8 @@ impl<'a> TableBuilder<'a> { fn available_width(&self) -> f32 { self.ui.available_rect_before_wrap().width() - - if self.scroll_options.vscroll { - self.ui.spacing().scroll.bar_inner_margin - + self.ui.spacing().scroll.bar_width - + self.ui.spacing().scroll.bar_outer_margin - } else { - 0.0 - } + - (self.scroll_options.vscroll as i32 as f32) + * self.ui.spacing().scroll.allocated_width() } /// Create a header row which always stays visible and at the top @@ -428,17 +423,13 @@ impl<'a> TableBuilder<'a> { let state_id = ui.id().with("__table_state"); - let initial_widths = - to_sizing(&columns).to_lengths(available_width, ui.spacing().item_spacing.x); - let mut max_used_widths = vec![0.0; initial_widths.len()]; - let (had_state, state) = TableState::load(ui, initial_widths, state_id); - let is_first_frame = !had_state; - let first_frame_auto_size_columns = is_first_frame && columns.iter().any(|c| c.is_auto()); + let (is_sizing_pass, state) = TableState::load(ui, state_id, &columns, available_width); + let mut max_used_widths = vec![0.0; columns.len()]; let table_top = ui.cursor().top(); ui.scope(|ui| { - if first_frame_auto_size_columns { + if is_sizing_pass { // Hide first-frame-jitters when auto-sizing. ui.set_sizing_pass(); } @@ -468,7 +459,7 @@ impl<'a> TableBuilder<'a> { available_width, state, max_used_widths, - first_frame_auto_size_columns, + is_sizing_pass, resizable, striped, cell_layout, @@ -498,13 +489,9 @@ impl<'a> TableBuilder<'a> { let state_id = ui.id().with("__table_state"); - let initial_widths = - to_sizing(&columns).to_lengths(available_width, ui.spacing().item_spacing.x); - let max_used_widths = vec![0.0; initial_widths.len()]; - let (had_state, state) = TableState::load(ui, initial_widths, state_id); - let is_first_frame = !had_state; - let first_frame_auto_size_columns = is_first_frame && columns.iter().any(|c| c.is_auto()); + let (is_sizing_pass, state) = TableState::load(ui, state_id, &columns, available_width); + let max_used_widths = vec![0.0; columns.len()]; let table_top = ui.cursor().top(); Table { @@ -515,7 +502,7 @@ impl<'a> TableBuilder<'a> { available_width, state, max_used_widths, - first_frame_auto_size_columns, + is_sizing_pass, resizable, striped, cell_layout, @@ -535,24 +522,30 @@ struct TableState { } impl TableState { - /// Returns `true` if it did load. - fn load(ui: &egui::Ui, default_widths: Vec, state_id: egui::Id) -> (bool, Self) { + /// Return true if we should do a sizing pass. + fn load(ui: &Ui, state_id: egui::Id, columns: &[Column], available_width: f32) -> (bool, Self) { let rect = Rect::from_min_size(ui.available_rect_before_wrap().min, Vec2::ZERO); ui.ctx().check_for_id_clash(state_id, rect, "Table"); - if let Some(state) = ui.data_mut(|d| d.get_persisted::(state_id)) { - // make sure that the stored widths aren't out-dated - if state.column_widths.len() == default_widths.len() { - return (true, state); - } - } + let state = ui + .data_mut(|d| d.get_persisted::(state_id)) + .filter(|state| { + // make sure that the stored widths aren't out-dated + state.column_widths.len() == columns.len() + }); - ( - false, + let is_sizing_pass = + ui.is_sizing_pass() || state.is_none() && columns.iter().any(|c| c.is_auto()); + + let state = state.unwrap_or_else(|| { + let initial_widths = + to_sizing(columns).to_lengths(available_width, ui.spacing().item_spacing.x); Self { - column_widths: default_widths, - }, - ) + column_widths: initial_widths, + } + }); + + (is_sizing_pass, state) } fn store(self, ui: &egui::Ui, state_id: egui::Id) { @@ -576,7 +569,8 @@ pub struct Table<'a> { /// Accumulated maximum used widths for each column. max_used_widths: Vec, - first_frame_auto_size_columns: bool, + /// During the sizing pass we calculate the width of columns with [`Column::auto`]. + is_sizing_pass: bool, resizable: bool, striped: bool, cell_layout: egui::Layout, @@ -608,7 +602,7 @@ impl<'a> Table<'a> { mut available_width, mut state, mut max_used_widths, - first_frame_auto_size_columns, + is_sizing_pass, striped, cell_layout, scroll_options, @@ -653,7 +647,7 @@ impl<'a> Table<'a> { // Hide first-frame-jitters when auto-sizing. ui.scope(|ui| { - if first_frame_auto_size_columns { + if is_sizing_pass { ui.set_sizing_pass(); } @@ -723,9 +717,8 @@ impl<'a> Table<'a> { x += *column_width + spacing_x; - if column.is_auto() && (first_frame_auto_size_columns || !column_is_resizable) { - *column_width = max_used_widths[i]; - *column_width = width_range.clamp(*column_width); + if column.is_auto() && (is_sizing_pass || !column_is_resizable) { + *column_width = width_range.clamp(max_used_widths[i]); } else if column_is_resizable { let column_resize_id = ui.id().with("resize_column").with(i);