diff --git a/assets/icons/maximize_alt.svg b/assets/icons/maximize_alt.svg
new file mode 100644
index 0000000000000000000000000000000000000000..b8b8705f902c2469ed959f93f89ca3caf3b8fc51
--- /dev/null
+++ b/assets/icons/maximize_alt.svg
@@ -0,0 +1,6 @@
+
diff --git a/crates/agent_ui/src/config_options.rs b/crates/agent_ui/src/config_options.rs
index b8cf7e5d57921c7710392911829fc2b5045a0f90..44c0baa232222c0ba7c1d54acdecaabacfa85f12 100644
--- a/crates/agent_ui/src/config_options.rs
+++ b/crates/agent_ui/src/config_options.rs
@@ -650,7 +650,7 @@ impl PickerDelegate for ConfigOptionPickerDelegate {
.end_slot(div().pr_2().when(is_selected, |this| {
this.child(Icon::new(IconName::Check).color(Color::Accent))
}))
- .end_hover_slot(div().pr_1p5().child({
+ .end_slot_on_hover(div().pr_1p5().child({
let (icon, color, tooltip) = if is_favorite {
(IconName::StarFilled, Color::Accent, "Unfavorite")
} else {
diff --git a/crates/agent_ui/src/ui/model_selector_components.rs b/crates/agent_ui/src/ui/model_selector_components.rs
index 01ba6c4511854e83b97b1fc053e41e5d0e82ff1e..88bf546a0e7beef53c8043fd04f8e6e9e5e92c88 100644
--- a/crates/agent_ui/src/ui/model_selector_components.rs
+++ b/crates/agent_ui/src/ui/model_selector_components.rs
@@ -160,7 +160,7 @@ impl RenderOnce for ModelSelectorListItem {
.end_slot(div().pr_2().when(self.is_selected, |this| {
this.child(Icon::new(IconName::Check).color(Color::Accent))
}))
- .end_hover_slot(div().pr_1p5().when_some(self.on_toggle_favorite, {
+ .end_slot_on_hover(div().pr_1p5().when_some(self.on_toggle_favorite, {
|this, handle_click| {
let (icon, color, tooltip) = if is_favorite {
(IconName::StarFilled, Color::Accent, "Unfavorite Model")
diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs
index 438df6839949d46d3ba8e0509995beb1300b7c80..83c8119a077ac1c024dbb3b3df948f762b072ec1 100644
--- a/crates/git_ui/src/branch_picker.rs
+++ b/crates/git_ui/src/branch_picker.rs
@@ -1087,13 +1087,8 @@ impl PickerDelegate for BranchListDelegate {
),
)
.when(!is_new_items && !is_head_branch, |this| {
- this.map(|this| {
- if self.selected_index() == ix {
- this.end_slot(deleted_branch_icon(ix))
- } else {
- this.end_hover_slot(deleted_branch_icon(ix))
- }
- })
+ this.end_slot(deleted_branch_icon(ix))
+ .show_end_slot_on_hover()
})
.when_some(
if is_new_items {
@@ -1102,13 +1097,8 @@ impl PickerDelegate for BranchListDelegate {
None
},
|this, create_from_default_button| {
- this.map(|this| {
- if self.selected_index() == ix {
- this.end_slot(create_from_default_button)
- } else {
- this.end_hover_slot(create_from_default_button)
- }
- })
+ this.end_slot(create_from_default_button)
+ .show_end_slot_on_hover()
},
),
)
diff --git a/crates/git_ui/src/stash_picker.rs b/crates/git_ui/src/stash_picker.rs
index 9987190f45b73f3f1132ce1295de6f412022abe2..2d3515e833e4d353c323f533f1f0f39bb1d76561 100644
--- a/crates/git_ui/src/stash_picker.rs
+++ b/crates/git_ui/src/stash_picker.rs
@@ -501,16 +501,39 @@ impl PickerDelegate for StashListDelegate {
.size(LabelSize::Small),
);
- let focus_handle = self.focus_handle.clone();
+ let view_button = {
+ let focus_handle = self.focus_handle.clone();
+ IconButton::new(("view-stash", ix), IconName::Eye)
+ .icon_size(IconSize::Small)
+ .tooltip(move |_, cx| {
+ Tooltip::for_action_in("View Stash", &ShowStashItem, &focus_handle, cx)
+ })
+ .on_click(cx.listener(move |this, _, window, cx| {
+ this.delegate.show_stash_at(ix, window, cx);
+ }))
+ };
+
+ let pop_button = {
+ let focus_handle = self.focus_handle.clone();
+ IconButton::new(("pop-stash", ix), IconName::MaximizeAlt)
+ .icon_size(IconSize::Small)
+ .tooltip(move |_, cx| {
+ Tooltip::for_action_in("Pop Stash", &menu::SecondaryConfirm, &focus_handle, cx)
+ })
+ .on_click(|_, window, cx| {
+ window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx);
+ })
+ };
- let drop_button = |entry_ix: usize| {
- IconButton::new(("drop-stash", entry_ix), IconName::Trash)
+ let drop_button = {
+ let focus_handle = self.focus_handle.clone();
+ IconButton::new(("drop-stash", ix), IconName::Trash)
.icon_size(IconSize::Small)
.tooltip(move |_, cx| {
Tooltip::for_action_in("Drop Stash", &DropStashItem, &focus_handle, cx)
})
.on_click(cx.listener(move |this, _, window, cx| {
- this.delegate.drop_stash_at(entry_ix, window, cx);
+ this.delegate.drop_stash_at(ix, window, cx);
}))
};
@@ -530,17 +553,14 @@ impl PickerDelegate for StashListDelegate {
)
.child(div().w_full().child(stash_label).child(branch_info)),
)
- .tooltip(Tooltip::text(format!(
- "stash@{{{}}}",
- entry_match.entry.index
- )))
- .map(|this| {
- if selected {
- this.end_slot(drop_button(ix))
- } else {
- this.end_hover_slot(drop_button(ix))
- }
- }),
+ .end_slot(
+ h_flex()
+ .gap_0p5()
+ .child(view_button)
+ .child(pop_button)
+ .child(drop_button),
+ )
+ .show_end_slot_on_hover(),
)
}
@@ -549,6 +569,10 @@ impl PickerDelegate for StashListDelegate {
}
fn render_footer(&self, _: &mut Window, cx: &mut Context>) -> Option {
+ if self.matches.is_empty() {
+ return None;
+ }
+
let focus_handle = self.focus_handle.clone();
Some(
diff --git a/crates/git_ui/src/worktree_picker.rs b/crates/git_ui/src/worktree_picker.rs
index 3e14b56f9bf4a95452855bc6cbef6f764e2b3530..c3e2259e411c7a3a56a36b92735f8d5e014e53d7 100644
--- a/crates/git_ui/src/worktree_picker.rs
+++ b/crates/git_ui/src/worktree_picker.rs
@@ -884,12 +884,30 @@ impl PickerDelegate for WorktreeListDelegate {
}
})),
)
- .when(can_delete, |this| {
- if selected {
- this.end_slot(delete_button(ix))
- } else {
- this.end_hover_slot(delete_button(ix))
- }
+ .when(!entry.is_new, |this| {
+ let focus_handle = self.focus_handle.clone();
+ let open_in_new_window_button =
+ IconButton::new(("open-new-window", ix), IconName::ArrowUpRight)
+ .icon_size(IconSize::Small)
+ .tooltip(move |_, cx| {
+ Tooltip::for_action_in(
+ "Open in New Window",
+ &menu::SecondaryConfirm,
+ &focus_handle,
+ cx,
+ )
+ })
+ .on_click(|_, window, cx| {
+ window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx);
+ });
+
+ this.end_slot(
+ h_flex()
+ .gap_0p5()
+ .child(open_in_new_window_button)
+ .when(can_delete, |this| this.child(delete_button(ix))),
+ )
+ .show_end_slot_on_hover()
}),
)
}
diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs
index ad7faa6664d1ddc618c8984781a244af7dda6c97..6929ae4e4ca8ca0ee00c9793c948892043dd6dd6 100644
--- a/crates/icons/src/icons.rs
+++ b/crates/icons/src/icons.rs
@@ -174,6 +174,7 @@ pub enum IconName {
LockOutlined,
MagnifyingGlass,
Maximize,
+ MaximizeAlt,
Menu,
MenuAltTemp,
Mic,
diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs
index 4dc06036ef8416fd859cc815ab090ba5896c0040..22987f6c56669e1972a9bfc940449991d9f55642 100644
--- a/crates/recent_projects/src/recent_projects.rs
+++ b/crates/recent_projects/src/recent_projects.rs
@@ -1264,13 +1264,8 @@ impl PickerDelegate for RecentProjectsDelegate {
this.tooltip(Tooltip::text(path.to_string_lossy().to_string()))
}),
)
- .map(|el| {
- if self.selected_index == ix {
- el.end_slot(secondary_actions)
- } else {
- el.end_hover_slot(secondary_actions)
- }
- })
+ .end_slot(secondary_actions)
+ .show_end_slot_on_hover()
.into_any_element(),
)
}
@@ -1363,13 +1358,8 @@ impl PickerDelegate for RecentProjectsDelegate {
})
.tooltip(Tooltip::text(tooltip_path)),
)
- .map(|el| {
- if self.selected_index == ix {
- el.end_slot(secondary_actions)
- } else {
- el.end_hover_slot(secondary_actions)
- }
- })
+ .end_slot(secondary_actions)
+ .show_end_slot_on_hover()
.into_any_element(),
)
}
@@ -1503,13 +1493,8 @@ impl PickerDelegate for RecentProjectsDelegate {
})
.tooltip(Tooltip::text(tooltip_path)),
)
- .map(|el| {
- if self.selected_index == ix {
- el.end_slot(secondary_actions)
- } else {
- el.end_hover_slot(secondary_actions)
- }
- })
+ .end_slot(secondary_actions)
+ .show_end_slot_on_hover()
.into_any_element(),
)
}
diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs
index f7054687579155d4895ae191de1b7fa7cd14fbf6..26592a8035d50caa4e267a5478d5aceb9fba6e3e 100644
--- a/crates/recent_projects/src/remote_servers.rs
+++ b/crates/recent_projects/src/remote_servers.rs
@@ -1621,23 +1621,24 @@ impl RemoteServerProjects {
}))
.tooltip(Tooltip::text(project.paths.join("\n")))
.when(is_from_zed, |server_list_item| {
- server_list_item.end_hover_slot::(Some(
- div()
- .mr_2()
- .child({
- let project = project.clone();
- // Right-margin to offset it from the Scrollbar
- IconButton::new("remove-remote-project", IconName::Trash)
- .icon_size(IconSize::Small)
- .shape(IconButtonShape::Square)
- .size(ButtonSize::Large)
- .tooltip(Tooltip::text("Delete Remote Project"))
- .on_click(cx.listener(move |this, _, _, cx| {
- this.delete_remote_project(server_ix, &project, cx)
- }))
- })
- .into_any_element(),
- ))
+ server_list_item
+ .end_slot(
+ div()
+ .mr_2()
+ .child({
+ let project = project.clone();
+ IconButton::new("remove-remote-project", IconName::Trash)
+ .icon_size(IconSize::Small)
+ .shape(IconButtonShape::Square)
+ .size(ButtonSize::Large)
+ .tooltip(Tooltip::text("Delete Remote Project"))
+ .on_click(cx.listener(move |this, _, _, cx| {
+ this.delete_remote_project(server_ix, &project, cx)
+ }))
+ })
+ .into_any_element(),
+ )
+ .show_end_slot_on_hover()
}),
)
}
@@ -2413,9 +2414,8 @@ impl RemoteServerProjects {
.spacing(ui::ListItemSpacing::Sparse)
.start_slot(Icon::new(IconName::Copy).color(Color::Muted))
.child(Label::new("Copy Server Address"))
- .end_hover_slot(
- Label::new(connection_string.clone()).color(Color::Muted),
- )
+ .end_slot(Label::new(connection_string.clone()).color(Color::Muted))
+ .show_end_slot_on_hover()
.on_click({
let connection_string = connection_string.clone();
move |_, _, cx| {
diff --git a/crates/rules_library/src/rules_library.rs b/crates/rules_library/src/rules_library.rs
index 23e7b83772f755089c49f824719af389ec589bd9..7e5a56f22d48c4d51f60d7d200dc8384582beb23 100644
--- a/crates/rules_library/src/rules_library.rs
+++ b/crates/rules_library/src/rules_library.rs
@@ -389,7 +389,7 @@ impl PickerDelegate for RulePickerDelegate {
}))
}))
.when(!prompt_id.is_built_in(), |this| {
- this.end_hover_slot(
+ this.end_slot_on_hover(
h_flex()
.child(
IconButton::new("delete-rule", IconName::Trash)
diff --git a/crates/tab_switcher/src/tab_switcher.rs b/crates/tab_switcher/src/tab_switcher.rs
index 0fb13c85d21797e4d57728c88fc8bb014a898f78..d1e19ea4faee8d8259d06e2c24875faac7a0117c 100644
--- a/crates/tab_switcher/src/tab_switcher.rs
+++ b/crates/tab_switcher/src/tab_switcher.rs
@@ -875,7 +875,7 @@ impl PickerDelegate for TabSwitcherDelegate {
el.end_slot::(close_button)
} else {
el.end_slot::(indicator)
- .end_hover_slot::(close_button)
+ .end_slot_on_hover::(close_button)
}
}),
)
diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs
index 34f0cd809692d649bcfbabb7952f3075618ead04..285a07c9562849b26b4cbba3de3979614384d875 100644
--- a/crates/tasks_ui/src/modal.rs
+++ b/crates/tasks_ui/src/modal.rs
@@ -570,7 +570,7 @@ impl PickerDelegate for TasksModalDelegate {
Tooltip::simple("Delete Previously Scheduled Task", cx)
}),
);
- item.end_hover_slot(delete_button)
+ item.end_slot_on_hover(delete_button)
} else {
item
}
diff --git a/crates/ui/src/components/list/list_item.rs b/crates/ui/src/components/list/list_item.rs
index 0d3efc024f1a202947fd0e7b0dab917c40ae8337..9a764efd58cfd3365d92e534a715a0f23ce46e90 100644
--- a/crates/ui/src/components/list/list_item.rs
+++ b/crates/ui/src/components/list/list_item.rs
@@ -14,6 +14,14 @@ pub enum ListItemSpacing {
Sparse,
}
+#[derive(Default)]
+enum EndSlotVisibility {
+ #[default]
+ Always,
+ OnHover,
+ SwapOnHover(AnyElement),
+}
+
#[derive(IntoElement, RegisterComponent)]
pub struct ListItem {
id: ElementId,
@@ -28,9 +36,7 @@ pub struct ListItem {
/// A slot for content that appears after the children, usually on the other side of the header.
/// This might be a button, a disclosure arrow, a face pile, etc.
end_slot: Option,
- /// A slot for content that appears on hover after the children
- /// It will obscure the `end_slot` when visible.
- end_hover_slot: Option,
+ end_slot_visibility: EndSlotVisibility,
toggle: Option,
inset: bool,
on_click: Option>,
@@ -61,7 +67,7 @@ impl ListItem {
indent_step_size: px(12.),
start_slot: None,
end_slot: None,
- end_hover_slot: None,
+ end_slot_visibility: EndSlotVisibility::default(),
toggle: None,
inset: false,
on_click: None,
@@ -165,8 +171,14 @@ impl ListItem {
self
}
- pub fn end_hover_slot(mut self, end_hover_slot: impl Into