Fix hover tooltips appearing after related element is pressed (#24540)

Closes https://github.com/zed-industries/zed/issues/23894

Reworks all trigger declarations from
`.trigger(element.tooltip(tooltip))` into
`.trigger_with_tooltip(element, tooltip)` , with new API disallowing
simultaneous trigger and tooltip display.

All existing `.trigger(` calls were replaced, except 2 not applicable
(in dock.rs and pane.rs), 15 left as ones without tooltips, and 2
unchanged places in `inline_completion_button.rs`, where


0f7bb2e9fd/crates/inline_completion_button/src/inline_completion_button.rs (L311-L319)

`with_animation` does not allow us to simply use the same approach.

Release Notes:

- Fixed hover tooltips appearing after related element is pressed

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
Kirill Bulatov 2025-02-10 02:16:12 +02:00 committed by GitHub
parent 1a133ab9d8
commit 6f7f0f30e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 218 additions and 180 deletions

View file

@ -250,10 +250,10 @@ impl AssistantPanel {
) )
.child( .child(
PopoverMenu::new("assistant-panel-popover-menu") PopoverMenu::new("assistant-panel-popover-menu")
.trigger( .trigger_with_tooltip(
IconButton::new("menu", IconName::EllipsisVertical) IconButton::new("menu", IconName::EllipsisVertical)
.icon_size(IconSize::Small) .icon_size(IconSize::Small),
.tooltip(Tooltip::text("Toggle Assistant Menu")), Tooltip::text("Toggle Assistant Menu"),
) )
.menu(move |window, cx| { .menu(move |window, cx| {
let zoom_label = if _pane.read(cx).is_zoomed() { let zoom_label = if _pane.read(cx).is_zoomed() {

View file

@ -1595,22 +1595,22 @@ impl Render for PromptEditor {
IconButton::new("context", IconName::SettingsAlt) IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_color(Color::Muted) .icon_color(Color::Muted),
.tooltip(move |window, cx| { move |window, cx| {
Tooltip::with_meta( Tooltip::with_meta(
format!( format!(
"Using {}", "Using {}",
LanguageModelRegistry::read_global(cx) LanguageModelRegistry::read_global(cx)
.active_model() .active_model()
.map(|model| model.name().0) .map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()), .unwrap_or_else(|| "No model selected".into()),
), ),
None, None,
"Change Model", "Change Model",
window, window,
cx, cx,
) )
}), },
)) ))
.map(|el| { .map(|el| {
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else { let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {

View file

@ -646,22 +646,22 @@ impl Render for PromptEditor {
IconButton::new("context", IconName::SettingsAlt) IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_color(Color::Muted) .icon_color(Color::Muted),
.tooltip(move |window, cx| { move |window, cx| {
Tooltip::with_meta( Tooltip::with_meta(
format!( format!(
"Using {}", "Using {}",
LanguageModelRegistry::read_global(cx) LanguageModelRegistry::read_global(cx)
.active_model() .active_model()
.map(|model| model.name().0) .map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()), .unwrap_or_else(|| "No model selected".into()),
), ),
None, None,
"Change Model", "Change Model",
window, window,
cx, cx,
) )
}), },
)) ))
.children( .children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status { if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {

View file

@ -74,16 +74,16 @@ impl Render for AssistantModelSelector {
.color(Color::Muted) .color(Color::Muted)
.size(IconSize::XSmall), .size(IconSize::XSmall),
), ),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
) )
.tooltip(move |window, cx| { },
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
}),
) )
.with_handle(self.menu_handle.clone()) .with_handle(self.menu_handle.clone())
} }

View file

@ -660,11 +660,11 @@ impl AssistantPanel {
.gap(DynamicSpacing::Base02.rems(cx)) .gap(DynamicSpacing::Base02.rems(cx))
.child( .child(
PopoverMenu::new("assistant-toolbar-new-popover-menu") PopoverMenu::new("assistant-toolbar-new-popover-menu")
.trigger( .trigger_with_tooltip(
IconButton::new("new", IconName::Plus) IconButton::new("new", IconName::Plus)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle),
.tooltip(Tooltip::text("New…")), Tooltip::text("New…"),
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(self.new_item_context_menu_handle.clone()) .with_handle(self.new_item_context_menu_handle.clone())
@ -677,11 +677,11 @@ impl AssistantPanel {
) )
.child( .child(
PopoverMenu::new("assistant-toolbar-history-popover-menu") PopoverMenu::new("assistant-toolbar-history-popover-menu")
.trigger( .trigger_with_tooltip(
IconButton::new("open-history", IconName::HistoryRerun) IconButton::new("open-history", IconName::HistoryRerun)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle),
.tooltip(Tooltip::text("History…")), Tooltip::text("History…"),
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(self.open_history_context_menu_handle.clone()) .with_handle(self.open_history_context_menu_handle.clone())

View file

@ -411,22 +411,22 @@ impl Render for ContextStrip {
Some(context_picker.clone()) Some(context_picker.clone())
}) })
.trigger( .trigger_with_tooltip(
IconButton::new("add-context", IconName::Plus) IconButton::new("add-context", IconName::Plus)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.style(ui::ButtonStyle::Filled) .style(ui::ButtonStyle::Filled),
.tooltip({ {
let focus_handle = focus_handle.clone(); let focus_handle = focus_handle.clone();
move |window, cx| { move |window, cx| {
Tooltip::for_action_in( Tooltip::for_action_in(
"Add Context", "Add Context",
&ToggleContextPicker, &ToggleContextPicker,
&focus_handle, &focus_handle,
window, window,
cx, cx,
) )
} }
}), },
) )
.attach(gpui::Corner::TopLeft) .attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft) .anchor(gpui::Corner::BottomLeft)

View file

@ -2359,8 +2359,8 @@ impl ContextEditor {
.icon(IconName::Plus) .icon(IconName::Plus)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start),
.tooltip(Tooltip::text("Type / to insert via keyboard")), Tooltip::text("Type / to insert via keyboard"),
) )
} }
@ -3323,10 +3323,10 @@ impl Render for ContextEditorToolbarItem {
.color(Color::Muted) .color(Color::Muted)
.size(IconSize::XSmall), .size(IconSize::XSmall),
), ),
) ),
.tooltip(move |window, cx| { move |window, cx| {
Tooltip::for_action("Change Model", &ToggleModelSelector, window, cx) Tooltip::for_action("Change Model", &ToggleModelSelector, window, cx)
}), },
) )
.with_handle(self.language_model_selector_menu_handle.clone()), .with_handle(self.language_model_selector_menu_handle.clone()),
) )

View file

@ -1,17 +1,22 @@
use std::sync::Arc; use std::sync::Arc;
use assistant_slash_command::SlashCommandWorkingSet; use assistant_slash_command::SlashCommandWorkingSet;
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakEntity}; use gpui::{AnyElement, AnyView, DismissEvent, SharedString, Task, WeakEntity};
use picker::{Picker, PickerDelegate, PickerEditorPosition}; use picker::{Picker, PickerDelegate, PickerEditorPosition};
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip}; use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
use crate::context_editor::ContextEditor; use crate::context_editor::ContextEditor;
#[derive(IntoElement)] #[derive(IntoElement)]
pub(super) struct SlashCommandSelector<T: PopoverTrigger> { pub(super) struct SlashCommandSelector<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
working_set: Arc<SlashCommandWorkingSet>, working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakEntity<ContextEditor>, active_context_editor: WeakEntity<ContextEditor>,
trigger: T, trigger: T,
tooltip: TT,
} }
#[derive(Clone)] #[derive(Clone)]
@ -48,16 +53,22 @@ pub(crate) struct SlashCommandDelegate {
selected_index: usize, selected_index: usize,
} }
impl<T: PopoverTrigger> SlashCommandSelector<T> { impl<T, TT> SlashCommandSelector<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
pub(crate) fn new( pub(crate) fn new(
working_set: Arc<SlashCommandWorkingSet>, working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakEntity<ContextEditor>, active_context_editor: WeakEntity<ContextEditor>,
trigger: T, trigger: T,
tooltip: TT,
) -> Self { ) -> Self {
SlashCommandSelector { SlashCommandSelector {
working_set, working_set,
active_context_editor, active_context_editor,
trigger, trigger,
tooltip,
} }
} }
} }
@ -241,7 +252,11 @@ impl PickerDelegate for SlashCommandDelegate {
} }
} }
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> { impl<T, TT> RenderOnce for SlashCommandSelector<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let all_models = self let all_models = self
.working_set .working_set
@ -322,7 +337,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
.ok(); .ok();
PopoverMenu::new("model-switcher") PopoverMenu::new("model-switcher")
.menu(move |_window, _cx| Some(picker_view.clone())) .menu(move |_window, _cx| Some(picker_view.clone()))
.trigger(self.trigger) .trigger_with_tooltip(self.trigger, self.tooltip)
.attach(gpui::Corner::TopLeft) .attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft) .anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point { .offset(gpui::Point {

View file

@ -763,7 +763,7 @@ impl Editor {
this.child({ this.child({
let focus = editor.focus_handle(cx); let focus = editor.focus_handle(cx);
PopoverMenu::new("hunk-controls-dropdown") PopoverMenu::new("hunk-controls-dropdown")
.trigger( .trigger_with_tooltip(
IconButton::new( IconButton::new(
"toggle_editor_selections_icon", "toggle_editor_selections_icon",
IconName::EllipsisVertical, IconName::EllipsisVertical,
@ -774,19 +774,8 @@ impl Editor {
.toggle_state( .toggle_state(
hunk_controls_menu_handle hunk_controls_menu_handle
.is_deployed(), .is_deployed(),
)
.when(
!hunk_controls_menu_handle
.is_deployed(),
|this| {
this.tooltip(|_, cx| {
Tooltip::simple(
"Hunk Controls",
cx,
)
})
},
), ),
Tooltip::simple("Hunk Controls", cx),
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(hunk_controls_menu_handle) .with_handle(hunk_controls_menu_handle)

View file

@ -1161,6 +1161,7 @@ impl GitPanel {
ButtonLike::new("active-repository") ButtonLike::new("active-repository")
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.child(Label::new(repository_display_name).size(LabelSize::Small)), .child(Label::new(repository_display_name).size(LabelSize::Small)),
Tooltip::text("Select a repository"),
) )
} }

View file

@ -1,6 +1,6 @@
use gpui::{ use gpui::{
AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, AnyElement, AnyView, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
Task, WeakEntity, Subscription, Task, WeakEntity,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::{ use project::{
@ -79,20 +79,27 @@ impl Render for RepositorySelector {
} }
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct RepositorySelectorPopoverMenu<T> pub struct RepositorySelectorPopoverMenu<T, TT>
where where
T: PopoverTrigger, T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{ {
repository_selector: Entity<RepositorySelector>, repository_selector: Entity<RepositorySelector>,
trigger: T, trigger: T,
tooltip: TT,
handle: Option<PopoverMenuHandle<RepositorySelector>>, handle: Option<PopoverMenuHandle<RepositorySelector>>,
} }
impl<T: PopoverTrigger> RepositorySelectorPopoverMenu<T> { impl<T, TT> RepositorySelectorPopoverMenu<T, TT>
pub fn new(repository_selector: Entity<RepositorySelector>, trigger: T) -> Self { where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
pub fn new(repository_selector: Entity<RepositorySelector>, trigger: T, tooltip: TT) -> Self {
Self { Self {
repository_selector, repository_selector,
trigger, trigger,
tooltip,
handle: None, handle: None,
} }
} }
@ -103,13 +110,17 @@ impl<T: PopoverTrigger> RepositorySelectorPopoverMenu<T> {
} }
} }
impl<T: PopoverTrigger> RenderOnce for RepositorySelectorPopoverMenu<T> { impl<T, TT> RenderOnce for RepositorySelectorPopoverMenu<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let repository_selector = self.repository_selector.clone(); let repository_selector = self.repository_selector.clone();
PopoverMenu::new("repository-switcher") PopoverMenu::new("repository-switcher")
.menu(move |_window, _cx| Some(repository_selector.clone())) .menu(move |_window, _cx| Some(repository_selector.clone()))
.trigger(self.trigger) .trigger_with_tooltip(self.trigger, self.tooltip)
.attach(gpui::Corner::BottomLeft) .attach(gpui::Corner::BottomLeft)
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle)) .when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
} }

View file

@ -142,9 +142,12 @@ impl Render for InlineCompletionButton {
}) })
}) })
.anchor(Corner::BottomRight) .anchor(Corner::BottomRight)
.trigger(IconButton::new("copilot-icon", icon).tooltip(|window, cx| { .trigger_with_tooltip(
Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx) IconButton::new("copilot-icon", icon),
})) |window, cx| {
Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx)
},
)
.with_handle(self.popover_menu_handle.clone()), .with_handle(self.popover_menu_handle.clone()),
) )
} }
@ -211,7 +214,8 @@ impl Render for InlineCompletionButton {
_ => None, _ => None,
}) })
.anchor(Corner::BottomRight) .anchor(Corner::BottomRight)
.trigger(IconButton::new("supermaven-icon", icon).tooltip( .trigger_with_tooltip(
IconButton::new("supermaven-icon", icon),
move |window, cx| { move |window, cx| {
if has_menu { if has_menu {
Tooltip::for_action( Tooltip::for_action(
@ -224,7 +228,7 @@ impl Render for InlineCompletionButton {
Tooltip::text(tooltip_text.clone())(window, cx) Tooltip::text(tooltip_text.clone())(window, cx)
} }
}, },
)) )
.with_handle(self.popover_menu_handle.clone()), .with_handle(self.popover_menu_handle.clone()),
); );
} }
@ -287,31 +291,6 @@ impl Render for InlineCompletionButton {
.when(enabled && !show_editor_predictions, |this| { .when(enabled && !show_editor_predictions, |this| {
this.indicator(Indicator::dot().color(Color::Muted)) this.indicator(Indicator::dot().color(Color::Muted))
.indicator_border_color(Some(cx.theme().colors().status_bar_background)) .indicator_border_color(Some(cx.theme().colors().status_bar_background))
})
.when(!self.popover_menu_handle.is_deployed(), |element| {
element.tooltip(move |window, cx| {
if enabled {
if show_editor_predictions {
Tooltip::for_action("Edit Prediction", &ToggleMenu, window, cx)
} else {
Tooltip::with_meta(
"Edit Prediction",
Some(&ToggleMenu),
"Hidden For This File",
window,
cx,
)
}
} else {
Tooltip::with_meta(
"Edit Prediction",
Some(&ToggleMenu),
"Disabled For This File",
window,
cx,
)
}
})
}); });
let this = cx.entity().clone(); let this = cx.entity().clone();

View file

@ -2,7 +2,7 @@ use std::sync::Arc;
use feature_flags::ZedPro; use feature_flags::ZedPro;
use gpui::{ use gpui::{
Action, AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Action, AnyElement, AnyView, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
Subscription, Task, WeakEntity, Subscription, Task, WeakEntity,
}; };
use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry}; use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry};
@ -115,20 +115,31 @@ impl Render for LanguageModelSelector {
} }
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct LanguageModelSelectorPopoverMenu<T> pub struct LanguageModelSelectorPopoverMenu<T, TT>
where where
T: PopoverTrigger, T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{ {
language_model_selector: Entity<LanguageModelSelector>, language_model_selector: Entity<LanguageModelSelector>,
trigger: T, trigger: T,
tooltip: TT,
handle: Option<PopoverMenuHandle<LanguageModelSelector>>, handle: Option<PopoverMenuHandle<LanguageModelSelector>>,
} }
impl<T: PopoverTrigger> LanguageModelSelectorPopoverMenu<T> { impl<T, TT> LanguageModelSelectorPopoverMenu<T, TT>
pub fn new(language_model_selector: Entity<LanguageModelSelector>, trigger: T) -> Self { where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
pub fn new(
language_model_selector: Entity<LanguageModelSelector>,
trigger: T,
tooltip: TT,
) -> Self {
Self { Self {
language_model_selector, language_model_selector,
trigger, trigger,
tooltip,
handle: None, handle: None,
} }
} }
@ -139,13 +150,17 @@ impl<T: PopoverTrigger> LanguageModelSelectorPopoverMenu<T> {
} }
} }
impl<T: PopoverTrigger> RenderOnce for LanguageModelSelectorPopoverMenu<T> { impl<T, TT> RenderOnce for LanguageModelSelectorPopoverMenu<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let language_model_selector = self.language_model_selector.clone(); let language_model_selector = self.language_model_selector.clone();
PopoverMenu::new("model-switcher") PopoverMenu::new("model-switcher")
.menu(move |_window, _cx| Some(language_model_selector.clone())) .menu(move |_window, _cx| Some(language_model_selector.clone()))
.trigger(self.trigger) .trigger_with_tooltip(self.trigger, self.tooltip)
.anchor(gpui::Corner::BottomRight) .anchor(gpui::Corner::BottomRight)
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle)) .when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
.offset(gpui::Point { .offset(gpui::Point {

View file

@ -2,6 +2,7 @@ use crate::kernels::KernelSpecification;
use crate::repl_store::ReplStore; use crate::repl_store::ReplStore;
use crate::KERNEL_DOCS_URL; use crate::KERNEL_DOCS_URL;
use gpui::AnyView;
use gpui::DismissEvent; use gpui::DismissEvent;
use gpui::FontWeight; use gpui::FontWeight;
@ -19,10 +20,15 @@ use ui::{prelude::*, ListItem, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
type OnSelect = Box<dyn Fn(KernelSpecification, &mut Window, &mut App)>; type OnSelect = Box<dyn Fn(KernelSpecification, &mut Window, &mut App)>;
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct KernelSelector<T: PopoverTrigger> { pub struct KernelSelector<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
handle: Option<PopoverMenuHandle<Picker<KernelPickerDelegate>>>, handle: Option<PopoverMenuHandle<Picker<KernelPickerDelegate>>>,
on_select: OnSelect, on_select: OnSelect,
trigger: T, trigger: T,
tooltip: TT,
info_text: Option<SharedString>, info_text: Option<SharedString>,
worktree_id: WorktreeId, worktree_id: WorktreeId,
} }
@ -44,12 +50,17 @@ fn truncate_path(path: &SharedString, max_length: usize) -> SharedString {
} }
} }
impl<T: PopoverTrigger> KernelSelector<T> { impl<T, TT> KernelSelector<T, TT>
pub fn new(on_select: OnSelect, worktree_id: WorktreeId, trigger: T) -> Self { where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
pub fn new(on_select: OnSelect, worktree_id: WorktreeId, trigger: T, tooltip: TT) -> Self {
KernelSelector { KernelSelector {
on_select, on_select,
handle: None, handle: None,
trigger, trigger,
tooltip,
info_text: None, info_text: None,
worktree_id, worktree_id,
} }
@ -235,7 +246,11 @@ impl PickerDelegate for KernelPickerDelegate {
} }
} }
impl<T: PopoverTrigger> RenderOnce for KernelSelector<T> { impl<T, TT> RenderOnce for KernelSelector<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let store = ReplStore::global(cx).read(cx); let store = ReplStore::global(cx).read(cx);
@ -262,7 +277,7 @@ impl<T: PopoverTrigger> RenderOnce for KernelSelector<T> {
PopoverMenu::new("kernel-switcher") PopoverMenu::new("kernel-switcher")
.menu(move |_window, _cx| Some(picker_view.clone())) .menu(move |_window, _cx| Some(picker_view.clone()))
.trigger(self.trigger) .trigger_with_tooltip(self.trigger, self.tooltip)
.attach(gpui::Corner::BottomLeft) .attach(gpui::Corner::BottomLeft)
.when_some(self.handle, |menu, handle| menu.with_handle(handle)) .when_some(self.handle, |menu, handle| menu.with_handle(handle))
} }

View file

@ -139,10 +139,9 @@ impl TerminalPanel {
.gap(DynamicSpacing::Base02.rems(cx)) .gap(DynamicSpacing::Base02.rems(cx))
.child( .child(
PopoverMenu::new("terminal-tab-bar-popover-menu") PopoverMenu::new("terminal-tab-bar-popover-menu")
.trigger( .trigger_with_tooltip(
IconButton::new("plus", IconName::Plus) IconButton::new("plus", IconName::Plus).icon_size(IconSize::Small),
.icon_size(IconSize::Small) Tooltip::text("New…"),
.tooltip(Tooltip::text("New…")),
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(pane.new_item_context_menu_handle.clone()) .with_handle(pane.new_item_context_menu_handle.clone())
@ -169,10 +168,10 @@ impl TerminalPanel {
.children(assistant_tab_bar_button.clone()) .children(assistant_tab_bar_button.clone())
.child( .child(
PopoverMenu::new("terminal-pane-tab-bar-split") PopoverMenu::new("terminal-pane-tab-bar-split")
.trigger( .trigger_with_tooltip(
IconButton::new("terminal-pane-split", IconName::Split) IconButton::new("terminal-pane-split", IconName::Split)
.icon_size(IconSize::Small) .icon_size(IconSize::Small),
.tooltip(Tooltip::text("Split Pane")), Tooltip::text("Split Pane"),
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(pane.split_item_context_menu_handle.clone()) .with_handle(pane.split_item_context_menu_handle.clone())

View file

@ -133,16 +133,14 @@ impl ApplicationMenu {
.menu(move |window, cx| { .menu(move |window, cx| {
Self::build_menu_from_items(entry.clone(), window, cx).into() Self::build_menu_from_items(entry.clone(), window, cx).into()
}) })
.trigger( .trigger_with_tooltip(
IconButton::new( IconButton::new(
SharedString::from(format!("{}-menu-trigger", menu_name)), SharedString::from(format!("{}-menu-trigger", menu_name)),
ui::IconName::Menu, ui::IconName::Menu,
) )
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.icon_size(IconSize::Small) .icon_size(IconSize::Small),
.when(!handle.is_deployed(), |this| { Tooltip::text("Open Application Menu"),
this.tooltip(Tooltip::text("Open Application Menu"))
}),
) )
.with_handle(handle), .with_handle(handle),
) )

View file

@ -690,7 +690,7 @@ impl TitleBar {
}) })
.into() .into()
}) })
.trigger( .trigger_with_tooltip(
ButtonLike::new("user-menu") ButtonLike::new("user-menu")
.child( .child(
h_flex() h_flex()
@ -706,8 +706,8 @@ impl TitleBar {
.color(Color::Muted), .color(Color::Muted),
), ),
) )
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle),
.tooltip(Tooltip::text("Toggle User Menu")), Tooltip::text("Toggle User Menu"),
) )
.anchor(gpui::Corner::TopRight) .anchor(gpui::Corner::TopRight)
} else { } else {
@ -736,10 +736,9 @@ impl TitleBar {
}) })
.into() .into()
}) })
.trigger( .trigger_with_tooltip(
IconButton::new("user-menu", IconName::ChevronDown) IconButton::new("user-menu", IconName::ChevronDown).icon_size(IconSize::Small),
.icon_size(IconSize::Small) Tooltip::text("Toggle User Menu"),
.tooltip(Tooltip::text("Toggle User Menu")),
) )
} }
} }

View file

@ -3,8 +3,8 @@
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use gpui::{ use gpui::{
anchored, deferred, div, point, prelude::FluentBuilder, px, size, AnyElement, App, Bounds, anchored, deferred, div, point, prelude::FluentBuilder, px, size, AnyElement, AnyView, App,
Corner, DismissEvent, DispatchPhase, Element, ElementId, Entity, Focusable as _, Bounds, Corner, DismissEvent, DispatchPhase, Element, ElementId, Entity, Focusable as _,
GlobalElementId, HitboxId, InteractiveElement, IntoElement, LayoutId, Length, ManagedView, GlobalElementId, HitboxId, InteractiveElement, IntoElement, LayoutId, Length, ManagedView,
MouseDownEvent, ParentElement, Pixels, Point, Style, Window, MouseDownEvent, ParentElement, Pixels, Point, Style, Window,
}; };
@ -178,6 +178,28 @@ impl<M: ManagedView> PopoverMenu<M> {
self self
} }
pub fn trigger_with_tooltip<T: PopoverTrigger + ButtonCommon>(
mut self,
t: T,
tooltip_builder: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
) -> Self {
let on_open = self.on_open.clone();
self.child_builder = Some(Box::new(move |menu, builder| {
let open = menu.borrow().is_some();
t.toggle_state(open)
.when_some(builder, |el, builder| {
el.on_click(move |_, window, cx| {
show_menu(&builder, &menu, on_open.clone(), window, cx)
})
.when(!open, |t| {
t.tooltip(move |window, cx| tooltip_builder(window, cx))
})
})
.into_any_element()
}));
self
}
/// anchor defines which corner of the menu to anchor to the attachment point /// anchor defines which corner of the menu to anchor to the attachment point
/// (by default the cursor position, but see attach) /// (by default the cursor position, but see attach)
pub fn anchor(mut self, anchor: Corner) -> Self { pub fn anchor(mut self, anchor: Corner) -> Self {

View file

@ -441,10 +441,9 @@ impl Pane {
.gap(DynamicSpacing::Base04.rems(cx)) .gap(DynamicSpacing::Base04.rems(cx))
.child( .child(
PopoverMenu::new("pane-tab-bar-popover-menu") PopoverMenu::new("pane-tab-bar-popover-menu")
.trigger( .trigger_with_tooltip(
IconButton::new("plus", IconName::Plus) IconButton::new("plus", IconName::Plus).icon_size(IconSize::Small),
.icon_size(IconSize::Small) Tooltip::text("New..."),
.tooltip(Tooltip::text("New...")),
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(pane.new_item_context_menu_handle.clone()) .with_handle(pane.new_item_context_menu_handle.clone())
@ -474,10 +473,10 @@ impl Pane {
) )
.child( .child(
PopoverMenu::new("pane-tab-bar-split") PopoverMenu::new("pane-tab-bar-split")
.trigger( .trigger_with_tooltip(
IconButton::new("split", IconName::Split) IconButton::new("split", IconName::Split)
.icon_size(IconSize::Small) .icon_size(IconSize::Small),
.tooltip(Tooltip::text("Split Pane")), Tooltip::text("Split Pane"),
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(pane.split_item_context_menu_handle.clone()) .with_handle(pane.split_item_context_menu_handle.clone())

View file

@ -168,15 +168,13 @@ impl Render for QuickActionBar {
let focus = editor.focus_handle(cx); let focus = editor.focus_handle(cx);
PopoverMenu::new("editor-selections-dropdown") PopoverMenu::new("editor-selections-dropdown")
.trigger( .trigger_with_tooltip(
IconButton::new("toggle_editor_selections_icon", IconName::CursorIBeam) IconButton::new("toggle_editor_selections_icon", IconName::CursorIBeam)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.toggle_state(self.toggle_selections_handle.is_deployed()) .toggle_state(self.toggle_selections_handle.is_deployed()),
.when(!self.toggle_selections_handle.is_deployed(), |this| { Tooltip::text("Selection Controls"),
this.tooltip(Tooltip::text("Selection Controls"))
}),
) )
.with_handle(self.toggle_selections_handle.clone()) .with_handle(self.toggle_selections_handle.clone())
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
@ -219,15 +217,13 @@ impl Render for QuickActionBar {
let vim_mode_enabled = VimModeSetting::get_global(cx).0; let vim_mode_enabled = VimModeSetting::get_global(cx).0;
PopoverMenu::new("editor-settings") PopoverMenu::new("editor-settings")
.trigger( .trigger_with_tooltip(
IconButton::new("toggle_editor_settings_icon", IconName::Sliders) IconButton::new("toggle_editor_settings_icon", IconName::Sliders)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.toggle_state(self.toggle_settings_handle.is_deployed()) .toggle_state(self.toggle_settings_handle.is_deployed()),
.when(!self.toggle_settings_handle.is_deployed(), |this| { Tooltip::text("Editor Controls"),
this.tooltip(Tooltip::text("Editor Controls"))
}),
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(self.toggle_settings_handle.clone()) .with_handle(self.toggle_settings_handle.clone())

View file

@ -209,16 +209,16 @@ impl QuickActionBar {
}) })
.into() .into()
}) })
.trigger( .trigger_with_tooltip(
ButtonLike::new_rounded_right(element_id("dropdown")) ButtonLike::new_rounded_right(element_id("dropdown"))
.child( .child(
Icon::new(IconName::ChevronDownSmall) Icon::new(IconName::ChevronDownSmall)
.size(IconSize::XSmall) .size(IconSize::XSmall)
.color(Color::Muted), .color(Color::Muted),
) )
.tooltip(Tooltip::text("REPL Menu"))
.width(rems(1.).into()) .width(rems(1.).into())
.disabled(menu_state.popover_disabled), .disabled(menu_state.popover_disabled),
Tooltip::text("REPL Menu"),
); );
let button = ButtonLike::new_rounded_left("toggle_repl_icon") let button = ButtonLike::new_rounded_left("toggle_repl_icon")
@ -343,8 +343,8 @@ impl QuickActionBar {
.color(Color::Muted) .color(Color::Muted)
.size(IconSize::XSmall), .size(IconSize::XSmall),
), ),
) ),
.tooltip(Tooltip::text("Select Kernel")), Tooltip::text("Select Kernel"),
) )
.with_handle(menu_handle.clone()) .with_handle(menu_handle.clone())
.into_any_element() .into_any_element()