Skip to content

Commit

Permalink
Improve Table to be almost usable (#3)
Browse files Browse the repository at this point in the history
* fixes a bunch of bugs
* adds row prefetching
* `u64` row indices
  • Loading branch information
emilk authored Sep 5, 2024
1 parent e0561ce commit a03f30b
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 159 deletions.
21 changes: 8 additions & 13 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
[[package]]
name = "ecolor"
version = "0.28.1"
source = "git+https://github.com/emilk/egui?rev=7bac528d4d32f22fc0e2578198945a0ff3d876e2#7bac528d4d32f22fc0e2578198945a0ff3d876e2"
source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba"
dependencies = [
"bytemuck",
"emath",
Expand All @@ -407,7 +407,7 @@ dependencies = [
[[package]]
name = "eframe"
version = "0.28.1"
source = "git+https://github.com/emilk/egui?rev=7bac528d4d32f22fc0e2578198945a0ff3d876e2#7bac528d4d32f22fc0e2578198945a0ff3d876e2"
source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba"
dependencies = [
"ahash",
"bytemuck",
Expand Down Expand Up @@ -443,7 +443,7 @@ dependencies = [
[[package]]
name = "egui"
version = "0.28.1"
source = "git+https://github.com/emilk/egui?rev=7bac528d4d32f22fc0e2578198945a0ff3d876e2#7bac528d4d32f22fc0e2578198945a0ff3d876e2"
source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba"
dependencies = [
"accesskit",
"ahash",
Expand All @@ -458,7 +458,7 @@ dependencies = [
[[package]]
name = "egui-winit"
version = "0.28.1"
source = "git+https://github.com/emilk/egui?rev=7bac528d4d32f22fc0e2578198945a0ff3d876e2#7bac528d4d32f22fc0e2578198945a0ff3d876e2"
source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba"
dependencies = [
"ahash",
"arboard",
Expand All @@ -475,7 +475,7 @@ dependencies = [
[[package]]
name = "egui_glow"
version = "0.28.1"
source = "git+https://github.com/emilk/egui?rev=7bac528d4d32f22fc0e2578198945a0ff3d876e2#7bac528d4d32f22fc0e2578198945a0ff3d876e2"
source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba"
dependencies = [
"ahash",
"bytemuck",
Expand All @@ -499,7 +499,7 @@ dependencies = [
[[package]]
name = "emath"
version = "0.28.1"
source = "git+https://github.com/emilk/egui?rev=7bac528d4d32f22fc0e2578198945a0ff3d876e2#7bac528d4d32f22fc0e2578198945a0ff3d876e2"
source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba"
dependencies = [
"bytemuck",
"serde",
Expand Down Expand Up @@ -532,7 +532,7 @@ dependencies = [
[[package]]
name = "epaint"
version = "0.28.1"
source = "git+https://github.com/emilk/egui?rev=7bac528d4d32f22fc0e2578198945a0ff3d876e2#7bac528d4d32f22fc0e2578198945a0ff3d876e2"
source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba"
dependencies = [
"ab_glyph",
"ahash",
Expand All @@ -549,7 +549,7 @@ dependencies = [
[[package]]
name = "epaint_default_fonts"
version = "0.28.1"
source = "git+https://github.com/emilk/egui?rev=7bac528d4d32f22fc0e2578198945a0ff3d876e2#7bac528d4d32f22fc0e2578198945a0ff3d876e2"
source = "git+https://github.com/emilk/egui?rev=2be93aca5d0db973ab163267fb4162363786ffba#2be93aca5d0db973ab163267fb4162363786ffba"

[[package]]
name = "equivalent"
Expand Down Expand Up @@ -2328,8 +2328,3 @@ dependencies = [
"quote",
"syn",
]

[[patch.unused]]
name = "egui_extras"
version = "0.28.1"
source = "git+https://github.com/emilk/egui?rev=7bac528d4d32f22fc0e2578198945a0ff3d876e2#7bac528d4d32f22fc0e2578198945a0ff3d876e2"
11 changes: 4 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,19 @@ egui_table = { version = "0.28.1", path = "egui_table", default-features = false
document-features = " 0.2.10"
eframe = { version = "0.28.1", default-features = false }
egui = { version = "0.28.1", default-features = false }
egui_extras = { version = "0.28.1", default-features = false }
emath = { version = "0.28.1", default-features = false }
serde = { version = "1", default-features = false, features = ["derive"] }
vec1 = { version = "1.12.1", default-features = false }


[patch.crates-io]
# If you want to use the bleeding edge version of egui and eframe:
eframe = { git = "https://github.com/emilk/egui", rev = "7bac528d4d32f22fc0e2578198945a0ff3d876e2" } # egui master 2024-09-02
egui = { git = "https://github.com/emilk/egui", rev = "7bac528d4d32f22fc0e2578198945a0ff3d876e2" } # egui master 2024-09-02
egui_extras = { git = "https://github.com/emilk/egui", rev = "7bac528d4d32f22fc0e2578198945a0ff3d876e2" } # egui master 2024-09-02
eframe = { git = "https://github.com/emilk/egui", rev = "2be93aca5d0db973ab163267fb4162363786ffba" } # egui master 2024-09-05
egui = { git = "https://github.com/emilk/egui", rev = "2be93aca5d0db973ab163267fb4162363786ffba" } # egui master 2024-09-05

# If you fork https://github.com/emilk/egui you can test with:
# egui = { path = "../egui/crates/egui" }
# egui_extras = { path = "../egui/crates/egui_extras" }
# eframe = { path = "../egui/crates/eframe" }
# egui = { path = "../../egui/crates/egui" }
# eframe = { path = "../../egui/crates/eframe" }


[workspace.lints.rust]
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
Table viewer for [egui](https://www.egui.rs/).

## Status
Work-in-progress.
Work-in-progress. Pre-release.


### TODO/Bugs
* Hierarchical headers
* Fix auto-sizing on parent resize
* Test with `egui::Sides`
* Test with truncating content
* Go through TODOs


### Testing
Expand Down
2 changes: 1 addition & 1 deletion demo/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fn main() -> eframe::Result {
..Default::default()
};
eframe::run_native(
"eframe template",
"egui_table demo",
native_options,
Box::new(|cc| Ok(Box::new(demo::DemoApp::new(cc)))),
)
Expand Down
95 changes: 71 additions & 24 deletions demo/src/table_demo.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,56 @@
use egui_table::{AutoSizeMode, Column, Table, TableDelegate};
use std::ops::Range;

use egui_table::{AutoSizeMode, CellInfo, Column, Table, TableDelegate, TableState};

#[derive(serde::Deserialize, serde::Serialize)]
pub struct TableDemo {
num_columns: usize,
num_rows: usize,
num_rows: u64,
num_sticky_cols: usize,
default_column: Column,
auto_size_mode: AutoSizeMode,
prefetched_row_ranges: Vec<Range<u64>>,
}

impl Default for TableDemo {
fn default() -> Self {
Self {
num_columns: 10,
num_rows: 100,
num_sticky_cols: 2,
num_sticky_cols: 1,
default_column: Column::new(100.0, 10.0..=500.0),
auto_size_mode: AutoSizeMode::default(),
prefetched_row_ranges: vec![],
}
}
}

impl TableDemo {
fn was_prefetched(&self, row_nr: u64) -> bool {
self.prefetched_row_ranges
.iter()
.any(|range| range.contains(&row_nr))
}
}

impl TableDelegate for TableDemo {
fn cell_ui(&mut self, ui: &mut egui::Ui, row_nr: usize, col_nr: usize) {
fn prefetch_rows(&mut self, row_numbers: std::ops::Range<u64>) {
self.prefetched_row_ranges.push(row_numbers);
}

fn cell_ui(&mut self, ui: &mut egui::Ui, cell: &CellInfo) {
let CellInfo { row_nr, col_nr, .. } = *cell;

ui.add_space(4.0);

if !self.was_prefetched(row_nr) {
ui.painter()
.rect_filled(ui.max_rect(), 0.0, ui.visuals().error_fg_color);
ui.label("ERROR: row not prefetched");
log::warn!("Was asked to show row {row_nr} which was not prefetched! This is a bug.");
return;
}

if row_nr == 0 {
ui.heading(format!("Column {col_nr}"));
} else {
Expand All @@ -41,42 +67,37 @@ impl TableDelegate for TableDemo {

impl TableDemo {
pub fn ui(&mut self, ui: &mut egui::Ui) {
egui::Grid::new("settings").num_columns(2).show(ui, |ui| {
egui::Grid::new("settings").show(ui, |ui| {
ui.label("Columns");
ui.add(egui::DragValue::new(&mut self.num_columns).speed(1.0));
ui.add(egui::DragValue::new(&mut self.num_columns));
ui.end_row();

ui.label("Rows");
let speed = 1.0 + 0.05 * self.num_rows as f32;
ui.add(
egui::DragValue::new(&mut self.num_rows)
.speed(speed)
.range(0..=10_000),
);
ui.horizontal(|ui| {
ui.add(
egui::DragValue::new(&mut self.num_rows)
.speed(speed)
.range(0..=10_000),
);
ui.weak("(includes header row)");
});
ui.end_row();

ui.label("Sticky columns");
ui.add(egui::DragValue::new(&mut self.num_sticky_cols).speed(1.0));
ui.add(egui::DragValue::new(&mut self.num_sticky_cols));
ui.end_row();

ui.label("Default column width");
ui.add(egui::DragValue::new(&mut self.default_column.current).speed(1.0));
ui.add(egui::DragValue::new(&mut self.default_column.current));
ui.end_row();

ui.label("Column width range");
ui.horizontal(|ui| {
let range = &mut self.default_column.range;
ui.add(
egui::DragValue::new(&mut range.min)
.speed(1.0)
.range(0.0..=range.max),
);
ui.add(egui::DragValue::new(&mut range.min).range(0.0..=range.max));
ui.label("to");
ui.add(
egui::DragValue::new(&mut range.max)
.speed(1.0)
.range(range.min..=1000.0),
);
ui.add(egui::DragValue::new(&mut range.max).range(range.min..=1000.0));
});
ui.end_row();

Expand All @@ -93,11 +114,37 @@ impl TableDemo {
ui.end_row();
});

let id_salt = egui::Id::new("table_demo");
let state_id = TableState::id(ui, id_salt); // Note: must be here (in the correct outer `ui` scope) to be correct.

ui.horizontal(|ui| {
if ui.button("Reset settings").clicked() {
*self = Self::default();
}
if ui.button("Reset state").clicked() {
debug_assert!(
TableState::load(ui.ctx(), state_id).is_some(),
"Wrong state_id"
);
TableState::reset(ui.ctx(), state_id);
}
});

ui.separator();

ui.horizontal(|ui| {
ui.label("Prefetched row ranges:");
for range in &self.prefetched_row_ranges {
ui.label(format!("{}..{}", range.start, range.end));
}
self.prefetched_row_ranges.clear();
});

ui.separator();

Table {
columns: vec![self.default_column; self.num_columns],
id_salt: egui::Id::new("table_demo"),
id_salt,
num_sticky_cols: self.num_sticky_cols,
sticky_row_heights: vec![20.0; 1],
row_height: 16.0,
Expand Down
2 changes: 1 addition & 1 deletion egui_table/src/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl Default for Column {
fn default() -> Self {
Self {
current: 100.0,
range: Rangef::new(0.0, f32::INFINITY),
range: Rangef::new(4.0, f32::INFINITY),
id: None,
resizable: true,
auto_size_this_frame: false,
Expand Down
2 changes: 1 addition & 1 deletion egui_table/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ mod table;

pub use columns::Column;
pub use split_scroll::{SplitScroll, SplitScrollDelegate};
pub use table::{AutoSizeMode, Table, TableDelegate, TableState};
pub use table::{AutoSizeMode, CellInfo, Table, TableDelegate, TableState};
33 changes: 23 additions & 10 deletions egui_table/src/split_scroll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pub trait SplitScrollDelegate {

/// The fully scrollable portion.
fn right_bottom_ui(&mut self, ui: &mut Ui);

/// Called last.
fn finish(&mut self, _ui: &mut Ui) {}
}

impl SplitScroll {
Expand All @@ -66,33 +69,41 @@ impl SplitScroll {
ui.scope(|ui| {
ui.visuals_mut().clip_rect_margin = 0.0; // Everything else looks awful

let full_clip_rect = ui.clip_rect();

let mut rect = ui.cursor();
rect.max = rect.min + fixed_size + scroll_outer_size;
ui.shrink_clip_rect(rect);

let bottom_right_rect = Rect::from_min_max(rect.min + fixed_size, rect.max);

let scroll_offset = {
// RIGHT BOTTOM: fully scrollable.

// Entire thing is a scroll region.
// The entire thing is a `ScrollArea` that we then paint over.
// PROBLEM: scroll bars show up at the full rect, instead of just the bottom-right.
// We could add something like `ScrollArea::with_scroll_bar_rect(bottom_right_rect)`

let mut scroll_ui = ui.new_child(UiBuilder::new().max_rect(rect));
egui::ScrollArea::new(scroll_enabled)
.show(&mut scroll_ui, |ui| {
.auto_shrink(false)
.scroll_bar_rect(bottom_right_rect)
.show_viewport(&mut scroll_ui, |ui, scroll_offset| {
ui.set_min_size(fixed_size + scroll_content_size);

let mut shrunk_rect = ui.max_rect();
shrunk_rect.min += fixed_size;

let mut shrunk_ui = ui.new_child(UiBuilder::new().max_rect(shrunk_rect));
shrunk_ui.set_clip_rect(full_clip_rect.intersect(bottom_right_rect));
shrunk_ui.shrink_clip_rect(bottom_right_rect);
delegate.right_bottom_ui(&mut shrunk_ui);

// It is very important that the scroll offset is synced between the
// right-bottom contents of the real scroll area,
// and the fake scroll areas we are painting later.
// The scroll offset that `ScrollArea` returns could be a newer one
// than was used for rendering, so we use the one _actually_ used for rendering instead:
scroll_offset.min
})
.state
.offset
.inner
};

{
Expand All @@ -101,7 +112,7 @@ impl SplitScroll {
.with_max_x(rect.left() + fixed_size.x)
.with_max_y(rect.top() + fixed_size.y);
let mut left_top_ui = ui.new_child(UiBuilder::new().max_rect(left_top_rect));
left_top_ui.set_clip_rect(full_clip_rect.intersect(left_top_rect));
left_top_ui.shrink_clip_rect(left_top_rect);
delegate.left_top_ui(&mut left_top_ui);
}

Expand All @@ -116,7 +127,7 @@ impl SplitScroll {
);
let mut right_top_ui =
ui.new_child(UiBuilder::new().max_rect(right_top_content_rect));
right_top_ui.set_clip_rect(full_clip_rect.intersect(right_top_outer_rect));
right_top_ui.shrink_clip_rect(right_top_outer_rect);
delegate.right_top_ui(&mut right_top_ui);
}

Expand All @@ -131,9 +142,11 @@ impl SplitScroll {
);
let mut left_bottom_ui =
ui.new_child(UiBuilder::new().max_rect(left_bottom_content_rect));
left_bottom_ui.set_clip_rect(full_clip_rect.intersect(left_bottom_outer_rect));
left_bottom_ui.shrink_clip_rect(left_bottom_outer_rect);
delegate.left_bottom_ui(&mut left_bottom_ui);
}

delegate.finish(ui);
});
}
}
Loading

0 comments on commit a03f30b

Please sign in to comment.