input: Disable context menu item for disabled input. (#1280)

This commit is contained in:
Jason Lee 2025-09-25 16:36:57 +08:00 committed by GitHub
parent 16105da122
commit a6a14fd57b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 70 additions and 67 deletions

View file

@ -17,24 +17,25 @@ struct Info(usize);
actions!(menu_story, [Copy, Paste, Cut, SearchAll, ToggleCheck]); actions!(menu_story, [Copy, Paste, Cut, SearchAll, ToggleCheck]);
const CONTEXT: &str = "menu_story";
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
cx.bind_keys([ cx.bind_keys([
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
KeyBinding::new("cmd-c", Copy, None), KeyBinding::new("cmd-c", Copy, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-c", Copy, None), KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
KeyBinding::new("cmd-v", Paste, None), KeyBinding::new("cmd-v", Paste, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-v", Paste, None), KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
KeyBinding::new("cmd-x", Cut, None), KeyBinding::new("cmd-x", Cut, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-x", Cut, None), KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
KeyBinding::new("cmd-shift-f", SearchAll, None), KeyBinding::new("cmd-shift-f", SearchAll, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))] #[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; let checked = self.checked;
v_flex() v_flex()
.key_context(CONTEXT)
.track_focus(&self.focus_handle) .track_focus(&self.focus_handle)
.on_action(cx.listener(Self::on_copy)) .on_action(cx.listener(Self::on_copy))
.on_action(cx.listener(Self::on_cut)) .on_action(cx.listener(Self::on_cut))

View file

@ -18,25 +18,25 @@ use serde::Deserialize;
struct Info(usize); struct Info(usize);
actions!(popover_story, [Copy, Paste, Cut, SearchAll, ToggleCheck]); actions!(popover_story, [Copy, Paste, Cut, SearchAll, ToggleCheck]);
const CONTEXT: &str = "popover-story";
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
cx.bind_keys([ cx.bind_keys([
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
KeyBinding::new("cmd-c", Copy, None), KeyBinding::new("cmd-c", Copy, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-c", Copy, None), KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
KeyBinding::new("cmd-v", Paste, None), KeyBinding::new("cmd-v", Paste, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-v", Paste, None), KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
KeyBinding::new("cmd-x", Cut, None), KeyBinding::new("cmd-x", Cut, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-x", Cut, None), KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
KeyBinding::new("cmd-shift-f", SearchAll, None), KeyBinding::new("cmd-shift-f", SearchAll, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))] #[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(); let form = self.form.clone();
v_flex() v_flex()
.key_context(CONTEXT)
.track_focus(&self.focus_handle) .track_focus(&self.focus_handle)
.on_action(cx.listener(Self::on_copy)) .on_action(cx.listener(Self::on_copy))
.on_action(cx.listener(Self::on_cut)) .on_action(cx.listener(Self::on_cut))

View file

@ -1,7 +1,7 @@
use gpui::{ use gpui::{
anchored, deferred, div, prelude::FluentBuilder as _, px, App, AppContext as _, Context, anchored, deferred, div, prelude::FluentBuilder as _, px, App, AppContext as _, Context,
DismissEvent, Entity, IntoElement, MouseDownEvent, ParentElement as _, Pixels, Point, Render, Corner, DismissEvent, Entity, IntoElement, MouseDownEvent, ParentElement as _, Pixels, Point,
Styled, Subscription, Window, Render, Styled, Subscription, Window,
}; };
use rust_i18n::t; use rust_i18n::t;
@ -40,11 +40,13 @@ impl InputState {
self.handle_hover_definition(offset, window, cx); self.handle_hover_definition(offset, window, cx);
} }
let has_goto_definition = self.lsp.definition_provider.is_some(); let is_enable = !self.disabled;
let has_code_action = !self.lsp.code_action_providers.is_empty(); let has_goto_definition = is_enable && self.lsp.definition_provider.is_some();
let is_selected = !self.selected_range.is_empty(); let has_code_action = is_enable && !self.lsp.code_action_providers.is_empty();
let has_paste = cx.read_from_clipboard().is_some(); 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| { self.mouse_context_menu.update(cx, |this, cx| {
this.mouse_position = event.position; this.mouse_position = event.position;
this.menu.update(cx, |menu, cx| { this.menu.update(cx, |menu, cx| {
@ -62,13 +64,18 @@ impl InputState {
) )
.separator() .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.Copy"), Box::new(input::Copy), is_selected)
.menu_with_enable(t!("Input.Paste"), Box::new(input::Paste), has_paste) .menu_with_enable(t!("Input.Paste"), Box::new(input::Paste), has_paste)
.separator() .separator()
.menu(t!("Input.Select All"), Box::new(input::SelectAll)); .menu(t!("Input.Select All"), Box::new(input::SelectAll));
menu.menu_items = new_menu.menu_items; menu.menu_items = new_menu.menu_items;
menu.action_context = Some(action_context);
cx.notify(); cx.notify();
}); });
this.open = true; this.open = true;
@ -123,13 +130,11 @@ impl Render for MouseContextMenu {
return div().into_any_element(); return div().into_any_element();
} }
let pos = self.mouse_position;
deferred( deferred(
anchored() anchored()
.snap_to_window_with_margin(px(8.)) .snap_to_window_with_margin(px(8.))
.anchor(gpui::Corner::TopLeft) .anchor(Corner::TopLeft)
.position(pos) .position(self.mouse_position)
.child( .child(
div() div()
.font_family(".SystemUIFont") .font_family(".SystemUIFont")

View file

@ -45,18 +45,18 @@ impl Kbd {
window: &Window, window: &Window,
) -> Option<Self> { ) -> Option<Self> {
let key_context = context.and_then(|context| KeyContext::parse(context).ok()); let key_context = context.and_then(|context| KeyContext::parse(context).ok());
let bindings = match key_context { let binding = match key_context {
Some(context) => window.bindings_for_action_in_context(action, context), Some(context) => {
None => window.bindings_for_action(action), window.highest_precedence_binding_for_action_in_context(action, context)
};
bindings.first().and_then(|binding| {
if let Some(key) = binding.keystrokes().first() {
Some(Self::new(key.as_keystroke().clone()))
} else {
None
} }
}) 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 /// Return the Platform specific keybinding string by KeyStroke
@ -208,6 +208,8 @@ impl RenderOnce for Kbd {
.rounded_sm() .rounded_sm()
.line_height(relative(1.)) .line_height(relative(1.))
.text_xs() .text_xs()
.whitespace_normal()
.flex_shrink_0()
.refine_style(&self.style) .refine_style(&self.style)
.child(Self::format(&self.stroke)) .child(Self::format(&self.stroke))
.into_any_element() .into_any_element()

View file

@ -13,8 +13,7 @@ use gpui::{
InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, Render, ScrollHandle, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, Render, ScrollHandle,
SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window,
}; };
use gpui::{AsKeystroke, Half, MouseDownEvent, Subscription}; use gpui::{Half, MouseDownEvent, Subscription};
use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
const CONTEXT: &str = "PopupMenu"; const CONTEXT: &str = "PopupMenu";
@ -109,10 +108,10 @@ impl PopupMenuItem {
} }
pub struct PopupMenu { pub struct PopupMenu {
/// The parent menu of this menu, if this is a submenu
parent_menu: Option<WeakEntity<Self>>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
pub(crate) menu_items: Vec<PopupMenuItem>, pub(crate) menu_items: Vec<PopupMenuItem>,
/// The focus handle of Entity to handle actions.
pub(crate) action_context: Option<FocusHandle>,
has_icon: bool, has_icon: bool,
selected_index: Option<usize>, selected_index: Option<usize>,
min_width: Option<Pixels>, min_width: Option<Pixels>,
@ -121,12 +120,13 @@ pub struct PopupMenu {
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
size: Size, size: Size,
/// The parent menu of this menu, if this is a submenu
parent_menu: Option<WeakEntity<Self>>,
scrollable: bool, scrollable: bool,
external_link_icon: bool, external_link_icon: bool,
scroll_handle: ScrollHandle, scroll_handle: ScrollHandle,
scroll_state: ScrollbarState, scroll_state: ScrollbarState,
previous_focus_handle: Option<FocusHandle>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@ -134,7 +134,7 @@ impl PopupMenu {
pub(crate) fn new(cx: &mut App) -> Self { pub(crate) fn new(cx: &mut App) -> Self {
Self { Self {
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
previous_focus_handle: None, action_context: None,
parent_menu: None, parent_menu: None,
menu_items: Vec::new(), menu_items: Vec::new(),
selected_index: None, selected_index: None,
@ -159,7 +159,7 @@ impl PopupMenu {
) -> Entity<Self> { ) -> Entity<Self> {
cx.new(|cx| { cx.new(|cx| {
let mut menu = Self::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) f(menu, window, cx)
}) })
} }
@ -725,8 +725,8 @@ impl PopupMenu {
cx.emit(DismissEvent); cx.emit(DismissEvent);
// Focus back to the previous focused handle. // Focus back to the previous focused handle.
if let Some(previous_focus_handle) = self.previous_focus_handle.as_ref() { if let Some(action_context) = self.action_context.as_ref() {
window.focus(previous_focus_handle); window.focus(action_context);
} }
let Some(parent_menu) = self.parent_menu.clone() else { let Some(parent_menu) = self.parent_menu.clone() else {
@ -743,22 +743,15 @@ impl PopupMenu {
fn render_key_binding( fn render_key_binding(
action: Option<Box<dyn Action>>, action: Option<Box<dyn Action>>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, _: &mut Context<Self>,
) -> Option<impl IntoElement> { ) -> Option<impl IntoElement> {
if let Some(action) = action { let action = action?;
if let Some(key_binding) = window.bindings_for_action(action.deref()).first() { Kbd::binding_for_action(action.as_ref(), None, window).map(|this| {
let el = div().text_color(cx.theme().muted_foreground).children( this.p_0()
key_binding .flex_nowrap()
.keystrokes() .border_0()
.into_iter() .bg(gpui::transparent_white())
.map(|key| Kbd::format(key.as_keystroke())), })
);
return Some(el);
}
}
return None;
} }
fn render_icon( fn render_icon(

View file

@ -253,7 +253,7 @@
"list.active.border": "#36A3D9", "list.active.border": "#36A3D9",
"list.even.background": "#191F2A99", "list.even.background": "#191F2A99",
"muted.background": "#1F2430", "muted.background": "#1F2430",
"muted.foreground": "#8a8986ff", "muted.foreground": "#6a6967",
"panel.background": "#1F2126", "panel.background": "#1F2126",
"popover.background": "#1F2127", "popover.background": "#1F2127",
"popover.foreground": "#B3B1AD", "popover.foreground": "#B3B1AD",
@ -263,10 +263,10 @@
"primary.hover.background": "#3DAEE9", "primary.hover.background": "#3DAEE9",
"scrollbar.background": "#0A0E1400", "scrollbar.background": "#0A0E1400",
"scrollbar.thumb.background": "#bfbdb64c", "scrollbar.thumb.background": "#bfbdb64c",
"secondary.active.background": "#191d24", "secondary.active.background": "#2D2F34",
"secondary.background": "#1f2127", "secondary.background": "#2D2F34",
"secondary.foreground": "#B3B1AD", "secondary.foreground": "#B3B1AD",
"secondary.hover.background": "#191d2499", "secondary.hover.background": "#2D2F3499",
"tab.active.foreground": "#B3B1AD", "tab.active.foreground": "#B3B1AD",
"tab.background": "#1F2127", "tab.background": "#1F2127",
"tab.foreground": "#82807d", "tab.foreground": "#82807d",