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

View file

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

View file

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