diff --git a/assets/icons/sliders.svg b/assets/icons/sliders.svg
new file mode 100644
index 0000000000..33e50b08d7
--- /dev/null
+++ b/assets/icons/sliders.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index 851c6b558b..7beabfba48 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -8889,6 +8889,10 @@ impl Editor {
cx.notify();
}
+ pub fn git_blame_inline_enabled(&self) -> bool {
+ self.git_blame_inline_enabled
+ }
+
fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext) {
if let Some(project) = self.project.as_ref() {
let Some(buffer) = self.buffer().read(cx).as_singleton() else {
diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs
index a6fe46ec66..1967ff10ea 100644
--- a/crates/quick_action_bar/src/quick_action_bar.rs
+++ b/crates/quick_action_bar/src/quick_action_bar.rs
@@ -3,18 +3,21 @@ use assistant::{AssistantPanel, InlineAssist};
use editor::{Editor, EditorSettings};
use gpui::{
- Action, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Styled,
- Subscription, View, ViewContext, WeakView,
+ anchored, deferred, Action, AnchorCorner, ClickEvent, DismissEvent, ElementId, EventEmitter,
+ InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView,
};
use search::{buffer_search, BufferSearchBar};
use settings::{Settings, SettingsStore};
-use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip};
+use ui::{
+ prelude::*, ButtonSize, ButtonStyle, ContextMenu, IconButton, IconName, IconSize, Tooltip,
+};
use workspace::{
item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
};
pub struct QuickActionBar {
buffer_search_bar: View,
+ toggle_settings_menu: Option>,
active_item: Option>,
_inlay_hints_enabled_subscription: Option,
workspace: WeakView,
@@ -29,6 +32,7 @@ impl QuickActionBar {
) -> Self {
let mut this = Self {
buffer_search_bar,
+ toggle_settings_menu: None,
active_item: None,
_inlay_hints_enabled_subscription: None,
workspace: workspace.weak_handle(),
@@ -63,6 +67,17 @@ impl QuickActionBar {
ToolbarItemLocation::Hidden
}
}
+
+ fn render_menu_overlay(menu: &View) -> 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 {
@@ -70,22 +85,6 @@ impl Render for QuickActionBar {
let Some(editor) = self.active_editor() else {
return div().id("empty quick action bar");
};
- let inlay_hints_button = Some(QuickActionBarButton::new(
- "toggle inlay hints",
- IconName::InlayHint,
- editor.read(cx).inlay_hints_enabled(),
- Box::new(editor::actions::ToggleInlayHints),
- "Toggle Inlay Hints",
- {
- let editor = editor.clone();
- move |_, cx| {
- editor.update(cx, |editor, cx| {
- editor.toggle_inlay_hints(&editor::actions::ToggleInlayHints, cx);
- });
- }
- },
- ))
- .filter(|_| editor.read(cx).supports_inlay_hints(cx));
let search_button = Some(QuickActionBarButton::new(
"toggle buffer search",
@@ -122,14 +121,85 @@ impl Render for QuickActionBar {
},
);
+ let editor_settings_dropdown =
+ IconButton::new("toggle_editor_settings_icon", IconName::Sliders)
+ .size(ButtonSize::Compact)
+ .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 inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
+ let supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
+ let git_blame_inline_enabled = editor.read(cx).git_blame_inline_enabled();
+
+ let menu = ContextMenu::build(cx, |mut menu, _| {
+ if supports_inlay_hints {
+ menu = menu.toggleable_entry(
+ "Show Inlay Hints",
+ inlay_hints_enabled,
+ 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(
+ "Show Git Blame",
+ git_blame_inline_enabled,
+ 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,
+ )
+ });
+ }
+ },
+ );
+
+ 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);
+ })
+ })
+ .tooltip(|cx| Tooltip::text("Editor Controls", cx));
+
h_flex()
.id("quick action bar")
- .gap_2()
- .children(inlay_hints_button)
- .children(search_button)
- .when(AssistantSettings::get_global(cx).button, |bar| {
- bar.child(assistant_button)
- })
+ .gap_3()
+ .child(
+ h_flex()
+ .gap_1p5()
+ .children(search_button)
+ .when(AssistantSettings::get_global(cx).button, |bar| {
+ bar.child(assistant_button)
+ }),
+ )
+ .child(editor_settings_dropdown)
+ .when_some(
+ self.toggle_settings_menu.as_ref(),
+ |el, toggle_settings_menu| {
+ el.child(Self::render_menu_overlay(toggle_settings_menu))
+ },
+ )
}
}
diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs
index 57b934f305..e4c42fe5eb 100644
--- a/crates/ui/src/components/context_menu.rs
+++ b/crates/ui/src/components/context_menu.rs
@@ -13,6 +13,7 @@ enum ContextMenuItem {
Separator,
Header(SharedString),
Entry {
+ toggled: Option,
label: SharedString,
icon: Option,
handler: Rc,
@@ -92,6 +93,24 @@ impl ContextMenu {
handler: impl Fn(&mut WindowContext) + 'static,
) -> Self {
self.items.push(ContextMenuItem::Entry {
+ toggled: None,
+ label: label.into(),
+ handler: Rc::new(handler),
+ icon: None,
+ action,
+ });
+ self
+ }
+
+ pub fn toggleable_entry(
+ mut self,
+ label: impl Into,
+ toggled: bool,
+ action: Option>,
+ handler: impl Fn(&mut WindowContext) + 'static,
+ ) -> Self {
+ self.items.push(ContextMenuItem::Entry {
+ toggled: Some(toggled),
label: label.into(),
handler: Rc::new(handler),
icon: None,
@@ -114,6 +133,7 @@ impl ContextMenu {
pub fn action(mut self, label: impl Into, action: Box) -> Self {
self.items.push(ContextMenuItem::Entry {
+ toggled: None,
label: label.into(),
action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
@@ -124,6 +144,7 @@ impl ContextMenu {
pub fn link(mut self, label: impl Into, action: Box) -> Self {
self.items.push(ContextMenuItem::Entry {
+ toggled: None,
label: label.into(),
action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
@@ -279,6 +300,7 @@ impl Render for ContextMenu {
.inset(true)
.into_any_element(),
ContextMenuItem::Entry {
+ toggled,
label,
handler,
icon,
@@ -300,13 +322,14 @@ impl Render for ContextMenu {
ListItem::new(ix)
.inset(true)
.selected(Some(ix) == self.selected_index)
- .on_click(move |_, cx| {
- handler(cx);
- menu.update(cx, |menu, cx| {
- menu.clicked = true;
- cx.emit(DismissEvent);
+ .when_some(*toggled, |list_item, toggled| {
+ list_item.start_slot(if toggled {
+ v_flex().flex_none().child(
+ Icon::new(IconName::Check).color(Color::Accent),
+ )
+ } else {
+ v_flex().flex_none().size(IconSize::default().rems())
})
- .ok();
})
.child(
h_flex()
@@ -328,6 +351,14 @@ impl Render for ContextMenu {
.map(|binding| div().ml_1().child(binding))
})),
)
+ .on_click(move |_, cx| {
+ handler(cx);
+ menu.update(cx, |menu, cx| {
+ menu.clicked = true;
+ cx.emit(DismissEvent);
+ })
+ .ok();
+ })
.into_any_element()
}
ContextMenuItem::CustomEntry {
diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs
index 5992f88438..cd7c53d67c 100644
--- a/crates/ui/src/components/icon.rs
+++ b/crates/ui/src/components/icon.rs
@@ -105,6 +105,7 @@ pub enum IconName {
Return,
ReplyArrowRight,
Settings,
+ Sliders,
Screen,
SelectAll,
Server,
@@ -204,6 +205,7 @@ impl IconName {
IconName::Return => "icons/return.svg",
IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
IconName::Settings => "icons/file_icons/settings.svg",
+ IconName::Sliders => "icons/sliders.svg",
IconName::Screen => "icons/desktop.svg",
IconName::SelectAll => "icons/select_all.svg",
IconName::Server => "icons/server.svg",