From a6a14fd57b327807df2ece3b3d1741a54a337d8f Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 25 Sep 2025 16:36:57 +0800 Subject: [PATCH] input: Disable context menu item for disabled input. (#1280) --- crates/story/src/menu_story.rs | 18 +++++---- crates/story/src/popover_story.rs | 19 ++++----- crates/ui/src/input/popovers/context_menu.rs | 27 +++++++------ crates/ui/src/kbd.rs | 24 ++++++------ crates/ui/src/menu/popup_menu.rs | 41 ++++++++------------ themes/ayu.json | 8 ++-- 6 files changed, 70 insertions(+), 67 deletions(-) diff --git a/crates/story/src/menu_story.rs b/crates/story/src/menu_story.rs index 44ce87b8..b46dbc33 100644 --- a/crates/story/src/menu_story.rs +++ b/crates/story/src/menu_story.rs @@ -17,24 +17,25 @@ struct Info(usize); actions!(menu_story, [Copy, Paste, Cut, SearchAll, ToggleCheck]); +const CONTEXT: &str = "menu_story"; pub fn init(cx: &mut App) { cx.bind_keys([ #[cfg(target_os = "macos")] - KeyBinding::new("cmd-c", Copy, None), + KeyBinding::new("cmd-c", Copy, Some(CONTEXT)), #[cfg(not(target_os = "macos"))] - KeyBinding::new("ctrl-c", Copy, None), + KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)), #[cfg(target_os = "macos")] - KeyBinding::new("cmd-v", Paste, None), + KeyBinding::new("cmd-v", Paste, Some(CONTEXT)), #[cfg(not(target_os = "macos"))] - KeyBinding::new("ctrl-v", Paste, None), + KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)), #[cfg(target_os = "macos")] - KeyBinding::new("cmd-x", Cut, None), + KeyBinding::new("cmd-x", Cut, Some(CONTEXT)), #[cfg(not(target_os = "macos"))] - KeyBinding::new("ctrl-x", Cut, None), + KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)), #[cfg(target_os = "macos")] - KeyBinding::new("cmd-shift-f", SearchAll, None), + KeyBinding::new("cmd-shift-f", SearchAll, Some(CONTEXT)), #[cfg(not(target_os = "macos"))] - KeyBinding::new("ctrl-shift-f", SearchAll, None), + KeyBinding::new("ctrl-shift-f", SearchAll, Some(CONTEXT)), ]) } @@ -116,6 +117,7 @@ impl Render for MenuStory { let checked = self.checked; v_flex() + .key_context(CONTEXT) .track_focus(&self.focus_handle) .on_action(cx.listener(Self::on_copy)) .on_action(cx.listener(Self::on_cut)) diff --git a/crates/story/src/popover_story.rs b/crates/story/src/popover_story.rs index 6cb8e796..87b8374f 100644 --- a/crates/story/src/popover_story.rs +++ b/crates/story/src/popover_story.rs @@ -18,25 +18,25 @@ use serde::Deserialize; struct Info(usize); actions!(popover_story, [Copy, Paste, Cut, SearchAll, ToggleCheck]); - +const CONTEXT: &str = "popover-story"; pub fn init(cx: &mut App) { cx.bind_keys([ #[cfg(target_os = "macos")] - KeyBinding::new("cmd-c", Copy, None), + KeyBinding::new("cmd-c", Copy, Some(CONTEXT)), #[cfg(not(target_os = "macos"))] - KeyBinding::new("ctrl-c", Copy, None), + KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)), #[cfg(target_os = "macos")] - KeyBinding::new("cmd-v", Paste, None), + KeyBinding::new("cmd-v", Paste, Some(CONTEXT)), #[cfg(not(target_os = "macos"))] - KeyBinding::new("ctrl-v", Paste, None), + KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)), #[cfg(target_os = "macos")] - KeyBinding::new("cmd-x", Cut, None), + KeyBinding::new("cmd-x", Cut, Some(CONTEXT)), #[cfg(not(target_os = "macos"))] - KeyBinding::new("ctrl-x", Cut, None), + KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)), #[cfg(target_os = "macos")] - KeyBinding::new("cmd-shift-f", SearchAll, None), + KeyBinding::new("cmd-shift-f", SearchAll, Some(CONTEXT)), #[cfg(not(target_os = "macos"))] - KeyBinding::new("ctrl-shift-f", SearchAll, None), + KeyBinding::new("ctrl-shift-f", SearchAll, Some(CONTEXT)), ]) } @@ -159,6 +159,7 @@ impl Render for PopoverStory { let form = self.form.clone(); v_flex() + .key_context(CONTEXT) .track_focus(&self.focus_handle) .on_action(cx.listener(Self::on_copy)) .on_action(cx.listener(Self::on_cut)) diff --git a/crates/ui/src/input/popovers/context_menu.rs b/crates/ui/src/input/popovers/context_menu.rs index 660972c6..289e6be0 100644 --- a/crates/ui/src/input/popovers/context_menu.rs +++ b/crates/ui/src/input/popovers/context_menu.rs @@ -1,7 +1,7 @@ use gpui::{ anchored, deferred, div, prelude::FluentBuilder as _, px, App, AppContext as _, Context, - DismissEvent, Entity, IntoElement, MouseDownEvent, ParentElement as _, Pixels, Point, Render, - Styled, Subscription, Window, + Corner, DismissEvent, Entity, IntoElement, MouseDownEvent, ParentElement as _, Pixels, Point, + Render, Styled, Subscription, Window, }; use rust_i18n::t; @@ -40,11 +40,13 @@ impl InputState { self.handle_hover_definition(offset, window, cx); } - let has_goto_definition = self.lsp.definition_provider.is_some(); - let has_code_action = !self.lsp.code_action_providers.is_empty(); - let is_selected = !self.selected_range.is_empty(); - let has_paste = cx.read_from_clipboard().is_some(); + let is_enable = !self.disabled; + let has_goto_definition = is_enable && self.lsp.definition_provider.is_some(); + let has_code_action = is_enable && !self.lsp.code_action_providers.is_empty(); + let is_selected = is_enable && !self.selected_range.is_empty(); + let has_paste = is_enable && cx.read_from_clipboard().is_some(); + let action_context = self.focus_handle.clone(); self.mouse_context_menu.update(cx, |this, cx| { this.mouse_position = event.position; this.menu.update(cx, |menu, cx| { @@ -62,13 +64,18 @@ impl InputState { ) .separator() }) - .menu_with_enable(t!("Input.Cut"), Box::new(input::Cut), is_selected) + .menu_with_enable( + t!("Input.Cut"), + Box::new(input::Cut), + is_enable && is_selected, + ) .menu_with_enable(t!("Input.Copy"), Box::new(input::Copy), is_selected) .menu_with_enable(t!("Input.Paste"), Box::new(input::Paste), has_paste) .separator() .menu(t!("Input.Select All"), Box::new(input::SelectAll)); menu.menu_items = new_menu.menu_items; + menu.action_context = Some(action_context); cx.notify(); }); this.open = true; @@ -123,13 +130,11 @@ impl Render for MouseContextMenu { return div().into_any_element(); } - let pos = self.mouse_position; - deferred( anchored() .snap_to_window_with_margin(px(8.)) - .anchor(gpui::Corner::TopLeft) - .position(pos) + .anchor(Corner::TopLeft) + .position(self.mouse_position) .child( div() .font_family(".SystemUIFont") diff --git a/crates/ui/src/kbd.rs b/crates/ui/src/kbd.rs index 3f49550f..64d48da2 100644 --- a/crates/ui/src/kbd.rs +++ b/crates/ui/src/kbd.rs @@ -45,18 +45,18 @@ impl Kbd { window: &Window, ) -> Option { let key_context = context.and_then(|context| KeyContext::parse(context).ok()); - let bindings = match key_context { - Some(context) => window.bindings_for_action_in_context(action, context), - None => window.bindings_for_action(action), - }; - - bindings.first().and_then(|binding| { - if let Some(key) = binding.keystrokes().first() { - Some(Self::new(key.as_keystroke().clone())) - } else { - None + let binding = match key_context { + Some(context) => { + window.highest_precedence_binding_for_action_in_context(action, context) } - }) + None => window.highest_precedence_binding_for_action(action), + }?; + + if let Some(key) = binding.keystrokes().first() { + Some(Self::new(key.as_keystroke().clone())) + } else { + None + } } /// Return the Platform specific keybinding string by KeyStroke @@ -208,6 +208,8 @@ impl RenderOnce for Kbd { .rounded_sm() .line_height(relative(1.)) .text_xs() + .whitespace_normal() + .flex_shrink_0() .refine_style(&self.style) .child(Self::format(&self.stroke)) .into_any_element() diff --git a/crates/ui/src/menu/popup_menu.rs b/crates/ui/src/menu/popup_menu.rs index 7e3410f0..0d10975a 100644 --- a/crates/ui/src/menu/popup_menu.rs +++ b/crates/ui/src/menu/popup_menu.rs @@ -13,8 +13,7 @@ use gpui::{ InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window, }; -use gpui::{AsKeystroke, Half, MouseDownEvent, Subscription}; -use std::ops::Deref; +use gpui::{Half, MouseDownEvent, Subscription}; use std::rc::Rc; const CONTEXT: &str = "PopupMenu"; @@ -109,10 +108,10 @@ impl PopupMenuItem { } pub struct PopupMenu { - /// The parent menu of this menu, if this is a submenu - parent_menu: Option>, focus_handle: FocusHandle, pub(crate) menu_items: Vec, + /// The focus handle of Entity to handle actions. + pub(crate) action_context: Option, has_icon: bool, selected_index: Option, min_width: Option, @@ -121,12 +120,13 @@ pub struct PopupMenu { bounds: Bounds, size: Size, + /// The parent menu of this menu, if this is a submenu + parent_menu: Option>, scrollable: bool, external_link_icon: bool, scroll_handle: ScrollHandle, scroll_state: ScrollbarState, - previous_focus_handle: Option, _subscriptions: Vec, } @@ -134,7 +134,7 @@ impl PopupMenu { pub(crate) fn new(cx: &mut App) -> Self { Self { focus_handle: cx.focus_handle(), - previous_focus_handle: None, + action_context: None, parent_menu: None, menu_items: Vec::new(), selected_index: None, @@ -159,7 +159,7 @@ impl PopupMenu { ) -> Entity { cx.new(|cx| { let mut menu = Self::new(cx); - menu.previous_focus_handle = window.focused(cx); + menu.action_context = window.focused(cx); f(menu, window, cx) }) } @@ -725,8 +725,8 @@ impl PopupMenu { cx.emit(DismissEvent); // Focus back to the previous focused handle. - if let Some(previous_focus_handle) = self.previous_focus_handle.as_ref() { - window.focus(previous_focus_handle); + if let Some(action_context) = self.action_context.as_ref() { + window.focus(action_context); } let Some(parent_menu) = self.parent_menu.clone() else { @@ -743,22 +743,15 @@ impl PopupMenu { fn render_key_binding( action: Option>, window: &mut Window, - cx: &mut Context, + _: &mut Context, ) -> Option { - if let Some(action) = action { - if let Some(key_binding) = window.bindings_for_action(action.deref()).first() { - let el = div().text_color(cx.theme().muted_foreground).children( - key_binding - .keystrokes() - .into_iter() - .map(|key| Kbd::format(key.as_keystroke())), - ); - - return Some(el); - } - } - - return None; + let action = action?; + Kbd::binding_for_action(action.as_ref(), None, window).map(|this| { + this.p_0() + .flex_nowrap() + .border_0() + .bg(gpui::transparent_white()) + }) } fn render_icon( diff --git a/themes/ayu.json b/themes/ayu.json index dca09aba..6ac3a473 100644 --- a/themes/ayu.json +++ b/themes/ayu.json @@ -253,7 +253,7 @@ "list.active.border": "#36A3D9", "list.even.background": "#191F2A99", "muted.background": "#1F2430", - "muted.foreground": "#8a8986ff", + "muted.foreground": "#6a6967", "panel.background": "#1F2126", "popover.background": "#1F2127", "popover.foreground": "#B3B1AD", @@ -263,10 +263,10 @@ "primary.hover.background": "#3DAEE9", "scrollbar.background": "#0A0E1400", "scrollbar.thumb.background": "#bfbdb64c", - "secondary.active.background": "#191d24", - "secondary.background": "#1f2127", + "secondary.active.background": "#2D2F34", + "secondary.background": "#2D2F34", "secondary.foreground": "#B3B1AD", - "secondary.hover.background": "#191d2499", + "secondary.hover.background": "#2D2F3499", "tab.active.foreground": "#B3B1AD", "tab.background": "#1F2127", "tab.foreground": "#82807d",