From c0f801dd8cf09ce0d0d82566f803355805fe6e4f Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 1 Dec 2025 17:42:44 +0800 Subject: [PATCH] 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>`. ```diff - fn render_item(&self, ix: IndexPath, window: &mut Window, cx: &mut App) -> Option + fn render_item(&mut self, ix: IndexPath, window: &mut Window, cx: &mut Context>) -> Option ``` --- crates/story/src/list_story.rs | 10 ++--- crates/story/src/popover_story.rs | 4 +- crates/story/src/sheet_story.rs | 8 +++- .../ui/src/input/popovers/code_action_menu.rs | 20 ++++++---- .../ui/src/input/popovers/completion_menu.rs | 21 +++++----- crates/ui/src/list/delegate.rs | 38 +++++++++++++------ crates/ui/src/list/list.rs | 8 ++-- crates/ui/src/select.rs | 32 ++++++++++------ 8 files changed, 89 insertions(+), 52 deletions(-) diff --git a/crates/story/src/list_story.rs b/crates/story/src/list_story.rs index 9563b0ba..ae5e1aa9 100644 --- a/crates/story/src/list_story.rs +++ b/crates/story/src/list_story.rs @@ -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>, ) -> Option { 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>, ) -> Option { 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 { + fn render_item(&mut self, ix: IndexPath, _: &mut Window, _: &mut Context>) -> Option { 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)); diff --git a/crates/story/src/popover_story.rs b/crates/story/src/popover_story.rs index c2e56080..ee00353b 100644 --- a/crates/story/src/popover_story.rs +++ b/crates/story/src/popover_story.rs @@ -75,10 +75,10 @@ impl ListDelegate for DropdownListDelegate { } fn render_item( - &self, + &mut self, ix: gpui_component::IndexPath, _: &mut Window, - _: &mut App, + _: &mut Context>, ) -> Option { Some(ListItem::new(ix).child(format!("Item {}", ix.row))) } diff --git a/crates/story/src/sheet_story.rs b/crates/story/src/sheet_story.rs index fc1d1b5e..577fc84c 100644 --- a/crates/story/src/sheet_story.rs +++ b/crates/story/src/sheet_story.rs @@ -65,7 +65,7 @@ impl ListDelegate for ListItemDeletegate { }) } - fn render_item(&self, ix: IndexPath, _: &mut Window, _: &mut App) -> Option { + fn render_item(&mut self, ix: IndexPath, _: &mut Window, _: &mut Context>) -> Option { 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>, + ) -> impl IntoElement { v_flex() .size_full() .child( diff --git a/crates/ui/src/input/popovers/code_action_menu.rs b/crates/ui/src/input/popovers/code_action_menu.rs index 77a960f3..e73ea667 100644 --- a/crates/ui/src/input/popovers/code_action_menu.rs +++ b/crates/ui/src/input/popovers/code_action_menu.rs @@ -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 { + fn render_item( + &mut self, + ix: crate::IndexPath, + _: &mut Window, + _: &mut Context>, + ) -> Option { let item = self.items.get(ix.row)?; Some(MenuItem::new(ix.row, item.clone())) } diff --git a/crates/ui/src/input/popovers/completion_menu.rs b/crates/ui/src/input/popovers/completion_menu.rs index 8d6dc6d4..0132e87d 100644 --- a/crates/ui/src/input/popovers/completion_menu.rs +++ b/crates/ui/src/input/popovers/completion_menu.rs @@ -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 { + fn render_item( + &mut self, + ix: crate::IndexPath, + _: &mut Window, + _: &mut Context>, + ) -> Option { let item = self.items.get(ix.row)?; Some(CompletionMenuItem::new(ix.row, item.clone()).highlight_prefix(self.query.clone())) } diff --git a/crates/ui/src/list/delegate.rs b/crates/ui/src/list/delegate.rs index 4979bbe8..0bb95a41 100644 --- a/crates/ui/src/list/delegate.rs +++ b/crates/ui/src/list/delegate.rs @@ -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; + fn render_item( + &mut self, + ix: IndexPath, + window: &mut Window, + cx: &mut Context>, + ) -> Option; /// 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>, ) -> Option { None:: } @@ -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>, ) -> Option { None:: } /// 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>, + ) -> 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 { + fn render_initial( + &mut self, + window: &mut Window, + cx: &mut Context>, + ) -> Option { 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>, + ) -> impl IntoElement { Loading } diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs index e062e212..666384fc 100644 --- a/crates/ui/src/list/list.rs +++ b/crates/ui/src/list/list.rs @@ -430,7 +430,7 @@ where } fn render_list_item( - &self, + &mut self, ix: IndexPath, window: &mut Window, cx: &mut Context, @@ -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()), }) diff --git a/crates/ui/src/select.rs b/crates/ui/src/select.rs index ff2ab084..01914c77 100644 --- a/crates/ui/src/select.rs +++ b/crates/ui/src/select.rs @@ -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>, ) -> Option { 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 { + fn render_item( + &mut self, + ix: IndexPath, + window: &mut Window, + cx: &mut Context>, + ) -> Option { 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>, + ) -> impl IntoElement { if let Some(empty) = self .state .upgrade()