Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move row_top_offset to the TableDelegate #14

Merged
merged 5 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 14 additions & 18 deletions demo/src/table_demo.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::BTreeMap;

use egui::{Align2, Id, Margin, NumExt, Sense, Vec2};
use egui::{Align2, Context, Id, Margin, NumExt, Sense, Vec2};

#[derive(serde::Deserialize, serde::Serialize)]
pub struct TableDemo {
Expand Down Expand Up @@ -177,6 +177,19 @@ impl egui_table::TableDelegate for TableDemo {
self.cell_content_ui(row_nr, col_nr, ui);
});
}

fn row_top_offset(&self, ctx: &Context, _table_id: Id, row_nr: u64) -> f32 {
let fully_expanded_row_height = 48.0;

self.is_row_expanded
.range(0..row_nr)
.map(|(expanded_row_nr, expanded)| {
let how_expanded = ctx.animate_bool(Id::new(expanded_row_nr), *expanded);
how_expanded * fully_expanded_row_height
})
.sum::<f32>()
+ row_nr as f32 * self.row_height
}
}

impl TableDemo {
Expand Down Expand Up @@ -306,11 +319,6 @@ impl TableDemo {

ui.separator();

// TODO: avoid this:
let egui_ctx = ui.ctx().clone();
let is_row_expanded = self.is_row_expanded.clone();
let row_height = self.row_height;

let mut table = egui_table::Table::new()
.id_salt(id_salt)
.num_rows(self.num_rows)
Expand All @@ -323,18 +331,6 @@ impl TableDemo {
},
egui_table::HeaderRow::new(self.top_row_height),
])
.row_top_offset(move |row_nr| -> f32 {
let fully_expanded_row_height = 48.0;
is_row_expanded
.range(0..row_nr)
.map(|(expanded_row_nr, expanded)| {
let how_expanded =
egui_ctx.animate_bool(Id::new(expanded_row_nr), *expanded);
how_expanded * fully_expanded_row_height
})
.sum::<f32>()
+ row_nr as f32 * row_height
})
.auto_size_mode(self.auto_size_mode);

if let Some(scroll_to_column) = scroll_to_column {
Expand Down
119 changes: 64 additions & 55 deletions egui_table/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::{
ops::{Range, RangeInclusive},
};

use egui::{vec2, Align, Id, IdMap, NumExt as _, Rangef, Rect, Ui, UiBuilder, Vec2, Vec2b};
use egui::{
vec2, Align, Context, Id, IdMap, NumExt as _, Rangef, Rect, Ui, UiBuilder, Vec2, Vec2b,
};
use vec1::Vec1;

use crate::{columns::Column, SplitScroll, SplitScrollDelegate};
Expand Down Expand Up @@ -88,7 +90,7 @@ impl HeaderRow {
/// * Does not add any margins to cells. Add it yourself with [`egui::Frame`].
/// * Does not wrap cells in scroll areas. Do that yourself.
/// * Doesn't paint any guide-lines for the rows. Paint them yourself.
pub struct Table<'cb> {
pub struct Table {
/// The columns of the table.
columns: Vec<Column>,

Expand All @@ -104,9 +106,6 @@ pub struct Table<'cb> {
/// The count and parameters of the sticky (non-scrolling) header rows.
headers: Vec<HeaderRow>,

/// Height of the non-sticky rows.
row_top_offset: Box<dyn Fn(u64) -> f32 + 'cb>,

/// Total number of rows (sticky + non-sticky).
num_rows: u64,

Expand All @@ -117,14 +116,13 @@ pub struct Table<'cb> {
scroll_to_rows: Option<(RangeInclusive<u64>, Option<Align>)>,
}

impl<'cb> Default for Table<'cb> {
impl Default for Table {
fn default() -> Self {
Self {
columns: vec![],
id_salt: Id::new("table"),
num_sticky_cols: 0,
headers: vec![HeaderRow::new(16.0)],
row_top_offset: Box::new(|row_nr| 16.0 * row_nr as f32),
num_rows: 0,
auto_size_mode: AutoSizeMode::default(),
scroll_to_columns: None,
Expand Down Expand Up @@ -181,9 +179,26 @@ pub trait TableDelegate {
///
/// The [`CellInfo::row_nr`] is ignoring header rows.
fn cell_ui(&mut self, ui: &mut Ui, cell: &CellInfo);

/// Compute the offset for the top of the given row.
///
/// Implement this for arbitrary row heights. The default implementation uses
/// [`Self::default_row_height`].
///
/// Note: must always return 0.0 for `row_nr = 0`.
fn row_top_offset(&self, _ctx: &Context, _table_id: Id, row_nr: u64) -> f32 {
row_nr as f32 * self.default_row_height()
}

/// Default row height.
///
/// This is used by the default implementation of [`Self::row_top_offset`].
fn default_row_height(&self) -> f32 {
20.0
}
}

impl<'cb> Table<'cb> {
impl Table {
/// Create a new table, with no columns and no headers, and zero rows.
#[inline]
pub fn new() -> Self {
Expand Down Expand Up @@ -230,38 +245,6 @@ impl<'cb> Table<'cb> {
self
}

/// Height of the non-sticky rows.
///
/// This is for tables with rows of uniform height.
/// If you want heterogenous rwo heights, use [`Self::row_top_offset`] instead.
#[inline]
pub fn row_height(mut self, row_height: f32) -> Self {
self.row_top_offset = Box::new(move |row_nr| row_nr as f32 * row_height);
self
}

/// Sets up where the bottom of each row is.
///
/// This is for when you have heterogenous row heights,
/// i.e. different rows has different heights.
///
/// The function should return the y-offset of the top edge of a specific row.
/// For instance, it could return:
///
/// * `f(0) => 0.0`
/// * `f(1) => 20.0`
/// * `f(2) => 30.0`
/// * `f(3) => 40.0`
///
/// The bottom of the last row will be calculated as `f(num_rows)`.
///
/// This function may be called hundreds of times, so make it fast!
#[inline]
pub fn row_top_offset(mut self, row_top_offset: impl Fn(u64) -> f32 + 'cb) -> Self {
self.row_top_offset = Box::new(row_top_offset);
self
}

/// How to do auto-sizing of columns, if at all.
#[inline]
pub fn auto_size_mode(mut self, auto_size_mode: AutoSizeMode) -> Self {
Expand Down Expand Up @@ -325,14 +308,27 @@ impl<'cb> Table<'cb> {
/// The top y coordinate offset of a specific row nr.
///
/// `get_row_top_offset(0)` should always return 0.0.
fn get_row_top_offset(&self, row_nr: u64) -> f32 {
(self.row_top_offset)(row_nr)
#[allow(clippy::unused_self)] // for uniformity
fn get_row_top_offset(
&self,
ctx: &Context,
table_id: Id,
table_delegate: &dyn TableDelegate,
row_nr: u64,
) -> f32 {
table_delegate.row_top_offset(ctx, table_id, row_nr)
}

/// Which row contains the given y offset (from the top)?
fn get_row_nr_at_y_offset(&self, y_offset: f32) -> u64 {
fn get_row_nr_at_y_offset(
&self,
ctx: &Context,
table_id: Id,
table_delegate: &dyn TableDelegate,
y_offset: f32,
) -> u64 {
partition_point(0..=self.num_rows, |row_nr| {
y_offset <= self.get_row_top_offset(row_nr)
y_offset <= self.get_row_top_offset(ctx, table_id, table_delegate, row_nr)
})
.saturating_sub(1)
}
Expand Down Expand Up @@ -427,7 +423,7 @@ impl<'cb> Table<'cb> {
.iter()
.map(|c| c.current)
.sum(),
self.get_row_top_offset(self.num_rows),
self.get_row_top_offset(ui.ctx(), id, table_delegate, self.num_rows),
),
}
.show(
Expand All @@ -443,6 +439,7 @@ impl<'cb> Table<'cb> {
visible_column_lines: Default::default(),
do_full_sizing_pass,
has_prefetched: false,
egui_ctx: ui.ctx().clone(),
},
);
});
Expand All @@ -469,10 +466,10 @@ fn update(map: &mut BTreeMap<usize, ColumnResizer>, key: usize, value: ColumnRes
}
}

struct TableSplitScrollDelegate<'a, 'cb> {
struct TableSplitScrollDelegate<'a> {
id: Id,
table_delegate: &'a mut dyn TableDelegate,
table: &'a mut Table<'cb>,
table: &'a mut Table,
state: &'a mut TableState,

/// The x coordinate for the start of each column, plus the end of the last column.
Expand All @@ -490,9 +487,23 @@ struct TableSplitScrollDelegate<'a, 'cb> {
do_full_sizing_pass: bool,

has_prefetched: bool,

egui_ctx: Context,
}

impl<'a, 'cb> TableSplitScrollDelegate<'a, 'cb> {
impl<'a> TableSplitScrollDelegate<'a> {
/// Helper wrapper around [`Table::get_row_top_offset`].
fn get_row_top_offset(&self, row_nr: u64) -> f32 {
self.table
.get_row_top_offset(&self.egui_ctx, self.id, self.table_delegate, row_nr)
}

/// Helper wrapper around [`Table::get_row_nr_at_y_offset`].
fn get_row_nr_at_y_offset(&self, y_offset: f32) -> u64 {
self.table
.get_row_nr_at_y_offset(&self.egui_ctx, self.id, self.table_delegate, y_offset)
}

fn header_ui(&mut self, ui: &mut Ui, offset: Vec2) {
for (row_nr, header_row) in self.table.headers.iter().enumerate() {
let groups = if header_row.groups.is_empty() {
Expand Down Expand Up @@ -608,9 +619,7 @@ impl<'a, 'cb> TableSplitScrollDelegate<'a, 'cb> {
} else {
// Only paint the visible rows:
let row_idx_at = |y: f32| -> u64 {
let row_nr = self
.table
.get_row_nr_at_y_offset(y - self.header_row_y.last());
let row_nr = self.get_row_nr_at_y_offset(y - self.header_row_y.last());
row_nr.at_most(self.table.num_rows.saturating_sub(1))
};

Expand Down Expand Up @@ -639,8 +648,8 @@ impl<'a, 'cb> TableSplitScrollDelegate<'a, 'cb> {

for row_nr in row_range {
let y_range = Rangef::new(
self.header_row_y.last() + self.table.get_row_top_offset(row_nr),
self.header_row_y.last() + self.table.get_row_top_offset(row_nr + 1),
self.header_row_y.last() + self.get_row_top_offset(row_nr),
self.header_row_y.last() + self.get_row_top_offset(row_nr + 1),
);

for col_nr in col_range.clone() {
Expand Down Expand Up @@ -689,7 +698,7 @@ impl<'a, 'cb> TableSplitScrollDelegate<'a, 'cb> {
}
}

impl<'a, 'cb> SplitScrollDelegate for TableSplitScrollDelegate<'a, 'cb> {
impl<'a> SplitScrollDelegate for TableSplitScrollDelegate<'a> {
// First to be called
fn right_bottom_ui(&mut self, ui: &mut Ui) {
if self.table.scroll_to_columns.is_some() || self.table.scroll_to_rows.is_some() {
Expand All @@ -716,7 +725,7 @@ impl<'a, 'cb> SplitScrollDelegate for TableSplitScrollDelegate<'a, 'cb> {

if let Some((row_range, align)) = &self.table.scroll_to_rows {
let y_from_row_nr = |row_nr: u64| -> f32 {
let mut y = self.table.get_row_top_offset(row_nr);
let mut y = self.get_row_top_offset(row_nr);

let sticky_height = self.header_row_y.last() - self.header_row_y.first();
if y < sticky_height {
Expand Down
Loading