egui_extras::Table: fix bugs in the virtual scrolling
This commit is contained in:
parent
56b127f209
commit
701ae3cb46
3 changed files with 65 additions and 72 deletions
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue