Skip to content

Commit 03dd61b

Browse files
committed
tree: Add context menu support
1 parent c8652f9 commit 03dd61b

File tree

4 files changed

+492
-225
lines changed

4 files changed

+492
-225
lines changed

crates/story/src/tree_story.rs

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,65 @@ use gpui_component::{
1212
h_flex,
1313
label::Label,
1414
list::ListItem,
15-
tree::{TreeItem, TreeState, tree},
15+
tree::{TreeDelegate, TreeEntry, TreeItem, TreeState, tree},
1616
v_flex,
1717
};
1818

1919
use crate::{Story, section};
2020

2121
actions!(story, [Rename, SelectItem]);
2222

23+
struct FileTreeDelegate {
24+
parent: Entity<TreeStory>,
25+
}
26+
27+
impl TreeDelegate for FileTreeDelegate {
28+
fn render_item(
29+
&self,
30+
ix: usize,
31+
entry: &TreeEntry,
32+
selected: bool,
33+
window: &mut Window,
34+
cx: &mut App,
35+
) -> ListItem {
36+
let item = entry.item();
37+
let icon = if !entry.is_folder() {
38+
IconName::File
39+
} else if entry.is_expanded() {
40+
IconName::FolderOpen
41+
} else {
42+
IconName::Folder
43+
};
44+
45+
ListItem::new(ix)
46+
.w_full()
47+
.rounded(cx.theme().radius)
48+
.px_3()
49+
.pl(px(16.) * entry.depth() + px(12.))
50+
.child(h_flex().gap_2().child(icon).child(item.label.clone()))
51+
.selected(selected)
52+
.on_click(window.listener_for(&self.parent, {
53+
let item = item.clone();
54+
move |this, _, _window, cx| {
55+
this.selected_item = Some(item.clone());
56+
cx.notify();
57+
}
58+
}))
59+
}
60+
61+
fn context_menu(
62+
&self,
63+
ix: usize,
64+
menu: gpui_component::menu::PopupMenu,
65+
_window: &mut Window,
66+
_cx: &mut App,
67+
) -> gpui_component::menu::PopupMenu {
68+
menu.label(format!("Selected Index: {}", ix))
69+
.separator()
70+
.menu("Rename", Box::new(Rename))
71+
}
72+
}
73+
2374
const CONTEXT: &str = "TreeStory";
2475
pub(crate) fn init(cx: &mut App) {
2576
cx.bind_keys([
@@ -29,7 +80,7 @@ pub(crate) fn init(cx: &mut App) {
2980
}
3081

3182
pub struct TreeStory {
32-
tree_state: Entity<TreeState>,
83+
tree_state: Entity<TreeState<FileTreeDelegate>>,
3384
selected_item: Option<TreeItem>,
3485
}
3586

@@ -71,7 +122,7 @@ impl TreeStory {
71122
cx.new(|cx| Self::new(window, cx))
72123
}
73124

74-
fn load_files(state: Entity<TreeState>, path: PathBuf, cx: &mut App) {
125+
fn load_files(state: Entity<TreeState<FileTreeDelegate>>, path: PathBuf, cx: &mut App) {
75126
cx.spawn(async move |cx| {
76127
let ignorer = Ignorer::new(&path.to_string_lossy());
77128
let items = build_file_items(&ignorer, &path, &path);
@@ -83,7 +134,15 @@ impl TreeStory {
83134
}
84135

85136
fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
86-
let tree_state = cx.new(|cx| TreeState::new(cx));
137+
let parent_entity = cx.entity();
138+
let tree_state = cx.new(move |cx| {
139+
TreeState::new(
140+
FileTreeDelegate {
141+
parent: parent_entity,
142+
},
143+
cx,
144+
)
145+
});
87146

88147
Self::load_files(tree_state.clone(), PathBuf::from("./"), cx);
89148

@@ -134,7 +193,6 @@ impl Render for TreeStory {
134193
_: &mut gpui::Window,
135194
cx: &mut gpui::Context<Self>,
136195
) -> impl gpui::IntoElement {
137-
let view = cx.entity();
138196
v_flex()
139197
.id("tree-story")
140198
.key_context(CONTEXT)
@@ -144,46 +202,18 @@ impl Render for TreeStory {
144202
.size_full()
145203
.child(
146204
section("File tree")
147-
.sub_title("Press `space` to select, `enter` to rename.")
205+
.sub_title(
206+
"Press `space` to select, `enter` to rename, right-click for context menu.",
207+
)
148208
.v_flex()
149209
.max_w_md()
150210
.child(
151-
tree(
152-
&self.tree_state,
153-
move |ix, entry, _selected, _window, cx| {
154-
view.update(cx, |_, cx| {
155-
let item = entry.item();
156-
let icon = if !entry.is_folder() {
157-
IconName::File
158-
} else if entry.is_expanded() {
159-
IconName::FolderOpen
160-
} else {
161-
IconName::Folder
162-
};
163-
164-
ListItem::new(ix)
165-
.w_full()
166-
.rounded(cx.theme().radius)
167-
.px_3()
168-
.pl(px(16.) * entry.depth() + px(12.))
169-
.child(
170-
h_flex().gap_2().child(icon).child(item.label.clone()),
171-
)
172-
.on_click(cx.listener({
173-
let item = item.clone();
174-
move |this, _, _window, cx| {
175-
this.selected_item = Some(item.clone());
176-
cx.notify();
177-
}
178-
}))
179-
})
180-
},
181-
)
182-
.p_1()
183-
.border_1()
184-
.border_color(cx.theme().border)
185-
.rounded(cx.theme().radius)
186-
.h(px(540.)),
211+
tree(&self.tree_state)
212+
.p_1()
213+
.border_1()
214+
.border_color(cx.theme().border)
215+
.rounded(cx.theme().radius)
216+
.h(px(540.)),
187217
)
188218
.child(
189219
h_flex()

0 commit comments

Comments
 (0)