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

View file

@ -75,10 +75,10 @@ impl ListDelegate for DropdownListDelegate {
}
fn render_item(
&self,
&mut self,
ix: gpui_component::IndexPath,
_: &mut Window,
_: &mut App,
_: &mut Context<ListState<Self>>,
) -> Option<Self::Item> {
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;
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()
.size_full()
.child(

View file

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

View file

@ -1,10 +1,10 @@
use std::rc::Rc;
use gpui::{
canvas, deferred, div, prelude::FluentBuilder, px, relative, Action, AnyElement, App,
AppContext, Bounds, Context, DismissEvent, Empty, Entity, EventEmitter, HighlightStyle,
InteractiveElement as _, IntoElement, ParentElement, Pixels, Point, Render, RenderOnce,
SharedString, Styled, StyledText, Subscription, Window,
Action, AnyElement, App, AppContext, Bounds, Context, DismissEvent, Empty, Entity,
EventEmitter, HighlightStyle, InteractiveElement as _, IntoElement, ParentElement, Pixels,
Point, Render, RenderOnce, SharedString, Styled, StyledText, Subscription, Window, canvas,
deferred, div, prelude::FluentBuilder, px, relative,
};
use lsp_types::{CompletionItem, CompletionTextEdit};
@ -13,15 +13,13 @@ const MAX_MENU_HEIGHT: Pixels = px(240.);
const POPOVER_GAP: Pixels = px(4.);
use crate::{
actions, h_flex,
ActiveTheme, IndexPath, Selectable, actions, h_flex,
input::{
self,
self, InputState, RopeExt,
popovers::{editor_popover, render_markdown},
InputState, RopeExt,
},
label::Label,
list::{List, ListDelegate, ListEvent, ListState},
ActiveTheme, IndexPath, Selectable,
};
struct ContextMenuDelegate {
@ -137,7 +135,12 @@ impl ListDelegate for ContextMenuDelegate {
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)?;
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 crate::{
h_flex,
list::{loading::Loading, ListState},
ActiveTheme as _, Icon, IconName, IndexPath, Selectable,
ActiveTheme as _, Icon, IconName, IndexPath, Selectable, h_flex,
list::{ListState, loading::Loading},
};
/// A delegate for the List.
@ -35,16 +34,21 @@ pub trait ListDelegate: Sized + 'static {
/// Return None will skip the item.
///
/// 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.
///
/// NOTE: Every header should have same height.
fn render_section_header(
&self,
&mut self,
section: usize,
window: &mut Window,
cx: &mut App,
cx: &mut Context<ListState<Self>>,
) -> Option<impl IntoElement> {
None::<AnyElement>
}
@ -53,16 +57,20 @@ pub trait ListDelegate: Sized + 'static {
///
/// NOTE: Every footer should have same height.
fn render_section_footer(
&self,
&mut self,
section: usize,
window: &mut Window,
cx: &mut App,
cx: &mut Context<ListState<Self>>,
) -> Option<impl IntoElement> {
None::<AnyElement>
}
/// 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()
.size_full()
.justify_center()
@ -79,7 +87,11 @@ pub trait ListDelegate: Sized + 'static {
/// For example: The last search results, or the last selected item.
///
/// 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
}
@ -90,7 +102,11 @@ pub trait ListDelegate: Sized + 'static {
/// Returns a Element to show when loading, default is built-in Skeleton
/// 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
}

View file

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

View file

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