table: Refactor TableDelegate to easy to access delegate. (#1712)

Closes #1610, #1583

Closes https://github.com/longbridge/gpui-component/discussions/1707

## Description

This change to improve the `TableDelegate` to have `&mut self` and
`Context<TableState<Self>>` to easy access delegate.

## Break Changes

- The argument types of `render_tr`, `render_td`, `render_th`,
`context_menu`, `render_loading`, `render_empty`,
`render_last_empty_col` in `TableDelegate` has been changed.

```diff
- fn render_th(&self, col_ix: usize, _: &mut Window, _: &mut App) -> impl IntoElement
+ fn render_th(&mut self, col_ix: usize, _: &mut Window, _: &mut Context<TableState<Self>>) -> impl IntoElement
```
This commit is contained in:
Jason Lee 2025-12-01 17:20:52 +08:00 committed by GitHub
parent 0126f1b036
commit 001b1795f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 71 additions and 30 deletions

View file

@ -189,6 +189,7 @@ struct StockTableDelegate {
loading: bool,
lazy_load: bool,
full_loading: bool,
clicked_row: Option<usize>,
eof: bool,
visible_rows: Range<usize>,
visible_cols: Range<usize>,
@ -202,6 +203,7 @@ impl StockTableDelegate {
size: Size::default(),
stocks: random_stocks(size),
lazy_load: false,
clicked_row: None,
columns: vec![
Column::new("id", "ID")
.width(60.)
@ -354,7 +356,12 @@ impl TableDelegate for StockTableDelegate {
&self.columns[col_ix]
}
fn render_th(&self, col_ix: usize, _: &mut Window, _: &mut App) -> impl IntoElement {
fn render_th(
&mut self,
col_ix: usize,
_: &mut Window,
_: &mut Context<TableState<Self>>,
) -> impl IntoElement {
let col = self.columns.get(col_ix).unwrap();
div()
@ -368,11 +375,11 @@ impl TableDelegate for StockTableDelegate {
}
fn context_menu(
&self,
&mut self,
row_ix: usize,
menu: PopupMenu,
_window: &mut Window,
_cx: &mut App,
_: &mut Context<TableState<Self>>,
) -> PopupMenu {
menu.menu(
format!("Selected Row: {}", row_ix),
@ -385,13 +392,23 @@ impl TableDelegate for StockTableDelegate {
.menu("Size XSmall", Box::new(ChangeSize(Size::XSmall)))
}
fn render_tr(&self, row_ix: usize, _: &mut Window, _: &mut App) -> Stateful<Div> {
div().id(row_ix).on_click(|ev: &ClickEvent, _, _| {
println!(
"You have clicked row with secondary: {}",
ev.modifiers().secondary()
)
})
fn render_tr(
&mut self,
row_ix: usize,
_: &mut Window,
cx: &mut Context<TableState<Self>>,
) -> Stateful<Div> {
div()
.id(row_ix)
.on_click(cx.listener(move |table, ev: &ClickEvent, _window, cx| {
println!(
"You have clicked row with secondary: {}",
ev.modifiers().secondary()
);
table.delegate_mut().clicked_row = Some(row_ix);
cx.notify();
}))
}
/// NOTE: Performance metrics
@ -403,11 +420,11 @@ impl TableDelegate for StockTableDelegate {
///
/// If we improve the td rendering, we can reduce the time to render the full table cells.
fn render_td(
&self,
&mut self,
row_ix: usize,
col_ix: usize,
_: &mut Window,
cx: &mut App,
cx: &mut Context<TableState<Self>>,
) -> impl IntoElement {
let stock = self.stocks.get(row_ix).unwrap();
let col = self.columns.get(col_ix).unwrap();

View file

@ -1,15 +1,14 @@
use std::ops::Range;
use gpui::{
div, App, Context, Div, InteractiveElement as _, IntoElement, ParentElement as _, Stateful,
Styled as _, Window,
App, Context, Div, InteractiveElement as _, IntoElement, ParentElement as _, Stateful,
Styled as _, Window, div,
};
use crate::{
h_flex,
ActiveTheme as _, Icon, IconName, Size, h_flex,
menu::PopupMenu,
table::{loading::Loading, Column, ColumnSort, TableState},
ActiveTheme as _, Icon, IconName, Size,
table::{Column, ColumnSort, TableState, loading::Loading},
};
/// A delegate trait for providing data and rendering for a table.
@ -37,35 +36,45 @@ pub trait TableDelegate: Sized + 'static {
}
/// Render the header cell at the given column index, default to the column name.
fn render_th(&self, col_ix: usize, window: &mut Window, cx: &mut App) -> impl IntoElement {
fn render_th(
&mut self,
col_ix: usize,
window: &mut Window,
cx: &mut Context<TableState<Self>>,
) -> impl IntoElement {
div()
.size_full()
.child(self.column(col_ix, cx).name.clone())
}
/// Render the row at the given row and column.
fn render_tr(&self, row_ix: usize, window: &mut Window, cx: &mut App) -> Stateful<Div> {
fn render_tr(
&mut self,
row_ix: usize,
window: &mut Window,
cx: &mut Context<TableState<Self>>,
) -> Stateful<Div> {
h_flex().id(("row", row_ix))
}
/// Render the context menu for the row at the given row index.
fn context_menu(
&self,
&mut self,
row_ix: usize,
menu: PopupMenu,
window: &mut Window,
cx: &mut App,
cx: &mut Context<TableState<Self>>,
) -> PopupMenu {
menu
}
/// Render cell at the given row and column.
fn render_td(
&self,
&mut self,
row_ix: usize,
col_ix: usize,
window: &mut Window,
cx: &mut App,
cx: &mut Context<TableState<Self>>,
) -> impl IntoElement;
/// Move the column at the given `col_ix` to insert before the column at the given `to_ix`.
@ -79,7 +88,11 @@ pub trait TableDelegate: Sized + 'static {
}
/// Return a Element to show when table is empty.
fn render_empty(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {
fn render_empty(
&mut self,
window: &mut Window,
cx: &mut Context<TableState<Self>>,
) -> impl IntoElement {
h_flex()
.size_full()
.justify_center()
@ -96,7 +109,12 @@ pub trait TableDelegate: Sized + 'static {
/// Return a Element to show when table is loading, default is built-in Skeleton loading view.
///
/// The size is the size of the Table.
fn render_loading(&self, size: Size, window: &mut Window, cx: &mut App) -> impl IntoElement {
fn render_loading(
&mut self,
size: Size,
window: &mut Window,
cx: &mut Context<TableState<Self>>,
) -> impl IntoElement {
Loading::new().size(size)
}
@ -125,7 +143,11 @@ pub trait TableDelegate: Sized + 'static {
fn load_more(&mut self, window: &mut Window, cx: &mut Context<TableState<Self>>) {}
/// Render the last empty column, default to empty.
fn render_last_empty_col(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {
fn render_last_empty_col(
&mut self,
window: &mut Window,
cx: &mut Context<TableState<Self>>,
) -> impl IntoElement {
h_flex().w_3().h_full().flex_shrink_0()
}

View file

@ -788,7 +788,7 @@ where
/// The children must be one by one items.
/// Because the horizontal scroll handle will use the child_item_bounds to
/// calculate the item position for itself's `scroll_to_item` method.
fn render_th(&self, col_ix: usize, window: &mut Window, cx: &mut Context<Self>) -> Div {
fn render_th(&mut self, col_ix: usize, window: &mut Window, cx: &mut Context<Self>) -> Div {
let entity_id = cx.entity_id();
let col_group = self.col_groups.get(col_ix).expect("BUG: invalid col index");
@ -896,7 +896,8 @@ where
.bg(cx.theme().table_head)
.children(
self.col_groups
.iter()
.clone()
.into_iter()
.filter(|col| col.column.fixed == Some(ColumnFixed::Left))
.enumerate()
.map(|(col_ix, _)| self.render_th(col_ix, window, cx)),
@ -939,7 +940,8 @@ where
.relative()
.children(
self.col_groups
.iter()
.clone()
.into_iter()
.skip(left_columns_count)
.enumerate()
.map(|(col_ix, _)| {
@ -1302,7 +1304,7 @@ where
move |this, window: &mut Window, cx: &mut Context<PopupMenu>| {
if let Some(row_ix) = view.read(cx).right_clicked_row {
view.update(cx, |menu, cx| {
menu.delegate().context_menu(row_ix, this, window, cx)
menu.delegate_mut().context_menu(row_ix, this, window, cx)
})
} else {
this