diff --git a/demo/src/table_demo.rs b/demo/src/table_demo.rs index 83c9d3b..dd269fb 100644 --- a/demo/src/table_demo.rs +++ b/demo/src/table_demo.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use egui::{Align2, Id, Margin, NumExt, Sense, Vec2}; +use egui::{Align2, Context, Id, Margin, NumExt, Sense, Vec2}; #[derive(serde::Deserialize, serde::Serialize)] pub struct TableDemo { @@ -177,6 +177,19 @@ impl egui_table::TableDelegate for TableDemo { self.cell_content_ui(row_nr, col_nr, ui); }); } + + fn row_top_offset(&self, ctx: &Context, _table_id: Id, row_nr: u64) -> f32 { + let fully_expanded_row_height = 48.0; + + self.is_row_expanded + .range(0..row_nr) + .map(|(expanded_row_nr, expanded)| { + let how_expanded = ctx.animate_bool(Id::new(expanded_row_nr), *expanded); + how_expanded * fully_expanded_row_height + }) + .sum::() + + row_nr as f32 * self.row_height + } } impl TableDemo { @@ -306,11 +319,6 @@ impl TableDemo { ui.separator(); - // TODO: avoid this: - let egui_ctx = ui.ctx().clone(); - let is_row_expanded = self.is_row_expanded.clone(); - let row_height = self.row_height; - let mut table = egui_table::Table::new() .id_salt(id_salt) .num_rows(self.num_rows) @@ -323,18 +331,6 @@ impl TableDemo { }, egui_table::HeaderRow::new(self.top_row_height), ]) - .row_top_offset(move |row_nr| -> f32 { - let fully_expanded_row_height = 48.0; - is_row_expanded - .range(0..row_nr) - .map(|(expanded_row_nr, expanded)| { - let how_expanded = - egui_ctx.animate_bool(Id::new(expanded_row_nr), *expanded); - how_expanded * fully_expanded_row_height - }) - .sum::() - + row_nr as f32 * row_height - }) .auto_size_mode(self.auto_size_mode); if let Some(scroll_to_column) = scroll_to_column { diff --git a/egui_table/src/table.rs b/egui_table/src/table.rs index 43f870b..f4dfdc4 100644 --- a/egui_table/src/table.rs +++ b/egui_table/src/table.rs @@ -3,7 +3,9 @@ use std::{ ops::{Range, RangeInclusive}, }; -use egui::{vec2, Align, Id, IdMap, NumExt as _, Rangef, Rect, Ui, UiBuilder, Vec2, Vec2b}; +use egui::{ + vec2, Align, Context, Id, IdMap, NumExt as _, Rangef, Rect, Ui, UiBuilder, Vec2, Vec2b, +}; use vec1::Vec1; use crate::{columns::Column, SplitScroll, SplitScrollDelegate}; @@ -88,7 +90,7 @@ impl HeaderRow { /// * Does not add any margins to cells. Add it yourself with [`egui::Frame`]. /// * Does not wrap cells in scroll areas. Do that yourself. /// * Doesn't paint any guide-lines for the rows. Paint them yourself. -pub struct Table<'cb> { +pub struct Table { /// The columns of the table. columns: Vec, @@ -104,9 +106,6 @@ pub struct Table<'cb> { /// The count and parameters of the sticky (non-scrolling) header rows. headers: Vec, - /// Height of the non-sticky rows. - row_top_offset: Box f32 + 'cb>, - /// Total number of rows (sticky + non-sticky). num_rows: u64, @@ -117,14 +116,13 @@ pub struct Table<'cb> { scroll_to_rows: Option<(RangeInclusive, Option)>, } -impl<'cb> Default for Table<'cb> { +impl Default for Table { fn default() -> Self { Self { columns: vec![], id_salt: Id::new("table"), num_sticky_cols: 0, headers: vec![HeaderRow::new(16.0)], - row_top_offset: Box::new(|row_nr| 16.0 * row_nr as f32), num_rows: 0, auto_size_mode: AutoSizeMode::default(), scroll_to_columns: None, @@ -181,9 +179,26 @@ pub trait TableDelegate { /// /// The [`CellInfo::row_nr`] is ignoring header rows. fn cell_ui(&mut self, ui: &mut Ui, cell: &CellInfo); + + /// Compute the offset for the top of the given row. + /// + /// Implement this for arbitrary row heights. The default implementation uses + /// [`Self::default_row_height`]. + /// + /// Note: must always return 0.0 for `row_nr = 0`. + fn row_top_offset(&self, _ctx: &Context, _table_id: Id, row_nr: u64) -> f32 { + row_nr as f32 * self.default_row_height() + } + + /// Default row height. + /// + /// This is used by the default implementation of [`Self::row_top_offset`]. + fn default_row_height(&self) -> f32 { + 20.0 + } } -impl<'cb> Table<'cb> { +impl Table { /// Create a new table, with no columns and no headers, and zero rows. #[inline] pub fn new() -> Self { @@ -230,38 +245,6 @@ impl<'cb> Table<'cb> { self } - /// Height of the non-sticky rows. - /// - /// This is for tables with rows of uniform height. - /// If you want heterogenous rwo heights, use [`Self::row_top_offset`] instead. - #[inline] - pub fn row_height(mut self, row_height: f32) -> Self { - self.row_top_offset = Box::new(move |row_nr| row_nr as f32 * row_height); - self - } - - /// Sets up where the bottom of each row is. - /// - /// This is for when you have heterogenous row heights, - /// i.e. different rows has different heights. - /// - /// The function should return the y-offset of the top edge of a specific row. - /// For instance, it could return: - /// - /// * `f(0) => 0.0` - /// * `f(1) => 20.0` - /// * `f(2) => 30.0` - /// * `f(3) => 40.0` - /// - /// The bottom of the last row will be calculated as `f(num_rows)`. - /// - /// This function may be called hundreds of times, so make it fast! - #[inline] - pub fn row_top_offset(mut self, row_top_offset: impl Fn(u64) -> f32 + 'cb) -> Self { - self.row_top_offset = Box::new(row_top_offset); - self - } - /// How to do auto-sizing of columns, if at all. #[inline] pub fn auto_size_mode(mut self, auto_size_mode: AutoSizeMode) -> Self { @@ -325,14 +308,27 @@ impl<'cb> Table<'cb> { /// The top y coordinate offset of a specific row nr. /// /// `get_row_top_offset(0)` should always return 0.0. - fn get_row_top_offset(&self, row_nr: u64) -> f32 { - (self.row_top_offset)(row_nr) + #[allow(clippy::unused_self)] // for uniformity + fn get_row_top_offset( + &self, + ctx: &Context, + table_id: Id, + table_delegate: &dyn TableDelegate, + row_nr: u64, + ) -> f32 { + table_delegate.row_top_offset(ctx, table_id, row_nr) } /// Which row contains the given y offset (from the top)? - fn get_row_nr_at_y_offset(&self, y_offset: f32) -> u64 { + fn get_row_nr_at_y_offset( + &self, + ctx: &Context, + table_id: Id, + table_delegate: &dyn TableDelegate, + y_offset: f32, + ) -> u64 { partition_point(0..=self.num_rows, |row_nr| { - y_offset <= self.get_row_top_offset(row_nr) + y_offset <= self.get_row_top_offset(ctx, table_id, table_delegate, row_nr) }) .saturating_sub(1) } @@ -427,7 +423,7 @@ impl<'cb> Table<'cb> { .iter() .map(|c| c.current) .sum(), - self.get_row_top_offset(self.num_rows), + self.get_row_top_offset(ui.ctx(), id, table_delegate, self.num_rows), ), } .show( @@ -443,6 +439,7 @@ impl<'cb> Table<'cb> { visible_column_lines: Default::default(), do_full_sizing_pass, has_prefetched: false, + egui_ctx: ui.ctx().clone(), }, ); }); @@ -469,10 +466,10 @@ fn update(map: &mut BTreeMap, key: usize, value: ColumnRes } } -struct TableSplitScrollDelegate<'a, 'cb> { +struct TableSplitScrollDelegate<'a> { id: Id, table_delegate: &'a mut dyn TableDelegate, - table: &'a mut Table<'cb>, + table: &'a mut Table, state: &'a mut TableState, /// The x coordinate for the start of each column, plus the end of the last column. @@ -490,9 +487,23 @@ struct TableSplitScrollDelegate<'a, 'cb> { do_full_sizing_pass: bool, has_prefetched: bool, + + egui_ctx: Context, } -impl<'a, 'cb> TableSplitScrollDelegate<'a, 'cb> { +impl<'a> TableSplitScrollDelegate<'a> { + /// Helper wrapper around [`Table::get_row_top_offset`]. + fn get_row_top_offset(&self, row_nr: u64) -> f32 { + self.table + .get_row_top_offset(&self.egui_ctx, self.id, self.table_delegate, row_nr) + } + + /// Helper wrapper around [`Table::get_row_nr_at_y_offset`]. + fn get_row_nr_at_y_offset(&self, y_offset: f32) -> u64 { + self.table + .get_row_nr_at_y_offset(&self.egui_ctx, self.id, self.table_delegate, y_offset) + } + fn header_ui(&mut self, ui: &mut Ui, offset: Vec2) { for (row_nr, header_row) in self.table.headers.iter().enumerate() { let groups = if header_row.groups.is_empty() { @@ -608,9 +619,7 @@ impl<'a, 'cb> TableSplitScrollDelegate<'a, 'cb> { } else { // Only paint the visible rows: let row_idx_at = |y: f32| -> u64 { - let row_nr = self - .table - .get_row_nr_at_y_offset(y - self.header_row_y.last()); + let row_nr = self.get_row_nr_at_y_offset(y - self.header_row_y.last()); row_nr.at_most(self.table.num_rows.saturating_sub(1)) }; @@ -639,8 +648,8 @@ impl<'a, 'cb> TableSplitScrollDelegate<'a, 'cb> { for row_nr in row_range { let y_range = Rangef::new( - self.header_row_y.last() + self.table.get_row_top_offset(row_nr), - self.header_row_y.last() + self.table.get_row_top_offset(row_nr + 1), + self.header_row_y.last() + self.get_row_top_offset(row_nr), + self.header_row_y.last() + self.get_row_top_offset(row_nr + 1), ); for col_nr in col_range.clone() { @@ -689,7 +698,7 @@ impl<'a, 'cb> TableSplitScrollDelegate<'a, 'cb> { } } -impl<'a, 'cb> SplitScrollDelegate for TableSplitScrollDelegate<'a, 'cb> { +impl<'a> SplitScrollDelegate for TableSplitScrollDelegate<'a> { // First to be called fn right_bottom_ui(&mut self, ui: &mut Ui) { if self.table.scroll_to_columns.is_some() || self.table.scroll_to_rows.is_some() { @@ -716,7 +725,7 @@ impl<'a, 'cb> SplitScrollDelegate for TableSplitScrollDelegate<'a, 'cb> { if let Some((row_range, align)) = &self.table.scroll_to_rows { let y_from_row_nr = |row_nr: u64| -> f32 { - let mut y = self.table.get_row_top_offset(row_nr); + let mut y = self.get_row_top_offset(row_nr); let sticky_height = self.header_row_y.last() - self.header_row_y.first(); if y < sticky_height {