dock: Refactor Panel trait to has &mut self and &mut Context<Self>. (#1716)

Continue #1712, #1713

## Description

Also change the `Panel` trait to has `&mut self` and `&mut
Context<Self>`.

## Break Changes

The methods `title`, `title_prefix`, `set_zoomed`, `set_active`,
`dropdown_menu`, `toolbar_buttons`, `on_added_to`, `on_removed` has
changed `&self` to `&mut self`, and `cx: &App` to `cx: &mut
Context<Self>`.

```diff
- fn title(&self, window: &Window, cx: &App) -> AnyElement
+ fn title(&mut self, window: &Window, cx: &mut Context<Self>) -> AnyElement
```
This commit is contained in:
Jason Lee 2025-12-01 17:57:18 +08:00 committed by GitHub
parent c0f801dd8c
commit 4ded78ed30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 106 additions and 113 deletions

View file

@ -94,11 +94,11 @@ impl Panel for ContainerPanel {
"ContainerPanel"
}
fn title(&self, window: &Window, cx: &App) -> AnyElement {
fn title(&mut self, window: &Window, cx: &mut Context<Self>) -> AnyElement {
self.panel.title(window, cx)
}
fn title_suffix(&self, _: &mut Window, cx: &mut App) -> Option<AnyElement> {
fn title_suffix(&mut self, _: &mut Window, cx: &mut Context<Self>) -> Option<AnyElement> {
Some(
div()
.w_24()

View file

@ -692,7 +692,7 @@ impl Panel for StoryContainer {
"StoryContainer"
}
fn title(&self, _window: &Window, _cx: &App) -> AnyElement {
fn title(&mut self, _window: &Window, _cx: &mut Context<Self>) -> AnyElement {
self.name.clone().into_any_element()
}
@ -722,11 +722,11 @@ impl Panel for StoryContainer {
.contains(&self.name)
}
fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, _cx: &mut App) {
fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, _cx: &mut Context<Self>) {
println!("panel: {} zoomed: {}", self.name, zoomed);
}
fn set_active(&mut self, active: bool, _window: &mut Window, cx: &mut App) {
fn set_active(&mut self, active: bool, _window: &mut Window, cx: &mut Context<Self>) {
println!("panel: {} active: {}", self.name, active);
if let Some(on_active) = self.on_active {
if let Some(story) = self.story.clone() {
@ -735,11 +735,20 @@ impl Panel for StoryContainer {
}
}
fn dropdown_menu(&self, menu: PopupMenu, _window: &Window, _cx: &App) -> PopupMenu {
fn dropdown_menu(
&mut self,
menu: PopupMenu,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> PopupMenu {
menu.menu("Info", Box::new(ShowPanelInfo))
}
fn toolbar_buttons(&self, _window: &mut Window, _cx: &mut App) -> Option<Vec<Button>> {
fn toolbar_buttons(
&mut self,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<Vec<Button>> {
Some(vec![
Button::new("info")
.icon(IconName::Info)

View file

@ -1,12 +1,10 @@
use std::{collections::HashMap, sync::Arc};
use crate::{button::Button, dock::TabPanel, menu::PopupMenu};
use gpui::{
AnyElement, AnyView, App, AppContext as _, Entity, EntityId, EventEmitter, FocusHandle,
Focusable, Global, Hsla, IntoElement, Render, SharedString, WeakEntity, Window,
AnyElement, AnyView, App, AppContext as _, Context, Entity, EntityId, EventEmitter,
FocusHandle, Focusable, Global, Hsla, IntoElement, Render, SharedString, WeakEntity, Window,
};
use rust_i18n::t;
use std::{collections::HashMap, sync::Arc};
use super::{DockArea, PanelInfo, PanelState, invalid_panel::InvalidPanel};
@ -68,7 +66,7 @@ pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
}
/// The title of the panel
fn title(&self, window: &Window, cx: &App) -> AnyElement {
fn title(&mut self, window: &Window, cx: &mut Context<Self>) -> AnyElement {
SharedString::from(t!("Dock.Unnamed")).into_any_element()
}
@ -80,7 +78,7 @@ pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
/// The suffix of the panel title, default is `None`.
///
/// This is used to add a suffix element to the panel title.
fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement> {
fn title_suffix(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<AnyElement> {
None
}
@ -110,28 +108,43 @@ pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
/// This method will be called when the panel is active or inactive.
///
/// The last_active_panel and current_active_panel will be touched when the panel is active.
fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut App) {}
fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {}
/// Set zoomed state of the panel.
///
/// This method will be called when the panel is zoomed or unzoomed.
///
/// Only current Panel will touch this method.
fn set_zoomed(&mut self, zoomed: bool, window: &mut Window, cx: &mut App) {}
fn set_zoomed(&mut self, zoomed: bool, window: &mut Window, cx: &mut Context<Self>) {}
/// When this Panel is added to a TabPanel, this will be called.
fn on_added_to(&mut self, tab_panel: WeakEntity<TabPanel>, window: &mut Window, cx: &mut App) {}
fn on_added_to(
&mut self,
tab_panel: WeakEntity<TabPanel>,
window: &mut Window,
cx: &mut Context<Self>,
) {
}
/// When this Panel is removed from a TabPanel, this will be called.
fn on_removed(&mut self, window: &mut Window, cx: &mut App) {}
fn on_removed(&mut self, window: &mut Window, cx: &mut Context<Self>) {}
/// The addition dropdown menu of the panel, default is `None`.
fn dropdown_menu(&self, this: PopupMenu, window: &Window, cx: &App) -> PopupMenu {
fn dropdown_menu(
&mut self,
this: PopupMenu,
window: &mut Window,
cx: &mut Context<Self>,
) -> PopupMenu {
this
}
/// The addition toolbar buttons of the panel used to show in the right of the title bar, default is `None`.
fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>> {
fn toolbar_buttons(
&mut self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Vec<Button>> {
None
}
@ -152,7 +165,7 @@ pub trait PanelView: 'static + Send + Sync {
fn panel_name(&self, cx: &App) -> &'static str;
fn panel_id(&self, cx: &App) -> EntityId;
fn tab_name(&self, cx: &App) -> Option<SharedString>;
fn title(&self, window: &Window, cx: &App) -> AnyElement;
fn title(&self, window: &Window, cx: &mut App) -> AnyElement;
fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement>;
fn title_style(&self, cx: &App) -> Option<TitleStyle>;
fn closable(&self, cx: &App) -> bool;
@ -162,7 +175,7 @@ pub trait PanelView: 'static + Send + Sync {
fn set_zoomed(&self, zoomed: bool, window: &mut Window, cx: &mut App);
fn on_added_to(&self, tab_panel: WeakEntity<TabPanel>, window: &mut Window, cx: &mut App);
fn on_removed(&self, window: &mut Window, cx: &mut App);
fn dropdown_menu(&self, menu: PopupMenu, window: &Window, cx: &App) -> PopupMenu;
fn dropdown_menu(&self, menu: PopupMenu, window: &mut Window, cx: &mut App) -> PopupMenu;
fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>>;
fn view(&self) -> AnyView;
fn focus_handle(&self, cx: &App) -> FocusHandle;
@ -183,8 +196,8 @@ impl<T: Panel> PanelView for Entity<T> {
self.read(cx).tab_name(cx)
}
fn title(&self, window: &Window, cx: &App) -> AnyElement {
self.read(cx).title(window, cx)
fn title(&self, window: &Window, cx: &mut App) -> AnyElement {
self.update(cx, |this, cx| this.title(window, cx))
}
fn title_suffix(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement> {
@ -227,8 +240,8 @@ impl<T: Panel> PanelView for Entity<T> {
self.update(cx, |this, cx| this.on_removed(window, cx));
}
fn dropdown_menu(&self, menu: PopupMenu, window: &Window, cx: &App) -> PopupMenu {
self.read(cx).dropdown_menu(menu, window, cx)
fn dropdown_menu(&self, menu: PopupMenu, window: &mut Window, cx: &mut App) -> PopupMenu {
self.update(cx, |this, cx| this.dropdown_menu(menu, window, cx))
}
fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>> {

View file

@ -1,13 +1,13 @@
use std::sync::Arc;
use crate::{
ActiveTheme, AxisExt as _, Placement,
dock::PanelInfo,
h_flex,
resizable::{
resizable_panel, ResizablePanelEvent, ResizablePanelGroup, ResizablePanelState,
ResizableState, PANEL_MIN_SIZE,
PANEL_MIN_SIZE, ResizablePanelEvent, ResizablePanelGroup, ResizablePanelState,
ResizableState, resizable_panel,
},
ActiveTheme, AxisExt as _, Placement,
};
use super::{DockArea, Panel, PanelEvent, PanelState, PanelView, TabPanel};
@ -32,10 +32,10 @@ impl Panel for StackPanel {
"StackPanel"
}
fn title(&self, _window: &gpui::Window, _cx: &gpui::App) -> gpui::AnyElement {
fn title(&mut self, _window: &gpui::Window, _cx: &mut Context<Self>) -> gpui::AnyElement {
"StackPanel".into_any_element()
}
fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut App) {
fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {
for panel in &self.panels {
panel.set_active(active, window, cx);
}

View file

@ -91,7 +91,7 @@ impl Panel for TabPanel {
"TabPanel"
}
fn title(&self, window: &Window, cx: &App) -> gpui::AnyElement {
fn title(&mut self, window: &Window, cx: &mut Context<Self>) -> gpui::AnyElement {
self.active_panel(cx)
.map(|panel| panel.title(window, cx))
.unwrap_or("Empty Tab".into_any_element())
@ -121,7 +121,12 @@ impl Panel for TabPanel {
self.visible_panels(cx).next().is_some()
}
fn dropdown_menu(&self, menu: PopupMenu, window: &Window, cx: &App) -> PopupMenu {
fn dropdown_menu(
&mut self,
menu: PopupMenu,
window: &mut Window,
cx: &mut Context<Self>,
) -> PopupMenu {
if let Some(panel) = self.active_panel(cx) {
panel.dropdown_menu(menu, window, cx)
} else {
@ -129,7 +134,11 @@ impl Panel for TabPanel {
}
}
fn toolbar_buttons(&self, window: &mut Window, cx: &mut App) -> Option<Vec<Button>> {
fn toolbar_buttons(
&mut self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Vec<Button>> {
self.active_panel(cx)
.and_then(|panel| panel.toolbar_buttons(window, cx))
}
@ -422,7 +431,7 @@ impl TabPanel {
}
fn render_toolbar(
&self,
&mut self,
state: &TabState,
window: &mut Window,
cx: &mut Context<Self>,
@ -481,9 +490,9 @@ impl TabPanel {
let zoomable = state.zoomable.map_or(false, |v| v.menu_visible());
let closable = state.closable;
move |this, window, cx| {
view.read(cx)
.dropdown_menu(this, window, cx)
move |menu, window, cx| {
view.update(cx, |this, cx| {
this.dropdown_menu(menu, window, cx)
.separator()
.menu_with_disabled(
if zoomed {
@ -498,6 +507,7 @@ impl TabPanel {
this.separator()
.menu(t!("Dock.Close"), Box::new(ClosePanel))
})
})
}
})
.anchor(Corner::TopRight),
@ -591,7 +601,7 @@ impl TabPanel {
}
fn render_title_bar(
&self,
&mut self,
state: &TabState,
window: &mut Window,
cx: &mut Context<Self>,

View file

@ -148,7 +148,7 @@ impl Panel for Tiles {
"Tiles"
}
fn title(&self, _window: &Window, _cx: &App) -> AnyElement {
fn title(&mut self, _window: &Window, _cx: &mut Context<Self>) -> AnyElement {
"Tiles".into_any_element()
}

View file

@ -34,10 +34,10 @@ impl ListDelegate for MyListDelegate {
}
fn render_item(
&self,
&mut self,
ix: IndexPath,
_window: &mut Window,
_cx: &mut App,
_cx: &mut Context<TableState<Self>>,
) -> Option<Self::Item> {
self.items.get(ix.row).map(|item| {
ListItem::new(ix)
@ -93,10 +93,10 @@ impl ListDelegate for MyListDelegate {
}
fn render_section_header(
&self,
&mut self,
section: usize,
_window: &mut Window,
cx: &mut App,
cx: &mut Context<TableState<Self>>,
) -> Option<impl IntoElement> {
let title = match section {
0 => "Section 1",
@ -118,10 +118,10 @@ impl ListDelegate for MyListDelegate {
}
fn render_section_footer(
&self,
&mut self,
section: usize,
_window: &mut Window,
cx: &mut App,
cx: &mut Context<TableState<Self>>,
) -> Option<impl IntoElement> {
Some(
div()
@ -139,10 +139,10 @@ impl ListDelegate for MyListDelegate {
```rust
fn render_item(
&self,
&mut self,
ix: IndexPath,
_window: &mut Window,
cx: &mut App,
cx: &mut Context<TableState<Self>>,
) -> Option<Self::Item> {
self.items.get(ix.row).map(|item| {
ListItem::new(ix)
@ -205,9 +205,9 @@ impl ListDelegate for MyListDelegate {
}
fn render_loading(
&self,
&mut self,
_window: &mut Window,
_cx: &mut App,
_cx: &mut Context<TableState<Self>>,
) -> impl IntoElement {
// Custom loading view
v_flex()
@ -305,7 +305,7 @@ ListSeparatorItem::new()
```rust
impl ListDelegate for MyListDelegate {
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 {
v_flex()
.size_full()
.justify_center()
@ -380,7 +380,7 @@ struct FileInfo {
impl ListDelegate for FileBrowserDelegate {
type Item = ListItem;
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<TableState<Self>>) -> Option<Self::Item> {
self.files.get(ix.row).map(|file| {
let icon = if file.is_directory {
IconName::Folder
@ -430,7 +430,7 @@ impl ListDelegate for ContactListDelegate {
self.contacts_by_letter.len()
}
fn render_section_header(&self, section: usize, _window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
fn render_section_header(&mut self, section: usize, _window: &mut Window, cx: &mut Context<TableState<Self>>) -> Option<impl IntoElement> {
let letter = self.contacts_by_letter.keys().nth(section)?;
Some(
@ -450,11 +450,3 @@ impl ListDelegate for ContactListDelegate {
}
}
```
## Performance
- **Virtualization**: Only renders visible items for large datasets
- **Efficient Updates**: Optimized re-rendering with proper change detection
- **Memory Management**: Automatic cleanup of off-screen items
- **Smooth Scrolling**: Hardware-accelerated scrolling with momentum
- **Lazy Loading**: Built-in support for infinite scrolling and pagination

View file

@ -66,7 +66,7 @@ impl TableDelegate for MyTableDelegate {
&self.columns[col_ix]
}
fn render_td(&self, row_ix: usize, col_ix: usize, _: &mut Window, _: &mut App) -> impl IntoElement {
fn render_td(&mut self, row_ix: usize, col_ix: usize, _: &mut Window, _: &mut Context<TableState<Self>>) -> impl IntoElement {
let row = &self.data[row_ix];
let col = &self.columns[col_ix];
@ -143,7 +143,7 @@ impl TableDelegate for LargeDataDelegate {
}
// Only visible rows are rendered
fn render_td(&self, row_ix: usize, col_ix: usize, _: &mut Window, _: &mut Context<TableState<Self>>) -> impl IntoElement {
fn render_td(&mut self, row_ix: usize, col_ix: usize, _: &mut Window, _: &mut Context<TableState<Self>>) -> impl IntoElement {
// This is only called for visible rows
// Efficiently render cell content
let row = &self.data[row_ix];
@ -191,26 +191,12 @@ impl TableDelegate for MyTableDelegate {
}
```
### Row Selection
Handle row selection and interaction:
### ContextMenu
```rust
impl TableDelegate for MyTableDelegate {
fn render_tr(&self, row_ix: usize, _: &mut Window, cx: &mut App) -> Stateful<Div> {
div()
.id(row_ix)
.on_click(move |ev, _, _| {
if ev.modifiers().secondary() {
println!("Right-clicked row {}", row_ix);
} else {
println!("Selected row {}", row_ix);
}
})
}
// Context menu for right-click
fn context_menu(&self, row_ix: usize, menu: PopupMenu, _: &mut Window, _: &mut App) -> PopupMenu {
fn context_menu(&mut self, row_ix: usize, menu: PopupMenu, _: &mut Window, _: &mut Context<TableState<Self>>) -> PopupMenu {
let row = &self.data[row_ix];
menu.menu(format!("Edit {}", row.name), Box::new(EditRowAction(row_ix)))
.menu("Delete", Box::new(DeleteRowAction(row_ix)))
@ -218,32 +204,15 @@ impl TableDelegate for MyTableDelegate {
.menu("Duplicate", Box::new(DuplicateRowAction(row_ix)))
}
}
// Handle table events
cx.subscribe_in(&state, window, |view, table, event, _, cx| {
match event {
TableEvent::SelectRow(row_ix) => {
println!("Row {} selected", row_ix);
}
TableEvent::DoubleClickedRow(row_ix) => {
println!("Row {} double-clicked", row_ix);
// Open detail view or edit mode
}
TableEvent::SelectColumn(col_ix) => {
println!("Column {} selected", col_ix);
}
_ => {}
}
}).detach();
```
### Custom Cell Rendering
### Cell Rendering
Create rich cell content with custom rendering:
```rust
impl TableDelegate for MyTableDelegate {
fn render_td(&self, row_ix: usize, col_ix: usize, _: &mut Window, cx: &mut App) -> impl IntoElement {
fn render_td(&mut self, row_ix: usize, col_ix: usize, _: &mut Window, cx: &mut Context<TableState<Self>>) -> impl IntoElement {
let row = &self.data[row_ix];
let col = &self.columns[col_ix];
@ -413,7 +382,7 @@ struct StockData {
}
impl TableDelegate for StockTableDelegate {
fn render_td(&self, row_ix: usize, col_ix: usize, _: &mut Window, cx: &mut App) -> impl IntoElement {
fn render_td(&mut self, row_ix: usize, col_ix: usize, _: &mut Window, cx: &mut Context<TableState<Self>>) -> impl IntoElement {
let stock = &self.stocks[row_ix];
let col = &self.columns[col_ix];