egui_extras::Table: fix bugs in the virtual scrolling

This commit is contained in:
Emil Ernerfeldt 2022-04-11 17:54:57 +02:00
parent 56b127f209
commit 701ae3cb46
3 changed files with 65 additions and 72 deletions

View file

@ -458,9 +458,7 @@ impl ScrollArea {
self.show_viewport(ui, |ui, viewport| { self.show_viewport(ui, |ui, viewport| {
ui.set_height((row_height_with_spacing * total_rows as f32 - spacing.y).at_least(0.0)); ui.set_height((row_height_with_spacing * total_rows as f32 - spacing.y).at_least(0.0));
let min_row = (viewport.min.y / row_height_with_spacing) let min_row = (viewport.min.y / row_height_with_spacing).floor() as usize;
.floor()
.at_least(0.0) as usize;
let max_row = (viewport.max.y / row_height_with_spacing).ceil() as usize + 1; let max_row = (viewport.max.y / row_height_with_spacing).ceil() as usize + 1;
let max_row = max_row.at_most(total_rows); let max_row = max_row.at_most(total_rows);

View file

@ -26,9 +26,9 @@ pub(crate) enum CellDirection {
/// Positions cells in `[CellDirection]` and starts a new line on `[StripLayout::end_line]` /// Positions cells in `[CellDirection]` and starts a new line on `[StripLayout::end_line]`
pub struct StripLayout<'l> { pub struct StripLayout<'l> {
ui: &'l mut Ui, pub(crate) ui: &'l mut Ui,
direction: CellDirection, direction: CellDirection,
rect: Rect, pub(crate) rect: Rect,
cursor: Pos2, cursor: Pos2,
max: Pos2, max: Pos2,
pub(crate) clip: bool, pub(crate) clip: bool,
@ -55,11 +55,6 @@ impl<'l> StripLayout<'l> {
cell_layout, cell_layout,
} }
} }
pub fn current_y(&self) -> f32 {
self.rect.top()
}
fn cell_rect(&self, width: &CellSize, height: &CellSize) -> Rect { fn cell_rect(&self, width: &CellSize, height: &CellSize) -> Rect {
Rect { Rect {
min: self.cursor, min: self.cursor,
@ -138,6 +133,14 @@ impl<'l> StripLayout<'l> {
} }
} }
/// Skip a lot of space.
pub(crate) fn skip_space(&mut self, delta: egui::Vec2) {
let before = self.cursor;
self.cursor += delta;
let rect = Rect::from_two_pos(before, self.cursor);
self.ui.allocate_rect(rect, Sense::hover());
}
fn cell(&mut self, rect: Rect, add_contents: impl FnOnce(&mut Ui)) -> Rect { fn cell(&mut self, rect: Rect, add_contents: impl FnOnce(&mut Ui)) -> Rect {
let mut child_ui = self.ui.child_ui(rect, self.cell_layout); let mut child_ui = self.ui.child_ui(rect, self.cell_layout);

View file

@ -379,8 +379,8 @@ pub struct TableBody<'a> {
} }
impl<'a> TableBody<'a> { impl<'a> TableBody<'a> {
fn y_progress(&self) -> f32 { fn scroll_offset_y(&self) -> f32 {
self.start_y - self.layout.current_y() self.start_y - self.layout.rect.top()
} }
/// Return a vector containing all column widths for this table body. /// Return a vector containing all column widths for this table body.
@ -430,21 +430,31 @@ impl<'a> TableBody<'a> {
/// }); /// });
/// # }); /// # });
/// ``` /// ```
pub fn rows(mut self, height: f32, rows: usize, mut row: impl FnMut(usize, TableRow<'_, '_>)) { pub fn rows(
let y_progress = self.y_progress(); mut self,
let mut start = 0; row_height_sans_spacing: f32,
total_rows: usize,
mut row: impl FnMut(usize, TableRow<'_, '_>),
) {
let spacing = self.layout.ui.spacing().item_spacing;
let row_height_with_spacing = row_height_sans_spacing + spacing.y;
if y_progress > 0.0 { let scroll_offset_y = self
start = (y_progress / height).floor() as usize; .scroll_offset_y()
.min(total_rows as f32 * row_height_with_spacing);
let max_height = self.end_y - self.start_y;
let mut min_row = 0;
self.add_buffer(y_progress); if scroll_offset_y > 0.0 {
min_row = (scroll_offset_y / row_height_with_spacing).floor() as usize;
self.add_buffer(min_row as f32 * row_height_with_spacing);
} }
let max_height = self.end_y - self.start_y; let max_row =
let count = (max_height / height).ceil() as usize; ((scroll_offset_y + max_height) / row_height_with_spacing).ceil() as usize + 1;
let end = rows.min(start + count); let max_row = max_row.min(total_rows);
for idx in start..end { for idx in min_row..max_row {
row( row(
idx, idx,
TableRow { TableRow {
@ -452,15 +462,14 @@ impl<'a> TableBody<'a> {
widths: &self.widths, widths: &self.widths,
width_index: 0, width_index: 0,
striped: self.striped && idx % 2 == 0, striped: self.striped && idx % 2 == 0,
height, height: row_height_sans_spacing,
}, },
); );
} }
if rows - end > 0 { if total_rows - max_row > 0 {
let skip_height = (rows - end) as f32 * height; let skip_height = (total_rows - max_row) as f32 * row_height_with_spacing;
self.add_buffer(skip_height - spacing.y);
self.add_buffer(skip_height);
} }
} }
@ -496,68 +505,58 @@ impl<'a> TableBody<'a> {
heights: impl Iterator<Item = f32>, heights: impl Iterator<Item = f32>,
mut populate_row: impl FnMut(usize, TableRow<'_, '_>), mut populate_row: impl FnMut(usize, TableRow<'_, '_>),
) { ) {
// in order for each row to retain its striped color as the table is scrolled, we need an let spacing = self.layout.ui.spacing().item_spacing;
// iterator with the boolean built in based on the enumerated index of the iterator element let mut enumerated_heights = heights.enumerate();
let mut striped_heights = heights
.enumerate()
.map(|(index, height)| (index, index % 2 == 0, height));
let max_height = self.end_y - self.start_y; let max_height = self.end_y - self.start_y;
let y_progress = self.y_progress(); let scroll_offset_y = self.scroll_offset_y() as f64;
// cumulative height of all rows above those being displayed let mut cursor_y: f64 = 0.0;
let mut height_above_visible: f64 = 0.0;
// cumulative height of all rows below those being displayed
let mut height_below_visible: f64 = 0.0;
// calculate height above visible table range and populate the first non-virtual row. // Skip the invisible rows, and populate the first non-virtual row.
// because this row is meant to slide under the top bound of the visual table we calculate for (row_index, row_height) in &mut enumerated_heights {
// height_of_first_row + height_above_visible >= y_progress as our break condition rather let old_cursor_y = cursor_y;
// than just height_above_visible >= y_progress cursor_y += (row_height + spacing.y) as f64;
for (row_index, striped, height) in &mut striped_heights { if cursor_y >= scroll_offset_y {
if height as f64 + height_above_visible >= y_progress as f64 { // This row is visible:
self.add_buffer(height_above_visible as f32); self.add_buffer(old_cursor_y as f32);
let tr = TableRow { let tr = TableRow {
layout: &mut self.layout, layout: &mut self.layout,
widths: &self.widths, widths: &self.widths,
width_index: 0, width_index: 0,
striped: self.striped && striped, striped: self.striped && row_index % 2 == 0,
height, height: row_height,
}; };
self.row_nr += 1;
populate_row(row_index, tr); populate_row(row_index, tr);
break; break;
} }
height_above_visible += height as f64;
} }
// populate visible rows, including the final row that should slide under the bottom bound // populate visible rows:
// of the visible table. for (row_index, row_height) in &mut enumerated_heights {
let mut current_height: f64 = 0.0;
for (row_index, striped, height) in &mut striped_heights {
if height as f64 + current_height > max_height as f64 {
break;
}
let tr = TableRow { let tr = TableRow {
layout: &mut self.layout, layout: &mut self.layout,
widths: &self.widths, widths: &self.widths,
width_index: 0, width_index: 0,
striped: self.striped && striped, striped: self.striped && row_index % 2 == 0,
height, height: row_height,
}; };
self.row_nr += 1;
populate_row(row_index, tr); populate_row(row_index, tr);
current_height += height as f64; cursor_y += (row_height + spacing.y) as f64;
if cursor_y > scroll_offset_y + max_height as f64 {
break;
}
} }
// calculate height below the visible table range // calculate height below the visible table range:
for (_, _, height) in striped_heights { let mut height_below_visible: f64 = 0.0;
for (_, height) in enumerated_heights {
height_below_visible += height as f64; height_below_visible += height as f64;
} }
// if height below visible is > 0 here then we need to add a buffer to allow the table to
// accurately calculate the "virtual" scrollbar position
if height_below_visible > 0.0 { if height_below_visible > 0.0 {
// we need to add a buffer to allow the table to
// accurately calculate the scrollbar position
self.add_buffer(height_below_visible as f32); self.add_buffer(height_below_visible as f32);
} }
} }
@ -565,14 +564,7 @@ impl<'a> TableBody<'a> {
// Create a table row buffer of the given height to represent the non-visible portion of the // Create a table row buffer of the given height to represent the non-visible portion of the
// table. // table.
fn add_buffer(&mut self, height: f32) { fn add_buffer(&mut self, height: f32) {
TableRow { self.layout.skip_space(egui::vec2(0.0, height));
layout: &mut self.layout,
widths: &self.widths,
width_index: 0,
striped: false,
height,
}
.col(|_| ()); // advances the cursor
} }
} }