diff --git a/egui_demo_lib/src/apps/demo/table_demo.rs b/egui_demo_lib/src/apps/demo/table_demo.rs index 641e003f..bb3c8ddd 100644 --- a/egui_demo_lib/src/apps/demo/table_demo.rs +++ b/egui_demo_lib/src/apps/demo/table_demo.rs @@ -1,5 +1,5 @@ use egui::TextStyle; -use egui_extras::{Size, StripBuilder, TableBuilder}; +use egui_extras::{Size, StripBuilder, TableBuilder, TableRow, TableRowBuilder}; /// Shows off a table with dynamic layout #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -75,7 +75,7 @@ impl TableDemo { }); }); }) - .body(|mut body| { + .body(|body| { if self.virtual_scroll { body.rows(text_height, 100_000, |row_index, mut row| { row.col(|ui| { @@ -91,37 +91,71 @@ impl TableDemo { }); }); } else { - for row_index in 0..20 { - let thick = row_index % 6 == 0; - let row_height = if thick { 30.0 } else { 18.0 }; - body.row(row_height, |mut row| { - row.col(|ui| { - ui.centered_and_justified(|ui| { - ui.label(row_index.to_string()); - }); - }); - row.col(|ui| { - ui.centered_and_justified(|ui| { - ui.label(clock_emoji(row_index)); - }); - }); - row.col(|ui| { - ui.centered_and_justified(|ui| { - ui.style_mut().wrap = Some(false); - if thick { - ui.heading("Extra thick row"); - } else { - ui.label("Normal row"); - } - }); - }); - }); - } + let row_builder = DemoRowBuilder { + row_count: 100000, + }; + body.heterogenous_rows(row_builder); } }); } } +struct DemoRowBuilder { + row_count: usize, +} + +struct DemoRows { + row_count: usize, + current_row: usize, +} + +impl Iterator for DemoRows { + type Item = f32; + + fn next(&mut self) -> Option { + if self.current_row < self.row_count { + let thick = self.current_row % 6 == 0; + self.current_row += 1; + Some(if thick { 30.0 } else { 18.0 }) + } else { + None + } + } +} + +impl TableRowBuilder for DemoRowBuilder { + fn row_heights(&self, _: &Vec) -> Box> { + Box::new(DemoRows{ + row_count: self.row_count, + current_row: 0, + }) + } + + fn populate_row(&self, index: usize, mut row: TableRow<'_, '_>) { + let thick = index % 6 == 0; + row.col(|ui| { + ui.centered_and_justified(|ui| { + ui.label(index.to_string()); + }); + }); + row.col(|ui| { + ui.centered_and_justified(|ui| { + ui.label(clock_emoji(index)); + }); + }); + row.col(|ui| { + ui.centered_and_justified(|ui| { + ui.style_mut().wrap = Some(false); + if thick { + ui.heading("Extra thick row"); + } else { + ui.label("Normal row"); + } + }); + }); + } +} + fn clock_emoji(row_index: usize) -> String { char::from_u32(0x1f550 + row_index as u32 % 24) .unwrap() diff --git a/egui_extras/src/table.rs b/egui_extras/src/table.rs index 6d7b8ec6..c5a6d685 100644 --- a/egui_extras/src/table.rs +++ b/egui_extras/src/table.rs @@ -351,7 +351,65 @@ pub struct TableBody<'a> { end_y: f32, } +pub trait TableRowBuilder { + fn row_heights(&self, widths: &Vec) -> Box + '_>; + fn populate_row(&self, index: usize, row: TableRow<'_, '_>); +} + impl<'a> TableBody<'a> { + pub fn heterogenous_rows(mut self, builder: impl TableRowBuilder) { + let mut heights = builder.row_heights(&self.widths); + + let max_height = self.end_y - self.start_y; + let delta = self.start_y - self.layout.current_y(); + + // cumulative height of all rows above those being displayed + let mut height_above_visible = 0.0; + // cumulative height of all rows below those being displayed + let mut height_below_visible = 0.0; + + let mut row_index = 0; + + let mut above_buffer_set = false; + let mut current_height = 0.0; // used to track height of visible rows + while let Some(height) = heights.next() { + // when delta is greater than height above 0, we need to increment the row index and + // update the height above visble with the current height then continue + if height_above_visible < delta { + row_index += 1; + height_above_visible += height; + continue; + } + + // if height above visible is > 0 here then we need to add a buffer to allow the table + // to calculate the "virtual" scrollbar position, reset height_above_visisble to 0 to + // prevent doing this more than once + if height_above_visible > 0.0 && !above_buffer_set { + self.buffer(height_above_visible); + above_buffer_set = true; // only set the upper buffer once + } + + // populate visible rows + if current_height < max_height { + let tr = TableRow { + layout: &mut self.layout, + widths: &self.widths, + striped: self.striped && self.row_nr % 2 == 0, + height: height, + }; + self.row_nr += 1; + builder.populate_row(row_index, tr); + row_index += 1; + current_height += height; + continue; + } + + // calculate below height + height_below_visible += height + } + self.buffer(height_below_visible); + } + /// Add rows with same height. /// /// Is a lot more performant than adding each individual row as non visible rows must not be rendered @@ -362,14 +420,7 @@ impl<'a> TableBody<'a> { if delta < 0.0 { start = (-delta / height).floor() as usize; - let skip_height = start as f32 * height; - TableRow { - layout: &mut self.layout, - widths: &self.widths, - striped: false, - height: skip_height, - } - .col(|_| ()); // advances the cursor + self.buffer(-delta); } let max_height = self.end_y - self.start_y; @@ -391,13 +442,7 @@ impl<'a> TableBody<'a> { if rows - end > 0 { let skip_height = (rows - end) as f32 * height; - TableRow { - layout: &mut self.layout, - widths: &self.widths, - striped: false, - height: skip_height, - } - .col(|_| ()); // advances the cursor + self.buffer(skip_height); } } @@ -412,6 +457,16 @@ impl<'a> TableBody<'a> { self.row_nr += 1; } + + pub fn buffer(&mut self, height: f32) { + TableRow { + layout: &mut self.layout, + widths: &self.widths, + striped: false, + height: height, + } + .col(|_| ()); // advances the cursor + } } impl<'a> Drop for TableBody<'a> {