Detailed changes
@@ -3,10 +3,9 @@ use editor::Editor;
use extension::ExtensionStore;
use futures::StreamExt;
use gpui::{
- actions, anchored, deferred, percentage, Animation, AnimationExt as _, AppContext, CursorStyle,
- DismissEvent, EventEmitter, InteractiveElement as _, Model, ParentElement as _, Render,
- SharedString, StatefulInteractiveElement, Styled, Transformation, View, ViewContext,
- VisualContext as _,
+ actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
+ InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
+ StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
};
use language::{
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
@@ -14,7 +13,7 @@ use language::{
use project::{LanguageServerProgress, Project};
use smallvec::SmallVec;
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
-use ui::{prelude::*, ContextMenu};
+use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle};
use workspace::{item::ItemHandle, StatusItemView, Workspace};
actions!(activity_indicator, [ShowErrorMessage]);
@@ -27,7 +26,7 @@ pub struct ActivityIndicator {
statuses: Vec<LspStatus>,
project: Model<Project>,
auto_updater: Option<Model<AutoUpdater>>,
- context_menu: Option<View<ContextMenu>>,
+ context_menu_handle: PopoverMenuHandle<ContextMenu>,
}
struct LspStatus {
@@ -79,7 +78,7 @@ impl ActivityIndicator {
statuses: Default::default(),
project: project.clone(),
auto_updater,
- context_menu: None,
+ context_menu_handle: Default::default(),
}
});
@@ -368,72 +367,7 @@ impl ActivityIndicator {
}
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
- if self.context_menu.take().is_some() {
- return;
- }
-
- self.build_lsp_work_context_menu(cx);
- cx.notify();
- }
-
- fn build_lsp_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
- let mut has_work = false;
- let this = cx.view().downgrade();
- let context_menu = ContextMenu::build(cx, |mut menu, cx| {
- for work in self.pending_language_server_work(cx) {
- has_work = true;
-
- let this = this.clone();
- let title = SharedString::from(
- work.progress
- .title
- .as_deref()
- .unwrap_or(work.progress_token)
- .to_string(),
- );
- if work.progress.is_cancellable {
- let language_server_id = work.language_server_id;
- let token = work.progress_token.to_string();
- menu = menu.custom_entry(
- move |_| {
- h_flex()
- .w_full()
- .justify_between()
- .child(Label::new(title.clone()))
- .child(Icon::new(IconName::XCircle))
- .into_any_element()
- },
- move |cx| {
- this.update(cx, |this, cx| {
- this.project.update(cx, |project, cx| {
- project.cancel_language_server_work(
- language_server_id,
- Some(token.clone()),
- cx,
- );
- });
- this.context_menu.take();
- })
- .ok();
- },
- );
- } else {
- menu = menu.label(title.clone());
- }
- }
- menu
- });
-
- if has_work {
- cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
- this.context_menu.take();
- cx.notify();
- })
- .detach();
- cx.focus_view(&context_menu);
- self.context_menu = Some(context_menu);
- cx.notify();
- }
+ self.context_menu_handle.toggle(cx);
}
}
@@ -455,19 +389,72 @@ impl Render for ActivityIndicator {
on_click(this, cx);
}))
}
-
- result
- .gap_2()
- .children(content.icon)
- .child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
- .children(self.context_menu.as_ref().map(|menu| {
- deferred(
- anchored()
- .anchor(gpui::AnchorCorner::BottomLeft)
- .child(menu.clone()),
+ let this = cx.view().downgrade();
+ result.gap_2().child(
+ PopoverMenu::new("activity-indicator-popover")
+ .trigger(
+ ButtonLike::new("activity-indicator-trigger").child(
+ h_flex()
+ .gap_2()
+ .children(content.icon)
+ .child(Label::new(content.message).size(LabelSize::Small)),
+ ),
)
- .with_priority(1)
- }))
+ .anchor(gpui::AnchorCorner::BottomLeft)
+ .menu(move |cx| {
+ let strong_this = this.upgrade()?;
+ ContextMenu::build(cx, |mut menu, cx| {
+ for work in strong_this.read(cx).pending_language_server_work(cx) {
+ let this = this.clone();
+ let mut title = work
+ .progress
+ .title
+ .as_deref()
+ .unwrap_or(work.progress_token)
+ .to_owned();
+
+ if work.progress.is_cancellable {
+ let language_server_id = work.language_server_id;
+ let token = work.progress_token.to_string();
+ let title = SharedString::from(title);
+ menu = menu.custom_entry(
+ move |_| {
+ h_flex()
+ .w_full()
+ .justify_between()
+ .child(Label::new(title.clone()))
+ .child(Icon::new(IconName::XCircle))
+ .into_any_element()
+ },
+ move |cx| {
+ this.update(cx, |this, cx| {
+ this.project.update(cx, |project, cx| {
+ project.cancel_language_server_work(
+ language_server_id,
+ Some(token.clone()),
+ cx,
+ );
+ });
+ this.context_menu_handle.hide(cx);
+ cx.notify();
+ })
+ .ok();
+ },
+ );
+ } else {
+ if let Some(progress_message) = work.progress.message.as_ref() {
+ title.push_str(": ");
+ title.push_str(progress_message);
+ }
+
+ menu = menu.label(title);
+ }
+ }
+ menu
+ })
+ .into()
+ }),
+ )
}
}
@@ -36,10 +36,10 @@ use fs::Fs;
use gpui::{
canvas, div, img, percentage, point, pulsating_between, size, Action, Animation, AnimationExt,
AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem,
- Context as _, DismissEvent, Empty, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
- FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels, ReadGlobal, Render,
- RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task,
- Transformation, UpdateGlobal, View, VisualContext, WeakView, WindowContext,
+ Context as _, Empty, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, FontWeight,
+ InteractiveElement, IntoElement, Model, ParentElement, Pixels, ReadGlobal, Render, RenderImage,
+ SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
+ UpdateGlobal, View, VisualContext, WeakView, WindowContext,
};
use indexed_docs::IndexedDocsStore;
use language::{
@@ -349,6 +349,7 @@ impl AssistantPanel {
model_summary_editor.clone(),
)
});
+
let pane = cx.new_view(|cx| {
let mut pane = Pane::new(
workspace.weak_handle(),
@@ -385,6 +386,7 @@ impl AssistantPanel {
pane.active_item()
.map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
);
+ let _pane = cx.view().clone();
let right_children = h_flex()
.gap(Spacing::Small.rems(cx))
.child(
@@ -395,32 +397,27 @@ impl AssistantPanel {
.tooltip(|cx| Tooltip::for_action("New Context", &NewFile, cx)),
)
.child(
- IconButton::new("menu", IconName::Menu)
- .icon_size(IconSize::Small)
- .on_click(cx.listener(|pane, _, cx| {
- let zoom_label = if pane.is_zoomed() {
+ PopoverMenu::new("assistant-panel-popover-menu")
+ .trigger(
+ IconButton::new("menu", IconName::Menu).icon_size(IconSize::Small),
+ )
+ .menu(move |cx| {
+ let zoom_label = if _pane.read(cx).is_zoomed() {
"Zoom Out"
} else {
"Zoom In"
};
- let menu = ContextMenu::build(cx, |menu, cx| {
- menu.context(pane.focus_handle(cx))
+ let focus_handle = _pane.focus_handle(cx);
+ Some(ContextMenu::build(cx, move |menu, _| {
+ menu.context(focus_handle.clone())
.action("New Context", Box::new(NewFile))
.action("History", Box::new(DeployHistory))
.action("Prompt Library", Box::new(DeployPromptLibrary))
.action("Configure", Box::new(ShowConfiguration))
.action(zoom_label, Box::new(ToggleZoom))
- });
- cx.subscribe(&menu, |pane, _, _: &DismissEvent, _| {
- pane.new_item_menu = None;
- })
- .detach();
- pane.new_item_menu = Some(menu);
- })),
+ }))
+ }),
)
- .when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
- el.child(Pane::render_menu_overlay(new_item_menu))
- })
.into_any_element()
.into();
@@ -8,13 +8,14 @@ use editor::actions::{
use editor::{Editor, EditorSettings};
use gpui::{
- anchored, deferred, Action, AnchorCorner, ClickEvent, DismissEvent, ElementId, EventEmitter,
- InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView,
+ Action, AnchorCorner, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement,
+ Render, Styled, Subscription, View, ViewContext, WeakView,
};
use search::{buffer_search, BufferSearchBar};
use settings::{Settings, SettingsStore};
use ui::{
- prelude::*, ButtonStyle, ContextMenu, IconButton, IconButtonShape, IconName, IconSize, Tooltip,
+ prelude::*, ButtonStyle, ContextMenu, IconButton, IconButtonShape, IconName, IconSize,
+ PopoverMenu, PopoverMenuHandle, Tooltip,
};
use workspace::{
item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
@@ -27,10 +28,9 @@ pub struct QuickActionBar {
_inlay_hints_enabled_subscription: Option<Subscription>,
active_item: Option<Box<dyn ItemHandle>>,
buffer_search_bar: View<BufferSearchBar>,
- repl_menu: Option<View<ContextMenu>>,
show: bool,
- toggle_selections_menu: Option<View<ContextMenu>>,
- toggle_settings_menu: Option<View<ContextMenu>>,
+ toggle_selections_handle: PopoverMenuHandle<ContextMenu>,
+ toggle_settings_handle: PopoverMenuHandle<ContextMenu>,
workspace: WeakView<Workspace>,
}
@@ -44,10 +44,9 @@ impl QuickActionBar {
_inlay_hints_enabled_subscription: None,
active_item: None,
buffer_search_bar,
- repl_menu: None,
show: true,
- toggle_selections_menu: None,
- toggle_settings_menu: None,
+ toggle_selections_handle: Default::default(),
+ toggle_settings_handle: Default::default(),
workspace: workspace.weak_handle(),
};
this.apply_settings(cx);
@@ -79,17 +78,6 @@ impl QuickActionBar {
ToolbarItemLocation::Hidden
}
}
-
- fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
- div().absolute().bottom_0().right_0().size_0().child(
- deferred(
- anchored()
- .anchor(AnchorCorner::TopRight)
- .child(menu.clone()),
- )
- .with_priority(1),
- )
- }
}
impl Render for QuickActionBar {
@@ -158,150 +146,155 @@ impl Render for QuickActionBar {
);
let editor_selections_dropdown = selection_menu_enabled.then(|| {
- IconButton::new("toggle_editor_selections_icon", IconName::TextCursor)
- .shape(IconButtonShape::Square)
- .icon_size(IconSize::Small)
- .style(ButtonStyle::Subtle)
- .selected(self.toggle_selections_menu.is_some())
- .on_click({
- let focus = editor.focus_handle(cx);
- cx.listener(move |quick_action_bar, _, cx| {
- let focus = focus.clone();
- let menu = ContextMenu::build(cx, move |menu, _| {
- menu.context(focus.clone())
- .action("Select All", Box::new(SelectAll))
- .action(
- "Select Next Occurrence",
- Box::new(SelectNext {
- replace_newest: false,
- }),
- )
- .action("Expand Selection", Box::new(SelectLargerSyntaxNode))
- .action("Shrink Selection", Box::new(SelectSmallerSyntaxNode))
- .action("Add Cursor Above", Box::new(AddSelectionAbove))
- .action("Add Cursor Below", Box::new(AddSelectionBelow))
- .separator()
- .action("Go to Symbol", Box::new(ToggleOutline))
- .action("Go to Line/Column", Box::new(ToggleGoToLine))
- .separator()
- .action("Next Problem", Box::new(GoToDiagnostic))
- .action("Previous Problem", Box::new(GoToPrevDiagnostic))
- .separator()
- .action("Next Hunk", Box::new(GoToHunk))
- .action("Previous Hunk", Box::new(GoToPrevHunk))
- .separator()
- .action("Move Line Up", Box::new(MoveLineUp))
- .action("Move Line Down", Box::new(MoveLineDown))
- .action("Duplicate Selection", Box::new(DuplicateLineDown))
- });
- cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| {
- quick_action_bar.toggle_selections_menu = None;
- })
- .detach();
- quick_action_bar.toggle_selections_menu = Some(menu);
- })
- })
- .when(self.toggle_selections_menu.is_none(), |this| {
- this.tooltip(|cx| Tooltip::text("Selection Controls", cx))
+ let focus = editor.focus_handle(cx);
+ PopoverMenu::new("editor-selections-dropdown")
+ .trigger(
+ IconButton::new("toggle_editor_selections_icon", IconName::TextCursor)
+ .shape(IconButtonShape::Square)
+ .icon_size(IconSize::Small)
+ .style(ButtonStyle::Subtle)
+ .selected(self.toggle_selections_handle.is_deployed())
+ .when(!self.toggle_selections_handle.is_deployed(), |this| {
+ this.tooltip(|cx| Tooltip::text("Selection Controls", cx))
+ }),
+ )
+ .with_handle(self.toggle_selections_handle.clone())
+ .anchor(AnchorCorner::TopRight)
+ .menu(move |cx| {
+ let focus = focus.clone();
+ let menu = ContextMenu::build(cx, move |menu, _| {
+ menu.context(focus.clone())
+ .action("Select All", Box::new(SelectAll))
+ .action(
+ "Select Next Occurrence",
+ Box::new(SelectNext {
+ replace_newest: false,
+ }),
+ )
+ .action("Expand Selection", Box::new(SelectLargerSyntaxNode))
+ .action("Shrink Selection", Box::new(SelectSmallerSyntaxNode))
+ .action("Add Cursor Above", Box::new(AddSelectionAbove))
+ .action("Add Cursor Below", Box::new(AddSelectionBelow))
+ .separator()
+ .action("Go to Symbol", Box::new(ToggleOutline))
+ .action("Go to Line/Column", Box::new(ToggleGoToLine))
+ .separator()
+ .action("Next Problem", Box::new(GoToDiagnostic))
+ .action("Previous Problem", Box::new(GoToPrevDiagnostic))
+ .separator()
+ .action("Next Hunk", Box::new(GoToHunk))
+ .action("Previous Hunk", Box::new(GoToPrevHunk))
+ .separator()
+ .action("Move Line Up", Box::new(MoveLineUp))
+ .action("Move Line Down", Box::new(MoveLineDown))
+ .action("Duplicate Selection", Box::new(DuplicateLineDown))
+ });
+ Some(menu)
})
});
- let editor_settings_dropdown =
- IconButton::new("toggle_editor_settings_icon", IconName::Sliders)
- .shape(IconButtonShape::Square)
- .icon_size(IconSize::Small)
- .style(ButtonStyle::Subtle)
- .selected(self.toggle_settings_menu.is_some())
- .on_click({
- let editor = editor.clone();
- cx.listener(move |quick_action_bar, _, cx| {
- let menu = ContextMenu::build(cx, |mut menu, _| {
- if supports_inlay_hints {
- menu = menu.toggleable_entry(
- "Inlay Hints",
- inlay_hints_enabled,
- IconPosition::Start,
- Some(editor::actions::ToggleInlayHints.boxed_clone()),
- {
- let editor = editor.clone();
- move |cx| {
- editor.update(cx, |editor, cx| {
- editor.toggle_inlay_hints(
- &editor::actions::ToggleInlayHints,
- cx,
- );
- });
- }
- },
- );
- }
-
- menu = menu.toggleable_entry(
- "Inline Git Blame",
- git_blame_inline_enabled,
- IconPosition::Start,
- Some(editor::actions::ToggleGitBlameInline.boxed_clone()),
- {
- let editor = editor.clone();
- move |cx| {
- editor.update(cx, |editor, cx| {
- editor.toggle_git_blame_inline(
- &editor::actions::ToggleGitBlameInline,
+ let editor = editor.downgrade();
+ let editor_settings_dropdown = PopoverMenu::new("editor-settings")
+ .trigger(
+ IconButton::new("toggle_editor_settings_icon", IconName::Sliders)
+ .shape(IconButtonShape::Square)
+ .icon_size(IconSize::Small)
+ .style(ButtonStyle::Subtle)
+ .selected(self.toggle_settings_handle.is_deployed())
+ .when(!self.toggle_settings_handle.is_deployed(), |this| {
+ this.tooltip(|cx| Tooltip::text("Editor Controls", cx))
+ }),
+ )
+ .anchor(AnchorCorner::TopRight)
+ .with_handle(self.toggle_settings_handle.clone())
+ .menu(move |cx| {
+ let menu = ContextMenu::build(cx, |mut menu, _| {
+ if supports_inlay_hints {
+ menu = menu.toggleable_entry(
+ "Inlay Hints",
+ inlay_hints_enabled,
+ IconPosition::Start,
+ Some(editor::actions::ToggleInlayHints.boxed_clone()),
+ {
+ let editor = editor.clone();
+ move |cx| {
+ editor
+ .update(cx, |editor, cx| {
+ editor.toggle_inlay_hints(
+ &editor::actions::ToggleInlayHints,
cx,
- )
- });
- }
- },
- );
+ );
+ })
+ .ok();
+ }
+ },
+ );
+ }
- menu = menu.toggleable_entry(
- "Selection Menu",
- selection_menu_enabled,
- IconPosition::Start,
- Some(editor::actions::ToggleSelectionMenu.boxed_clone()),
- {
- let editor = editor.clone();
- move |cx| {
- editor.update(cx, |editor, cx| {
- editor.toggle_selection_menu(
- &editor::actions::ToggleSelectionMenu,
- cx,
- )
- });
- }
- },
- );
+ menu = menu.toggleable_entry(
+ "Inline Git Blame",
+ git_blame_inline_enabled,
+ IconPosition::Start,
+ Some(editor::actions::ToggleGitBlameInline.boxed_clone()),
+ {
+ let editor = editor.clone();
+ move |cx| {
+ editor
+ .update(cx, |editor, cx| {
+ editor.toggle_git_blame_inline(
+ &editor::actions::ToggleGitBlameInline,
+ cx,
+ )
+ })
+ .ok();
+ }
+ },
+ );
- menu = menu.toggleable_entry(
- "Auto Signature Help",
- auto_signature_help_enabled,
- IconPosition::Start,
- Some(editor::actions::ToggleAutoSignatureHelp.boxed_clone()),
- {
- let editor = editor.clone();
- move |cx| {
- editor.update(cx, |editor, cx| {
- editor.toggle_auto_signature_help_menu(
- &editor::actions::ToggleAutoSignatureHelp,
- cx,
- );
- });
- }
- },
- );
+ menu = menu.toggleable_entry(
+ "Selection Menu",
+ selection_menu_enabled,
+ IconPosition::Start,
+ Some(editor::actions::ToggleSelectionMenu.boxed_clone()),
+ {
+ let editor = editor.clone();
+ move |cx| {
+ editor
+ .update(cx, |editor, cx| {
+ editor.toggle_selection_menu(
+ &editor::actions::ToggleSelectionMenu,
+ cx,
+ )
+ })
+ .ok();
+ }
+ },
+ );
- menu
- });
- cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| {
- quick_action_bar.toggle_settings_menu = None;
- })
- .detach();
- quick_action_bar.toggle_settings_menu = Some(menu);
- })
- })
- .when(self.toggle_settings_menu.is_none(), |this| {
- this.tooltip(|cx| Tooltip::text("Editor Controls", cx))
+ menu = menu.toggleable_entry(
+ "Auto Signature Help",
+ auto_signature_help_enabled,
+ IconPosition::Start,
+ Some(editor::actions::ToggleAutoSignatureHelp.boxed_clone()),
+ {
+ let editor = editor.clone();
+ move |cx| {
+ editor
+ .update(cx, |editor, cx| {
+ editor.toggle_auto_signature_help_menu(
+ &editor::actions::ToggleAutoSignatureHelp,
+ cx,
+ );
+ })
+ .ok();
+ }
+ },
+ );
+
+ menu
});
+ Some(menu)
+ });
h_flex()
.id("quick action bar")
@@ -316,21 +309,6 @@ impl Render for QuickActionBar {
)
.children(editor_selections_dropdown)
.child(editor_settings_dropdown)
- .when_some(self.repl_menu.as_ref(), |el, repl_menu| {
- el.child(Self::render_menu_overlay(repl_menu))
- })
- .when_some(
- self.toggle_settings_menu.as_ref(),
- |el, toggle_settings_menu| {
- el.child(Self::render_menu_overlay(toggle_settings_menu))
- },
- )
- .when_some(
- self.toggle_selections_menu.as_ref(),
- |el, toggle_selections_menu| {
- el.child(Self::render_menu_overlay(toggle_selections_menu))
- },
- )
}
}
@@ -5,7 +5,7 @@ use collections::{HashMap, HashSet};
use db::kvp::KEY_VALUE_STORE;
use futures::future::join_all;
use gpui::{
- actions, Action, AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EventEmitter,
+ actions, Action, AnchorCorner, AnyView, AppContext, AsyncWindowContext, Entity, EventEmitter,
ExternalPaths, FocusHandle, FocusableView, IntoElement, Model, ParentElement, Pixels, Render,
Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
};
@@ -20,7 +20,7 @@ use terminal::{
Terminal,
};
use ui::{
- h_flex, ButtonCommon, Clickable, ContextMenu, FluentBuilder, IconButton, IconSize, Selectable,
+ h_flex, ButtonCommon, Clickable, ContextMenu, IconButton, IconSize, PopoverMenu, Selectable,
Tooltip,
};
use util::{ResultExt, TryFutureExt};
@@ -173,47 +173,42 @@ impl TerminalPanel {
let additional_buttons = self.additional_tab_bar_buttons.clone();
self.pane.update(cx, |pane, cx| {
pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
- if !pane.has_focus(cx) {
+ if !pane.has_focus(cx) && !pane.context_menu_focused(cx) {
return (None, None);
}
+ let focus_handle = pane.focus_handle(cx);
let right_children = h_flex()
.gap_2()
.children(additional_buttons.clone())
.child(
- IconButton::new("plus", IconName::Plus)
- .icon_size(IconSize::Small)
- .on_click(cx.listener(|pane, _, cx| {
- let focus_handle = pane.focus_handle(cx);
+ PopoverMenu::new("terminal-tab-bar-popover-menu")
+ .trigger(
+ IconButton::new("plus", IconName::Plus)
+ .icon_size(IconSize::Small)
+ .tooltip(|cx| Tooltip::text("New...", cx)),
+ )
+ .anchor(AnchorCorner::TopRight)
+ .with_handle(pane.new_item_context_menu_handle.clone())
+ .menu(move |cx| {
+ let focus_handle = focus_handle.clone();
let menu = ContextMenu::build(cx, |menu, _| {
- menu.action(
- "New Terminal",
- workspace::NewTerminal.boxed_clone(),
- )
- .entry(
- "Spawn task",
- Some(tasks_ui::Spawn::modal().boxed_clone()),
- move |cx| {
- // We want the focus to go back to terminal panel once task modal is dismissed,
- // hence we focus that first. Otherwise, we'd end up without a focused element, as
- // context menu will be gone the moment we spawn the modal.
- cx.focus(&focus_handle);
- cx.dispatch_action(
- tasks_ui::Spawn::modal().boxed_clone(),
- );
- },
- )
+ menu.context(focus_handle.clone())
+ .action(
+ "New Terminal",
+ workspace::NewTerminal.boxed_clone(),
+ )
+ // We want the focus to go back to terminal panel once task modal is dismissed,
+ // hence we focus that first. Otherwise, we'd end up without a focused element, as
+ // context menu will be gone the moment we spawn the modal.
+ .action(
+ "Spawn task",
+ tasks_ui::Spawn::modal().boxed_clone(),
+ )
});
- cx.subscribe(&menu, |pane, _, _: &DismissEvent, _| {
- pane.new_item_menu = None;
- })
- .detach();
- pane.new_item_menu = Some(menu);
- }))
- .tooltip(|cx| Tooltip::text("New...", cx)),
+
+ Some(menu)
+ }),
)
- .when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
- el.child(Pane::render_menu_overlay(new_item_menu))
- })
.child({
let zoomed = pane.is_zoomed();
IconButton::new("toggle_zoom", IconName::Maximize)
@@ -56,6 +56,23 @@ impl<M: ManagedView> PopoverMenuHandle<M> {
}
}
}
+
+ pub fn is_deployed(&self) -> bool {
+ self.0
+ .borrow()
+ .as_ref()
+ .map_or(false, |state| state.menu.borrow().as_ref().is_some())
+ }
+
+ pub fn is_focused(&self, cx: &mut WindowContext) -> bool {
+ self.0.borrow().as_ref().map_or(false, |state| {
+ state
+ .menu
+ .borrow()
+ .as_ref()
+ .map_or(false, |view| view.focus_handle(cx).is_focused(cx))
+ })
+ }
}
pub struct PopoverMenu<M: ManagedView> {
@@ -340,9 +357,12 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
// want a click on the toggle to re-open it.
cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble && child_hitbox.is_hovered(cx) {
- menu_handle.borrow_mut().take();
+ if let Some(menu) = menu_handle.borrow().as_ref() {
+ menu.update(cx, |_, cx| {
+ cx.emit(DismissEvent);
+ });
+ }
cx.stop_propagation();
- cx.refresh();
}
})
}
@@ -17,9 +17,9 @@ use collections::{BTreeSet, HashMap, HashSet, VecDeque};
use futures::{stream::FuturesUnordered, StreamExt};
use gpui::{
actions, anchored, deferred, impl_actions, prelude::*, Action, AnchorCorner, AnyElement,
- AppContext, AsyncWindowContext, ClickEvent, ClipboardItem, DismissEvent, Div, DragMoveEvent,
- EntityId, EventEmitter, ExternalPaths, FocusHandle, FocusOutEvent, FocusableView, KeyContext,
- Model, MouseButton, MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render,
+ AppContext, AsyncWindowContext, ClickEvent, ClipboardItem, Div, DragMoveEvent, EntityId,
+ EventEmitter, ExternalPaths, FocusHandle, FocusOutEvent, FocusableView, KeyContext, Model,
+ MouseButton, MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render,
ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakFocusHandle, WeakView,
WindowContext,
};
@@ -43,7 +43,7 @@ use theme::ThemeSettings;
use ui::{
prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconButtonShape, IconName,
- IconSize, Indicator, Label, Tab, TabBar, TabPosition, Tooltip,
+ IconSize, Indicator, Label, PopoverMenu, PopoverMenuHandle, Tab, TabBar, TabPosition, Tooltip,
};
use ui::{v_flex, ContextMenu};
use util::{debug_panic, maybe, truncate_and_remove_front, ResultExt};
@@ -250,8 +250,6 @@ pub struct Pane {
last_focus_handle_by_item: HashMap<EntityId, WeakFocusHandle>,
nav_history: NavHistory,
toolbar: View<Toolbar>,
- pub new_item_menu: Option<View<ContextMenu>>,
- split_item_menu: Option<View<ContextMenu>>,
pub(crate) workspace: WeakView<Workspace>,
project: Model<Project>,
drag_split_direction: Option<SplitDirection>,
@@ -269,6 +267,8 @@ pub struct Pane {
display_nav_history_buttons: Option<bool>,
double_click_dispatch_action: Box<dyn Action>,
save_modals_spawned: HashSet<EntityId>,
+ pub new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
+ split_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
}
pub struct ActivationHistoryEntry {
@@ -369,8 +369,6 @@ impl Pane {
next_timestamp,
}))),
toolbar: cx.new_view(|_| Toolbar::new()),
- new_item_menu: None,
- split_item_menu: None,
tab_bar_scroll_handle: ScrollHandle::new(),
drag_split_direction: None,
workspace,
@@ -380,7 +378,7 @@ impl Pane {
can_split: true,
should_display_tab_bar: Rc::new(|cx| TabBarSettings::get_global(cx).show),
render_tab_bar_buttons: Rc::new(move |pane, cx| {
- if !pane.has_focus(cx) {
+ if !pane.has_focus(cx) && !pane.context_menu_focused(cx) {
return (None, None);
}
// Ideally we would return a vec of elements here to pass directly to the [TabBar]'s
@@ -389,10 +387,16 @@ impl Pane {
// Instead we need to replicate the spacing from the [TabBar]'s `end_slot` here.
.gap(Spacing::Small.rems(cx))
.child(
- IconButton::new("plus", IconName::Plus)
- .icon_size(IconSize::Small)
- .on_click(cx.listener(|pane, _, cx| {
- let menu = ContextMenu::build(cx, |menu, _| {
+ PopoverMenu::new("pane-tab-bar-popover-menu")
+ .trigger(
+ IconButton::new("plus", IconName::Plus)
+ .icon_size(IconSize::Small)
+ .tooltip(|cx| Tooltip::text("New...", cx)),
+ )
+ .anchor(AnchorCorner::TopRight)
+ .with_handle(pane.new_item_context_menu_handle.clone())
+ .menu(move |cx| {
+ Some(ContextMenu::build(cx, |menu, _| {
menu.action("New File", NewFile.boxed_clone())
.action(
"Open File",
@@ -412,37 +416,27 @@ impl Pane {
)
.separator()
.action("New Terminal", NewTerminal.boxed_clone())
- });
- cx.subscribe(&menu, |pane, _, _: &DismissEvent, cx| {
- pane.focus(cx);
- pane.new_item_menu = None;
- })
- .detach();
- pane.new_item_menu = Some(menu);
- }))
- .tooltip(|cx| Tooltip::text("New...", cx)),
+ }))
+ }),
)
- .when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
- el.child(Self::render_menu_overlay(new_item_menu))
- })
.child(
- IconButton::new("split", IconName::Split)
- .icon_size(IconSize::Small)
- .on_click(cx.listener(|pane, _, cx| {
- let menu = ContextMenu::build(cx, |menu, _| {
+ PopoverMenu::new("pane-tab-bar-split")
+ .trigger(
+ IconButton::new("split", IconName::Split)
+ .icon_size(IconSize::Small)
+ .tooltip(|cx| Tooltip::text("Split Pane", cx)),
+ )
+ .anchor(AnchorCorner::TopRight)
+ .with_handle(pane.split_item_context_menu_handle.clone())
+ .menu(move |cx| {
+ ContextMenu::build(cx, |menu, _| {
menu.action("Split Right", SplitRight.boxed_clone())
.action("Split Left", SplitLeft.boxed_clone())
.action("Split Up", SplitUp.boxed_clone())
.action("Split Down", SplitDown.boxed_clone())
- });
- cx.subscribe(&menu, |pane, _, _: &DismissEvent, cx| {
- pane.focus(cx);
- pane.split_item_menu = None;
})
- .detach();
- pane.split_item_menu = Some(menu);
- }))
- .tooltip(|cx| Tooltip::text("Split Pane", cx)),
+ .into()
+ }),
)
.child({
let zoomed = pane.is_zoomed();
@@ -461,9 +455,6 @@ impl Pane {
)
})
})
- .when_some(pane.split_item_menu.as_ref(), |el, split_item_menu| {
- el.child(Self::render_menu_overlay(split_item_menu))
- })
.into_any_element()
.into();
(None, right_children)
@@ -474,6 +465,8 @@ impl Pane {
_subscriptions: subscriptions,
double_click_dispatch_action,
save_modals_spawned: HashSet::default(),
+ split_item_context_menu_handle: Default::default(),
+ new_item_context_menu_handle: Default::default(),
}
}
@@ -557,11 +550,9 @@ impl Pane {
}
}
- fn context_menu_focused(&self, cx: &mut ViewContext<Self>) -> bool {
- self.new_item_menu
- .as_ref()
- .or(self.split_item_menu.as_ref())
- .map_or(false, |menu| menu.focus_handle(cx).is_focused(cx))
+ pub fn context_menu_focused(&self, cx: &mut ViewContext<Self>) -> bool {
+ self.new_item_context_menu_handle.is_focused(cx)
+ || self.split_item_context_menu_handle.is_focused(cx)
}
fn focus_out(&mut self, _event: FocusOutEvent, cx: &mut ViewContext<Self>) {