Compare commits
16 commits
master
...
enable-vir
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ea5941c3a1 | ||
![]() |
a17519fb34 | ||
![]() |
603ce062b3 | ||
![]() |
1d7f698669 | ||
![]() |
84e0700459 | ||
![]() |
2d6ca4265f | ||
![]() |
b82fc89462 | ||
![]() |
51a98d586b | ||
![]() |
91d78fa2e9 | ||
![]() |
1c5f93322d | ||
![]() |
7706a91974 | ||
![]() |
2f22f41185 | ||
![]() |
c2df572dd1 | ||
![]() |
44bd8c1cc4 | ||
![]() |
ab4930fde7 | ||
![]() |
e81a92d32d |
2 changed files with 227 additions and 34 deletions
|
@ -1,12 +1,24 @@
|
|||
use egui::TextStyle;
|
||||
use egui_extras::{Size, StripBuilder, TableBuilder};
|
||||
use egui_extras::{Size, StripBuilder, TableBuilder, TableRow};
|
||||
|
||||
/// Shows off a table with dynamic layout
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[derive(Default)]
|
||||
pub struct TableDemo {
|
||||
heterogeneous_rows: bool,
|
||||
virtual_scroll: bool,
|
||||
resizable: bool,
|
||||
num_rows: usize,
|
||||
}
|
||||
|
||||
impl Default for TableDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
heterogeneous_rows: true,
|
||||
virtual_scroll: false,
|
||||
resizable: true,
|
||||
num_rows: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Demo for TableDemo {
|
||||
|
@ -28,8 +40,23 @@ impl super::Demo for TableDemo {
|
|||
|
||||
impl super::View for TableDemo {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.checkbox(&mut self.virtual_scroll, "Virtual scroll");
|
||||
ui.checkbox(&mut self.resizable, "Resizable columns");
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.checkbox(&mut self.resizable, "Resizable columns");
|
||||
ui.checkbox(&mut self.virtual_scroll, "Virtual Scroll");
|
||||
if self.virtual_scroll {
|
||||
ui.checkbox(&mut self.heterogeneous_rows, "Heterogeneous row heights");
|
||||
}
|
||||
});
|
||||
|
||||
if self.virtual_scroll {
|
||||
ui.add(
|
||||
egui::Slider::new(&mut self.num_rows, 0..=100_000)
|
||||
.logarithmic(true)
|
||||
.text("Num rows"),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Leave room for the source code link after the table demo:
|
||||
StripBuilder::new(ui)
|
||||
|
@ -77,19 +104,25 @@ impl TableDemo {
|
|||
})
|
||||
.body(|mut body| {
|
||||
if self.virtual_scroll {
|
||||
body.rows(text_height, 100_000, |row_index, mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label(row_index.to_string());
|
||||
if !self.heterogeneous_rows {
|
||||
body.rows(text_height, self.num_rows, |row_index, mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label(row_index.to_string());
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(clock_emoji(row_index));
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.add(
|
||||
egui::Label::new("Thousands of rows of even height")
|
||||
.wrap(false),
|
||||
);
|
||||
});
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(clock_emoji(row_index));
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.add(
|
||||
egui::Label::new("Thousands of rows of even height").wrap(false),
|
||||
);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
let rows = DemoRows::new(self.num_rows);
|
||||
body.heterogeneous_rows(rows, DemoRows::populate_row);
|
||||
}
|
||||
} else {
|
||||
for row_index in 0..20 {
|
||||
let thick = row_index % 6 == 0;
|
||||
|
@ -122,6 +155,58 @@ impl TableDemo {
|
|||
}
|
||||
}
|
||||
|
||||
struct DemoRows {
|
||||
row_count: usize,
|
||||
current_row: usize,
|
||||
}
|
||||
|
||||
impl DemoRows {
|
||||
fn new(row_count: usize) -> Self {
|
||||
Self {
|
||||
row_count,
|
||||
current_row: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn populate_row(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");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for DemoRows {
|
||||
type Item = f32;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clock_emoji(row_index: usize) -> String {
|
||||
char::from_u32(0x1f550 + row_index as u32 % 24)
|
||||
.unwrap()
|
||||
|
|
|
@ -352,24 +352,126 @@ pub struct TableBody<'a> {
|
|||
}
|
||||
|
||||
impl<'a> TableBody<'a> {
|
||||
fn y_progress(&self) -> f32 {
|
||||
self.start_y - self.layout.current_y()
|
||||
}
|
||||
|
||||
/// Return a vector containing all column widths for this table body.
|
||||
///
|
||||
/// This is primarily meant for use with [`TableBody::heterogeneous_rows`] in cases where row
|
||||
/// heights are expected to according to the width of one or more cells -- for example, if text
|
||||
/// is wrapped rather than clippped within the cell.
|
||||
pub fn widths(&self) -> &[f32] {
|
||||
&self.widths
|
||||
}
|
||||
|
||||
/// Add rows with varying heights.
|
||||
///
|
||||
/// This takes a very slight performance hit compared to [`TableBody::rows`] due to the need to
|
||||
/// iterate over all row heights in to calculate the virtual table height above and below the
|
||||
/// visible region, but it is many orders of magnitude more performant than adding individual
|
||||
/// heterogenously-sized rows using [`TableBody::row`] at the cost of the additional complexity
|
||||
/// that comes with pre-calculating row heights and representing them as an iterator.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// use egui_extras::{TableBuilder, Size};
|
||||
/// TableBuilder::new(ui)
|
||||
/// .column(Size::remainder().at_least(100.0))
|
||||
/// .body(|mut body| {
|
||||
/// let row_heights: Vec<f32> = vec![60.0, 18.0, 31.0, 240.0];
|
||||
/// body.heterogeneous_rows(row_heights.into_iter(), |row_index, mut row| {
|
||||
/// let thick = row_index % 6 == 0;
|
||||
/// row.col(|ui| {
|
||||
/// ui.centered_and_justified(|ui| {
|
||||
/// ui.label(row_index.to_string());
|
||||
/// });
|
||||
/// });
|
||||
/// });
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn heterogeneous_rows(
|
||||
&mut self,
|
||||
heights: impl Iterator<Item = f32>,
|
||||
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
|
||||
// iterator with the boolean built in based on the enumerated index of the iterator element
|
||||
let mut striped_heights = heights
|
||||
.enumerate()
|
||||
.map(|(index, height)| (index, index % 2 == 0, height));
|
||||
|
||||
let max_height = self.end_y - self.start_y;
|
||||
let y_progress = self.y_progress();
|
||||
|
||||
// cumulative height of all rows above those being displayed
|
||||
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.
|
||||
// because this row is meant to slide under the top bound of the visual table we calculate
|
||||
// height_of_first_row + height_above_visible >= y_progress as our break condition rather
|
||||
// than just height_above_visible >= y_progress
|
||||
while let Some((row_index, striped, height)) = striped_heights.next() {
|
||||
if height as f64 + height_above_visible >= y_progress as f64 {
|
||||
self.add_buffer(height_above_visible as f32);
|
||||
let tr = TableRow {
|
||||
layout: &mut self.layout,
|
||||
widths: &self.widths,
|
||||
striped: self.striped && striped,
|
||||
height,
|
||||
};
|
||||
self.row_nr += 1;
|
||||
populate_row(row_index, tr);
|
||||
break;
|
||||
}
|
||||
height_above_visible += height as f64;
|
||||
}
|
||||
|
||||
// populate visible rows, including the final row that should slide under the bottom bound
|
||||
// of the visible table.
|
||||
let mut current_height: f64 = 0.0;
|
||||
while let Some((row_index, striped, height)) = striped_heights.next() {
|
||||
if height as f64 + current_height > max_height as f64 {
|
||||
break;
|
||||
}
|
||||
let tr = TableRow {
|
||||
layout: &mut self.layout,
|
||||
widths: &self.widths,
|
||||
striped: self.striped && striped,
|
||||
height,
|
||||
};
|
||||
self.row_nr += 1;
|
||||
populate_row(row_index, tr);
|
||||
current_height += height as f64;
|
||||
}
|
||||
|
||||
// calculate height below the visible table range
|
||||
while let Some((_, _, height)) = striped_heights.next() {
|
||||
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 {
|
||||
self.add_buffer(height_below_visible as f32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add rows with same height.
|
||||
///
|
||||
/// Is a lot more performant than adding each individual row as non visible rows must not be rendered
|
||||
pub fn rows(mut self, height: f32, rows: usize, mut row: impl FnMut(usize, TableRow<'_, '_>)) {
|
||||
let delta = self.layout.current_y() - self.start_y;
|
||||
let y_progress = self.y_progress();
|
||||
let mut start = 0;
|
||||
|
||||
if delta < 0.0 {
|
||||
start = (-delta / height).floor() as usize;
|
||||
if y_progress > 0.0 {
|
||||
start = (y_progress / 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.add_buffer(y_progress);
|
||||
}
|
||||
|
||||
let max_height = self.end_y - self.start_y;
|
||||
|
@ -391,13 +493,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.add_buffer(skip_height);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,6 +508,18 @@ impl<'a> TableBody<'a> {
|
|||
|
||||
self.row_nr += 1;
|
||||
}
|
||||
|
||||
// Create a table row buffer of the given height to represent the non-visible portion of the
|
||||
// table.
|
||||
fn add_buffer(&mut self, height: f32) {
|
||||
TableRow {
|
||||
layout: &mut self.layout,
|
||||
widths: &self.widths,
|
||||
striped: false,
|
||||
height,
|
||||
}
|
||||
.col(|_| ()); // advances the cursor
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for TableBody<'a> {
|
||||
|
|
Loading…
Reference in a new issue