list: Refactor ListDelegate to easy access delegate. (#1713)

Continue #1712

## Break Changes

- The argument type of `render_item`, `render_section_header`,
`render_section_footer`, `render_empty`, `render_initial`,
`render_loading` in `ListDelegate` has been changed to has `&mut self`
and `&mut Context<ListState<Self>>`.

```diff
- fn render_item(&self, ix: IndexPath, window: &mut Window, cx: &mut App) -> Option<Self::Item>
+ fn render_item(&mut self, ix: IndexPath, window: &mut Window, cx: &mut Context<ListState<Self>>) -> Option<Self::Item>
```
This commit is contained in:
Jason Lee 2025-12-01 17:42:44 +08:00 committed by GitHub
parent 001b1795f1
commit c0f801dd8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 89 additions and 52 deletions

View file

@ -225,10 +225,10 @@ impl ListDelegate for CompanyListDelegate {
} }
fn render_section_header( fn render_section_header(
&self, &mut self,
section: usize, section: usize,
_: &mut Window, _: &mut Window,
cx: &mut App, cx: &mut Context<ListState<Self>>,
) -> Option<impl IntoElement> { ) -> Option<impl IntoElement> {
let Some(industry) = self.industries.get(section) else { let Some(industry) = self.industries.get(section) else {
return None; return None;
@ -247,10 +247,10 @@ impl ListDelegate for CompanyListDelegate {
} }
fn render_section_footer( fn render_section_footer(
&self, &mut self,
section: usize, section: usize,
_: &mut Window, _: &mut Window,
cx: &mut App, cx: &mut Context<ListState<Self>>,
) -> Option<impl IntoElement> { ) -> Option<impl IntoElement> {
let Some(_) = self.industries.get(section) else { let Some(_) = self.industries.get(section) else {
return None; return None;
@ -270,7 +270,7 @@ impl ListDelegate for CompanyListDelegate {
) )
} }
fn render_item(&self, ix: IndexPath, _: &mut Window, _: &mut App) -> Option<Self::Item> { fn render_item(&mut self, ix: IndexPath, _: &mut Window, _: &mut Context<ListState<Self>>) -> Option<Self::Item> {
let selected = Some(ix) == self.selected_index || Some(ix) == self.confirmed_index; let selected = Some(ix) == self.selected_index || Some(ix) == self.confirmed_index;
if let Some(company) = self.matched_companies[ix.section].get(ix.row) { if let Some(company) = self.matched_companies[ix.section].get(ix.row) {
return Some(CompanyListItem::new(ix, company.clone(), selected)); return Some(CompanyListItem::new(ix, company.clone(), selected));

View file

@ -75,10 +75,10 @@ impl ListDelegate for DropdownListDelegate {
} }
fn render_item( fn render_item(
&self, &mut self,
ix: gpui_component::IndexPath, ix: gpui_component::IndexPath,
_: &mut Window, _: &mut Window,
_: &mut App, _: &mut Context<ListState<Self>>,
) -> Option<Self::Item> { ) -> Option<Self::Item> {
Some(ListItem::new(ix).child(format!("Item {}", ix.row))) Some(ListItem::new(ix).child(format!("Item {}", ix.row)))
} }

View file

@ -65,7 +65,7 @@ impl ListDelegate for ListItemDeletegate {
}) })
} }
fn render_item(&self, ix: IndexPath, _: &mut Window, _: &mut App) -> Option<Self::Item> { fn render_item(&mut self, ix: IndexPath, _: &mut Window, _: &mut Context<ListState<Self>>) -> Option<Self::Item> {
let confirmed = Some(ix.row) == self.confirmed_index; let confirmed = Some(ix.row) == self.confirmed_index;
if let Some(item) = self.matches.get(ix.row) { if let Some(item) = self.matches.get(ix.row) {
@ -97,7 +97,11 @@ impl ListDelegate for ListItemDeletegate {
} }
} }
fn render_empty(&self, _: &mut Window, cx: &mut App) -> impl IntoElement { fn render_empty(
&mut self,
_: &mut Window,
cx: &mut Context<ListState<Self>>,
) -> impl IntoElement {
v_flex() v_flex()
.size_full() .size_full()
.child( .child(

View file

@ -1,10 +1,10 @@
use std::rc::Rc; use std::rc::Rc;
use gpui::{ use gpui::{
canvas, deferred, div, prelude::FluentBuilder, px, relative, Action, AnyElement, App, Action, AnyElement, App, AppContext, Bounds, Context, DismissEvent, Empty, Entity,
AppContext, Bounds, Context, DismissEvent, Empty, Entity, EventEmitter, EventEmitter, InteractiveElement as _, IntoElement, ParentElement, Pixels, Point, Render,
InteractiveElement as _, IntoElement, ParentElement, Pixels, Point, Render, RenderOnce, RenderOnce, SharedString, Styled, StyledText, Subscription, Window, canvas, deferred, div,
SharedString, Styled, StyledText, Subscription, Window, prelude::FluentBuilder, px, relative,
}; };
use lsp_types::CodeAction; use lsp_types::CodeAction;
@ -12,10 +12,9 @@ const MAX_MENU_WIDTH: Pixels = px(320.);
const MAX_MENU_HEIGHT: Pixels = px(480.); const MAX_MENU_HEIGHT: Pixels = px(480.);
use crate::{ use crate::{
actions, h_flex, ActiveTheme, IndexPath, Selectable, actions, h_flex,
input::{self, popovers::editor_popover, InputState}, input::{self, InputState, popovers::editor_popover},
list::{List, ListDelegate, ListEvent, ListState}, list::{List, ListDelegate, ListEvent, ListState},
ActiveTheme, IndexPath, Selectable,
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -110,7 +109,12 @@ impl ListDelegate for MenuDelegate {
self.items.len() self.items.len()
} }
fn render_item(&self, ix: crate::IndexPath, _: &mut Window, _: &mut App) -> Option<Self::Item> { fn render_item(
&mut self,
ix: crate::IndexPath,
_: &mut Window,
_: &mut Context<ListState<Self>>,
) -> Option<Self::Item> {
let item = self.items.get(ix.row)?; let item = self.items.get(ix.row)?;
Some(MenuItem::new(ix.row, item.clone())) Some(MenuItem::new(ix.row, item.clone()))
} }

View file

@ -1,10 +1,10 @@
use std::rc::Rc; use std::rc::Rc;
use gpui::{ use gpui::{
canvas, deferred, div, prelude::FluentBuilder, px, relative, Action, AnyElement, App, Action, AnyElement, App, AppContext, Bounds, Context, DismissEvent, Empty, Entity,
AppContext, Bounds, Context, DismissEvent, Empty, Entity, EventEmitter, HighlightStyle, EventEmitter, HighlightStyle, InteractiveElement as _, IntoElement, ParentElement, Pixels,
InteractiveElement as _, IntoElement, ParentElement, Pixels, Point, Render, RenderOnce, Point, Render, RenderOnce, SharedString, Styled, StyledText, Subscription, Window, canvas,
SharedString, Styled, StyledText, Subscription, Window, deferred, div, prelude::FluentBuilder, px, relative,
}; };
use lsp_types::{CompletionItem, CompletionTextEdit}; use lsp_types::{CompletionItem, CompletionTextEdit};
@ -13,15 +13,13 @@ const MAX_MENU_HEIGHT: Pixels = px(240.);
const POPOVER_GAP: Pixels = px(4.); const POPOVER_GAP: Pixels = px(4.);
use crate::{ use crate::{
actions, h_flex, ActiveTheme, IndexPath, Selectable, actions, h_flex,
input::{ input::{
self, self, InputState, RopeExt,
popovers::{editor_popover, render_markdown}, popovers::{editor_popover, render_markdown},
InputState, RopeExt,
}, },
label::Label, label::Label,
list::{List, ListDelegate, ListEvent, ListState}, list::{List, ListDelegate, ListEvent, ListState},
ActiveTheme, IndexPath, Selectable,
}; };
struct ContextMenuDelegate { struct ContextMenuDelegate {
@ -137,7 +135,12 @@ impl ListDelegate for ContextMenuDelegate {
self.items.len() self.items.len()
} }
fn render_item(&self, ix: crate::IndexPath, _: &mut Window, _: &mut App) -> Option<Self::Item> { fn render_item(
&mut self,
ix: crate::IndexPath,
_: &mut Window,
_: &mut Context<ListState<Self>>,
) -> Option<Self::Item> {
let item = self.items.get(ix.row)?; let item = self.items.get(ix.row)?;
Some(CompletionMenuItem::new(ix.row, item.clone()).highlight_prefix(self.query.clone())) Some(CompletionMenuItem::new(ix.row, item.clone()).highlight_prefix(self.query.clone()))
} }

View file

@ -1,9 +1,8 @@
use gpui::{AnyElement, App, Context, IntoElement, ParentElement as _, Styled as _, Task, Window}; use gpui::{AnyElement, App, Context, IntoElement, ParentElement as _, Styled as _, Task, Window};
use crate::{ use crate::{
h_flex, ActiveTheme as _, Icon, IconName, IndexPath, Selectable, h_flex,
list::{loading::Loading, ListState}, list::{ListState, loading::Loading},
ActiveTheme as _, Icon, IconName, IndexPath, Selectable,
}; };
/// A delegate for the List. /// A delegate for the List.
@ -35,16 +34,21 @@ pub trait ListDelegate: Sized + 'static {
/// Return None will skip the item. /// Return None will skip the item.
/// ///
/// NOTE: Every item should have same height. /// NOTE: Every item should have same height.
fn render_item(&self, ix: IndexPath, window: &mut Window, cx: &mut App) -> Option<Self::Item>; fn render_item(
&mut self,
ix: IndexPath,
window: &mut Window,
cx: &mut Context<ListState<Self>>,
) -> Option<Self::Item>;
/// Render the section header at the given index, default is None. /// Render the section header at the given index, default is None.
/// ///
/// NOTE: Every header should have same height. /// NOTE: Every header should have same height.
fn render_section_header( fn render_section_header(
&self, &mut self,
section: usize, section: usize,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut Context<ListState<Self>>,
) -> Option<impl IntoElement> { ) -> Option<impl IntoElement> {
None::<AnyElement> None::<AnyElement>
} }
@ -53,16 +57,20 @@ pub trait ListDelegate: Sized + 'static {
/// ///
/// NOTE: Every footer should have same height. /// NOTE: Every footer should have same height.
fn render_section_footer( fn render_section_footer(
&self, &mut self,
section: usize, section: usize,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut Context<ListState<Self>>,
) -> Option<impl IntoElement> { ) -> Option<impl IntoElement> {
None::<AnyElement> None::<AnyElement>
} }
/// Return a Element to show when list is empty. /// Return a Element to show when list 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<ListState<Self>>,
) -> impl IntoElement {
h_flex() h_flex()
.size_full() .size_full()
.justify_center() .justify_center()
@ -79,7 +87,11 @@ pub trait ListDelegate: Sized + 'static {
/// For example: The last search results, or the last selected item. /// For example: The last search results, or the last selected item.
/// ///
/// Default is None, that means no initial state. /// Default is None, that means no initial state.
fn render_initial(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement> { fn render_initial(
&mut self,
window: &mut Window,
cx: &mut Context<ListState<Self>>,
) -> Option<AnyElement> {
None None
} }
@ -90,7 +102,11 @@ pub trait ListDelegate: Sized + 'static {
/// Returns a Element to show when loading, default is built-in Skeleton /// Returns a Element to show when loading, default is built-in Skeleton
/// loading view. /// loading view.
fn render_loading(&self, window: &mut Window, cx: &mut App) -> impl IntoElement { fn render_loading(
&mut self,
window: &mut Window,
cx: &mut Context<ListState<Self>>,
) -> impl IntoElement {
Loading Loading
} }

View file

@ -430,7 +430,7 @@ where
} }
fn render_list_item( fn render_list_item(
&self, &mut self,
ix: IndexPath, ix: IndexPath,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
@ -474,7 +474,7 @@ where
} }
fn render_items( fn render_items(
&self, &mut self,
items_count: usize, items_count: usize,
entities_count: usize, entities_count: usize,
window: &mut Window, window: &mut Window,
@ -526,11 +526,11 @@ where
.into_any_element(), .into_any_element(),
), ),
RowEntry::SectionHeader(section_ix) => list RowEntry::SectionHeader(section_ix) => list
.delegate() .delegate_mut()
.render_section_header(section_ix, window, cx) .render_section_header(section_ix, window, cx)
.map(|r| r.into_any_element()), .map(|r| r.into_any_element()),
RowEntry::SectionFooter(section_ix) => list RowEntry::SectionFooter(section_ix) => list
.delegate() .delegate_mut()
.render_section_footer(section_ix, window, cx) .render_section_footer(section_ix, window, cx)
.map(|r| r.into_any_element()), .map(|r| r.into_any_element()),
}) })

View file

@ -1,19 +1,20 @@
use gpui::{ use gpui::{
anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, App, AppContext, AnyElement, App, AppContext, Bounds, ClickEvent, Context, DismissEvent, Edges, ElementId,
Bounds, ClickEvent, Context, DismissEvent, Edges, ElementId, Entity, EventEmitter, FocusHandle, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding,
Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement, Pixels, Render, Length, ParentElement, Pixels, Render, RenderOnce, SharedString, StatefulInteractiveElement,
RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Subscription, StyleRefinement, Styled, Subscription, Task, WeakEntity, Window, anchored, canvas, deferred,
Task, WeakEntity, Window, div, prelude::FluentBuilder, px, rems,
}; };
use rust_i18n::t; use rust_i18n::t;
use crate::{ use crate::{
ActiveTheme, Disableable, Icon, IconName, IndexPath, Selectable, Sizable, Size, StyleSized,
StyledExt,
actions::{Cancel, Confirm, SelectDown, SelectUp}, actions::{Cancel, Confirm, SelectDown, SelectUp},
h_flex, h_flex,
input::clear_button, input::clear_button,
list::{List, ListDelegate, ListState}, list::{List, ListDelegate, ListState},
v_flex, ActiveTheme, Disableable, Icon, IconName, IndexPath, Selectable, Sizable, Size, v_flex,
StyleSized, StyledExt,
}; };
const CONTEXT: &str = "Select"; const CONTEXT: &str = "Select";
@ -167,10 +168,10 @@ where
} }
fn render_section_header( fn render_section_header(
&self, &mut self,
section: usize, section: usize,
_: &mut Window, _: &mut Window,
cx: &mut App, cx: &mut Context<ListState<Self>>,
) -> Option<impl IntoElement> { ) -> Option<impl IntoElement> {
let state = self.state.upgrade()?.read(cx); let state = self.state.upgrade()?.read(cx);
let Some(item) = self.delegate.section(section) else { let Some(item) = self.delegate.section(section) else {
@ -188,7 +189,12 @@ where
); );
} }
fn render_item(&self, ix: IndexPath, window: &mut Window, cx: &mut App) -> Option<Self::Item> { fn render_item(
&mut self,
ix: IndexPath,
window: &mut Window,
cx: &mut Context<ListState<Self>>,
) -> Option<Self::Item> {
let selected = self let selected = self
.selected_index .selected_index
.map_or(false, |selected_index| selected_index == ix); .map_or(false, |selected_index| selected_index == ix);
@ -273,7 +279,11 @@ where
self.selected_index = ix; self.selected_index = ix;
} }
fn render_empty(&self, window: &mut Window, cx: &mut App) -> impl IntoElement { fn render_empty(
&mut self,
window: &mut Window,
cx: &mut Context<ListState<Self>>,
) -> impl IntoElement {
if let Some(empty) = self if let Some(empty) = self
.state .state
.upgrade() .upgrade()