From bf5650b5dbd8561774b9603b01a3a56facb59735 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Tue, 23 Sep 2025 22:06:58 +0800 Subject: [PATCH] input: Add context menu for cut, copy, paste. (#1279) Close #27 image --- crates/ui/locales/ui.yml | 26 +++- crates/ui/src/input/cursor.rs | 5 + crates/ui/src/input/lsp/code_actions.rs | 11 +- crates/ui/src/input/lsp/definitions.rs | 91 +++++++++--- crates/ui/src/input/lsp/mod.rs | 3 +- crates/ui/src/input/mod.rs | 2 +- crates/ui/src/input/popovers/context_menu.rs | 143 +++++++++++++++++++ crates/ui/src/input/popovers/mod.rs | 5 + crates/ui/src/input/state.rs | 25 ++-- crates/ui/src/input/text_input.rs | 15 +- crates/ui/src/menu/context_menu.rs | 4 +- crates/ui/src/menu/popup_menu.rs | 83 +++++++---- 12 files changed, 342 insertions(+), 71 deletions(-) create mode 100644 crates/ui/src/input/popovers/context_menu.rs diff --git a/crates/ui/locales/ui.yml b/crates/ui/locales/ui.yml index 4c03904c..74754a4e 100644 --- a/crates/ui/locales/ui.yml +++ b/crates/ui/locales/ui.yml @@ -160,9 +160,31 @@ Input: en: Replace zh-CN: 替换 zh-HK: 替換 - it: Sostituisci Replace All: en: Replace All zh-CN: 全部替换 zh-HK: 全部替換 - it: Sostituisci tutto + Cut: + en: Cut + zh-CN: 剪切 + zh-HK: 剪切 + Copy: + en: Copy + zh-CN: 复制 + zh-HK: 複製 + Paste: + en: Paste + zh-CN: 粘贴 + zh-HK: 貼上 + Select All: + en: Select All + zh-CN: 全选 + zh-HK: 全選 + Go to Definition: + en: Go to Definition + zh-CN: 跳转到定义 + zh-HK: 跳轉到定義 + Show Code Actions: + en: Show Code Actions + zh-CN: 显示代码操作 + zh-HK: 顯示代碼操作 diff --git a/crates/ui/src/input/cursor.rs b/crates/ui/src/input/cursor.rs index baf07c4e..c8d7fb84 100644 --- a/crates/ui/src/input/cursor.rs +++ b/crates/ui/src/input/cursor.rs @@ -25,6 +25,11 @@ impl Selection { self.start = 0; self.end = 0; } + + /// Checks if the given offset is within the selection range. + pub fn contains(&self, offset: usize) -> bool { + offset >= self.start && offset < self.end + } } impl From> for Selection { diff --git a/crates/ui/src/input/lsp/code_actions.rs b/crates/ui/src/input/lsp/code_actions.rs index 993f7023..a5b7de6d 100644 --- a/crates/ui/src/input/lsp/code_actions.rs +++ b/crates/ui/src/input/lsp/code_actions.rs @@ -5,7 +5,7 @@ use std::ops::Range; use crate::input::{ popovers::{CodeActionItem, CodeActionMenu, ContextMenu}, - InputState, + InputState, ToggleCodeActions, }; pub trait CodeActionProvider { @@ -37,6 +37,15 @@ pub trait CodeActionProvider { } impl InputState { + pub(crate) fn on_action_toggle_code_actions( + &mut self, + _: &ToggleCodeActions, + window: &mut Window, + cx: &mut Context, + ) { + self.handle_code_action_trigger(window, cx) + } + /// Show code actions for the cursor. pub(crate) fn handle_code_action_trigger( &mut self, diff --git a/crates/ui/src/input/lsp/definitions.rs b/crates/ui/src/input/lsp/definitions.rs index faf514d4..4a21ed91 100644 --- a/crates/ui/src/input/lsp/definitions.rs +++ b/crates/ui/src/input/lsp/definitions.rs @@ -6,7 +6,7 @@ use rope::Rope; use std::{ops::Range, rc::Rc}; use crate::{ - input::{element::TextElement, InputState, RopeExt}, + input::{element::TextElement, GoToDefinition, InputState, RopeExt}, ActiveTheme, }; @@ -31,14 +31,31 @@ pub(crate) struct HoverDefinition { /// The range of the symbol that triggered the hover. symbol_range: Range, pub(crate) locations: Rc>, + last_location: Option<(Range, Rc>)>, } impl HoverDefinition { - pub(crate) fn new(symbol_range: Range, locations: Vec) -> Self { - Self { - symbol_range, - locations: Rc::new(locations), + pub(crate) fn update( + &mut self, + symbol_range: Range, + locations: Vec, + ) { + self.clear(); + self.symbol_range = symbol_range; + self.locations = Rc::new(locations); + } + + pub(crate) fn is_empty(&self) -> bool { + self.locations.is_empty() + } + + pub(crate) fn clear(&mut self) { + if !self.locations.is_empty() { + self.last_location = Some((self.symbol_range.clone(), self.locations.clone())); } + + self.symbol_range = 0..0; + self.locations = Rc::new(vec![]); } pub(crate) fn is_same(&self, offset: usize) -> bool { @@ -47,19 +64,18 @@ impl HoverDefinition { } impl InputState { - pub(super) fn handle_hover_definition( + pub(crate) fn handle_hover_definition( &mut self, offset: usize, window: &mut Window, - cx: &mut Context, + cx: &mut Context, ) { let Some(provider) = self.lsp.definition_provider.clone() else { return; }; - if let Some(hover_definition) = self.hover_definition.as_ref() { - if hover_definition.is_same(offset) { - return; - } + + if self.hover_definition.is_same(offset) { + return; } // Currently not implemented. @@ -71,7 +87,7 @@ impl InputState { _ = editor.update(cx, |editor, cx| { if locations.is_empty() { - editor.hover_definition = None; + editor.hover_definition.clear(); } else { if let Some(location) = locations.first() { if let Some(range) = location.origin_selection_range { @@ -81,7 +97,9 @@ impl InputState { } } - editor.hover_definition = Some(HoverDefinition::new(symbol_range, locations)); + editor + .hover_definition + .update(symbol_range.clone(), locations.clone()); } cx.notify(); }); @@ -90,6 +108,24 @@ impl InputState { }); } + pub(crate) fn on_action_go_to_definition( + &mut self, + _: &GoToDefinition, + _: &mut Window, + cx: &mut Context, + ) { + let offset = self.cursor(); + if let Some((symbol_range, locations)) = self.hover_definition.last_location.clone() { + if !(symbol_range.start..=symbol_range.end).contains(&offset) { + return; + } + + if let Some(location) = locations.first().cloned() { + self.go_to_definition(&location, cx); + } + } + } + /// Return true if handled. pub(crate) fn handle_click_hover_definition( &mut self, @@ -102,17 +138,27 @@ impl InputState { return false; } - let Some(hover_definition) = self.hover_definition.as_ref() else { + if self.hover_definition.is_empty() { return false; }; - if !hover_definition.is_same(offset) { + if !self.hover_definition.is_same(offset) { return false; } - let Some(location) = hover_definition.locations.first().cloned() else { + let Some(location) = self.hover_definition.locations.first().cloned() else { return false; }; + self.go_to_definition(&location, cx); + + true + } + + pub(crate) fn go_to_definition( + &mut self, + location: &lsp_types::LocationLink, + cx: &mut Context, + ) { if location .target_uri .scheme() @@ -129,8 +175,6 @@ impl InputState { self.move_to(start, cx); self.select_to(end, cx); } - - true } } @@ -144,7 +188,7 @@ impl TextElement { return None; } - let Some(hover_definition) = editor.hover_definition.as_ref() else { + if editor.hover_definition.is_empty() { return None; }; @@ -160,7 +204,10 @@ impl TextElement { ..UnderlineStyle::default() }); - Some((hover_definition.symbol_range.clone(), highlight_style)) + Some(( + editor.hover_definition.symbol_range.clone(), + highlight_style, + )) } pub(crate) fn layout_hover_definition_hitbox( @@ -173,11 +220,11 @@ impl TextElement { return None; } - let Some(hover_definition) = editor.hover_definition.as_ref() else { + if editor.hover_definition.is_empty() { return None; }; - let Some(bounds) = editor.range_to_bounds(&hover_definition.symbol_range) else { + let Some(bounds) = editor.range_to_bounds(&editor.hover_definition.symbol_range) else { return None; }; diff --git a/crates/ui/src/input/lsp/mod.rs b/crates/ui/src/input/lsp/mod.rs index 67ad61bc..85b96726 100644 --- a/crates/ui/src/input/lsp/mod.rs +++ b/crates/ui/src/input/lsp/mod.rs @@ -82,6 +82,7 @@ impl InputState { handled = menu.handle_action(action, window, cx) }); } + ContextMenu::MouseContext(..) => {} }; handled @@ -114,7 +115,7 @@ impl InputState { self.hover_popover = None; self.handle_hover_definition(offset, window, cx); } else { - self.hover_definition = None; + self.hover_definition.clear(); self.handle_hover_popover(offset, window, cx); } } diff --git a/crates/ui/src/input/mod.rs b/crates/ui/src/input/mod.rs index f89abe95..62f22bd6 100644 --- a/crates/ui/src/input/mod.rs +++ b/crates/ui/src/input/mod.rs @@ -8,7 +8,7 @@ mod mask_pattern; mod mode; mod number_input; mod otp_input; -mod popovers; +pub(crate) mod popovers; mod rope_ext; mod search; mod state; diff --git a/crates/ui/src/input/popovers/context_menu.rs b/crates/ui/src/input/popovers/context_menu.rs new file mode 100644 index 00000000..660972c6 --- /dev/null +++ b/crates/ui/src/input/popovers/context_menu.rs @@ -0,0 +1,143 @@ +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, +}; +use rust_i18n::t; + +use crate::{ + input::{self, popovers::ContextMenu, InputState}, + popup_menu::PopupMenu, +}; + +/// Context menu for mouse right clicks. +pub(crate) struct MouseContextMenu { + editor: Entity, + menu: Entity, + mouse_position: Point, + open: bool, + + _subscriptions: Vec, +} + +impl InputState { + pub(crate) fn handle_right_click_menu( + &mut self, + event: &MouseDownEvent, + offset: usize, + window: &mut Window, + cx: &mut Context, + ) { + // Show Mouse context menu + if !self.selected_range.contains(offset) { + self.move_to(offset, cx); + } + + self.context_menu = Some(ContextMenu::MouseContext(self.mouse_context_menu.clone())); + + let is_code_editor = self.mode.is_code_editor(); + if is_code_editor { + 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(); + + self.mouse_context_menu.update(cx, |this, cx| { + this.mouse_position = event.position; + this.menu.update(cx, |menu, cx| { + let new_menu = PopupMenu::new(cx) + .when(is_code_editor, |m| { + m.menu_with_enable( + t!("Input.Go to Definition"), + Box::new(input::GoToDefinition), + has_goto_definition, + ) + .menu_with_enable( + t!("Input.Show Code Actions"), + Box::new(input::ToggleCodeActions), + has_code_action, + ) + .separator() + }) + .menu_with_enable(t!("Input.Cut"), Box::new(input::Cut), 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; + cx.notify(); + }); + this.open = true; + + cx.notify(); + }); + } +} + +impl MouseContextMenu { + pub(crate) fn new( + editor: Entity, + window: &mut Window, + cx: &mut App, + ) -> Entity { + cx.new(|cx| { + let menu = cx.new(|cx| PopupMenu::new(cx).small()); + + let _subscriptions = vec![cx.subscribe_in(&menu, window, { + move |this: &mut Self, _, _: &DismissEvent, window, cx| { + this.close(window, cx); + } + })]; + + Self { + editor, + menu, + mouse_position: Point::default(), + open: false, + _subscriptions, + } + }) + } + + #[inline] + pub(crate) fn is_open(&self) -> bool { + self.open + } + + #[inline] + pub(crate) fn close(&mut self, window: &mut Window, cx: &mut Context) { + self.open = false; + self.editor.update(cx, |this, cx| { + this.focus(window, cx); + }); + } +} + +impl Render for MouseContextMenu { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + if !self.open { + 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) + .child( + div() + .font_family(".SystemUIFont") + .text_size(px(14.)) + .cursor_default() + .child(self.menu.clone()), + ), + ) + .into_any_element() + } +} diff --git a/crates/ui/src/input/popovers/mod.rs b/crates/ui/src/input/popovers/mod.rs index dcd1ffcd..78752012 100644 --- a/crates/ui/src/input/popovers/mod.rs +++ b/crates/ui/src/input/popovers/mod.rs @@ -1,10 +1,12 @@ mod code_action_menu; mod completion_menu; +mod context_menu; mod diagnostic_popover; mod hover_popover; pub(crate) use code_action_menu::*; pub(crate) use completion_menu::*; +pub(crate) use context_menu::*; pub(crate) use diagnostic_popover::*; pub(crate) use hover_popover::*; @@ -21,6 +23,7 @@ use crate::{ pub(crate) enum ContextMenu { Completion(Entity), CodeAction(Entity), + MouseContext(Entity), } impl ContextMenu { @@ -28,6 +31,7 @@ impl ContextMenu { match self { ContextMenu::Completion(menu) => menu.read(cx).is_open(), ContextMenu::CodeAction(menu) => menu.read(cx).is_open(), + ContextMenu::MouseContext(menu) => menu.read(cx).is_open(), } } @@ -35,6 +39,7 @@ impl ContextMenu { match self { ContextMenu::Completion(menu) => menu.clone().into_any_element(), ContextMenu::CodeAction(menu) => menu.clone().into_any_element(), + ContextMenu::MouseContext(menu) => menu.clone().into_any_element(), } } } diff --git a/crates/ui/src/input/state.rs b/crates/ui/src/input/state.rs index db305380..83994d69 100644 --- a/crates/ui/src/input/state.rs +++ b/crates/ui/src/input/state.rs @@ -30,7 +30,7 @@ use super::{ text_wrapper::TextWrapper, }; use crate::input::{ - popovers::{ContextMenu, DiagnosticPopover, HoverPopover}, + popovers::{ContextMenu, DiagnosticPopover, HoverPopover, MouseContextMenu}, search::{self, SearchPanel}, HoverDefinition, Lsp, Position, }; @@ -92,6 +92,7 @@ actions!( Escape, ToggleCodeActions, Search, + GoToDefinition, ] ); @@ -298,11 +299,12 @@ pub struct InputState { diagnostic_popover: Option>, /// Completion/CodeAction context menu pub(super) context_menu: Option, + pub(super) mouse_context_menu: Entity, /// A flag to indicate if we are currently inserting a completion item. pub(super) completion_inserting: bool, pub(super) hover_popover: Option>, /// The LSP definitions locations for "Go to Definition" feature. - pub(super) hover_definition: Option, + pub(super) hover_definition: HoverDefinition, pub lsp: Lsp, @@ -349,6 +351,7 @@ impl InputState { ]; let text_style = window.text_style(); + let mouse_context_menu = MouseContextMenu::new(cx.entity(), window, cx); Self { focus_handle: focus_handle.clone(), @@ -390,9 +393,10 @@ impl InputState { lsp: Lsp::default(), diagnostic_popover: None, context_menu: None, + mouse_context_menu, completion_inserting: false, hover_popover: None, - hover_definition: None, + hover_definition: HoverDefinition::default(), silent_replace_text: false, _subscriptions, _context_menu_task: Task::ready(Ok(())), @@ -1530,15 +1534,6 @@ impl InputState { cx.propagate(); } - pub(super) fn toggle_code_actions( - &mut self, - _: &ToggleCodeActions, - window: &mut Window, - cx: &mut Context, - ) { - self.handle_code_action_trigger(window, cx) - } - pub(super) fn on_mouse_down( &mut self, event: &MouseDownEvent, @@ -1566,6 +1561,12 @@ impl InputState { return; } + // Show Mouse context menu + if event.button == MouseButton::Right { + self.handle_right_click_menu(event, offset, window, cx); + return; + } + if event.modifiers.shift { self.select_to(offset, cx); } else { diff --git a/crates/ui/src/input/text_input.rs b/crates/ui/src/input/text_input.rs index e9227aab..5c5651f9 100644 --- a/crates/ui/src/input/text_input.rs +++ b/crates/ui/src/input/text_input.rs @@ -278,7 +278,9 @@ impl RenderOnce for TextInput { .on_action(window.listener_for(&self.state, InputState::indent_block)) .on_action(window.listener_for(&self.state, InputState::outdent_block)) }) - .on_action(window.listener_for(&self.state, InputState::toggle_code_actions)) + .on_action( + window.listener_for(&self.state, InputState::on_action_toggle_code_actions), + ) }) .on_action(window.listener_for(&self.state, InputState::left)) .on_action(window.listener_for(&self.state, InputState::right)) @@ -291,6 +293,9 @@ impl RenderOnce for TextInput { .on_action(window.listener_for(&self.state, InputState::select_down)) .on_action(window.listener_for(&self.state, InputState::page_up)) .on_action(window.listener_for(&self.state, InputState::page_down)) + .on_action( + window.listener_for(&self.state, InputState::on_action_go_to_definition), + ) }) .on_action(window.listener_for(&self.state, InputState::select_all)) .on_action(window.listener_for(&self.state, InputState::select_to_start_of_line)) @@ -313,10 +318,18 @@ impl RenderOnce for TextInput { MouseButton::Left, window.listener_for(&self.state, InputState::on_mouse_down), ) + .on_mouse_down( + MouseButton::Right, + window.listener_for(&self.state, InputState::on_mouse_down), + ) .on_mouse_up( MouseButton::Left, window.listener_for(&self.state, InputState::on_mouse_up), ) + .on_mouse_up( + MouseButton::Right, + window.listener_for(&self.state, InputState::on_mouse_up), + ) .on_mouse_move(window.listener_for(&self.state, InputState::on_mouse_move)) .on_scroll_wheel(window.listener_for(&self.state, InputState::on_scroll_wheel)) .size_full() diff --git a/crates/ui/src/menu/context_menu.rs b/crates/ui/src/menu/context_menu.rs index d4e85129..2bc87de9 100644 --- a/crates/ui/src/menu/context_menu.rs +++ b/crates/ui/src/menu/context_menu.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, rc::Rc}; use gpui::{ anchored, deferred, div, prelude::FluentBuilder, px, relative, AnyElement, App, Context, - Corner, DismissEvent, DispatchPhase, Element, ElementId, Entity, Focusable, GlobalElementId, + Corner, DismissEvent, Element, ElementId, Entity, Focusable, GlobalElementId, InspectorElementId, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Position, Stateful, Style, Window, }; @@ -224,7 +224,7 @@ impl Element for ContextMenu { // When right mouse click, to build content menu, and show it at the mouse position. window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| { - if phase == DispatchPhase::Bubble + if phase.bubble() && event.button == MouseButton::Right && bounds.contains(&event.position) { diff --git a/crates/ui/src/menu/popup_menu.rs b/crates/ui/src/menu/popup_menu.rs index 6da01f95..7e3410f0 100644 --- a/crates/ui/src/menu/popup_menu.rs +++ b/crates/ui/src/menu/popup_menu.rs @@ -6,18 +6,17 @@ use crate::{ button::Button, h_flex, popover::Popover, v_flex, ActiveTheme, Icon, IconName, Selectable, Sizable as _, }; -use crate::{Kbd, Side, StyledExt}; +use crate::{Kbd, Side, Size, StyledExt}; use gpui::{ anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement, App, AppContext, Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window, }; -use gpui::{AsKeystroke, MouseDownEvent, Subscription}; +use gpui::{AsKeystroke, Half, MouseDownEvent, Subscription}; use std::ops::Deref; use std::rc::Rc; -const ITEM_HEIGHT: Pixels = px(26.); const CONTEXT: &str = "PopupMenu"; pub fn init(cx: &mut App) { @@ -61,7 +60,7 @@ pub trait PopupMenuExt: Styled + Selectable + InteractiveElement + IntoElement + } impl PopupMenuExt for Button {} -enum PopupMenuItem { +pub(crate) enum PopupMenuItem { Separator, Label(SharedString), Item { @@ -113,13 +112,14 @@ pub struct PopupMenu { /// The parent menu of this menu, if this is a submenu parent_menu: Option>, focus_handle: FocusHandle, - menu_items: Vec, + pub(crate) menu_items: Vec, has_icon: bool, selected_index: Option, min_width: Option, max_width: Option, max_height: Option, bounds: Bounds, + size: Size, scrollable: bool, external_link_icon: bool, @@ -131,32 +131,35 @@ pub struct PopupMenu { } impl PopupMenu { + pub(crate) fn new(cx: &mut App) -> Self { + Self { + focus_handle: cx.focus_handle(), + previous_focus_handle: None, + parent_menu: None, + menu_items: Vec::new(), + selected_index: None, + min_width: None, + max_width: None, + max_height: None, + has_icon: false, + bounds: Bounds::default(), + scrollable: false, + scroll_handle: ScrollHandle::default(), + scroll_state: ScrollbarState::default(), + external_link_icon: true, + size: Size::default(), + _subscriptions: vec![], + } + } + pub fn build( window: &mut Window, cx: &mut App, f: impl FnOnce(Self, &mut Window, &mut Context) -> Self, ) -> Entity { cx.new(|cx| { - let focus_handle = cx.focus_handle(); - let _subscriptions = vec![]; - - let menu = Self { - focus_handle, - previous_focus_handle: window.focused(cx), - parent_menu: None, - menu_items: Vec::new(), - selected_index: None, - min_width: None, - max_width: None, - max_height: None, - has_icon: false, - bounds: Bounds::default(), - scrollable: false, - scroll_handle: ScrollHandle::default(), - scroll_state: ScrollbarState::default(), - external_link_icon: true, - _subscriptions, - }; + let mut menu = Self::new(cx); + menu.previous_focus_handle = window.focused(cx); f(menu, window, cx) }) } @@ -198,6 +201,17 @@ impl PopupMenu { self.menu_with_disabled(label, action, false) } + /// Add Menu Item with enable state + pub fn menu_with_enable( + mut self, + label: impl Into, + action: Box, + enable: bool, + ) -> Self { + self.add_menu_item(label, None, action, !enable); + self + } + /// Add Menu Item with disabled state pub fn menu_with_disabled( mut self, @@ -428,6 +442,12 @@ impl PopupMenu { }) } + /// Use small size, the menu item will have smaller height. + pub(crate) fn small(mut self) -> Self { + self.size = Size::Small; + self + } + /// Add a separator Menu Item pub fn separator(mut self) -> Self { if self.menu_items.is_empty() { @@ -805,12 +825,17 @@ impl PopupMenu { const EDGE_PADDING: Pixels = px(8.); const INNER_PADDING: Pixels = px(4.); + let (item_height, radius) = match self.size { + Size::Small => (px(20.), state.radius.half()), + _ => (px(26.), state.radius), + }; + let this = MenuItem::new(ix) .relative() .text_sm() .py_0() .px(INNER_PADDING) - .rounded(state.radius) + .rounded(radius) .items_center() .hovered(selected) .on_mouse_enter(cx.listener(move |this, _, _, cx| { @@ -853,7 +878,7 @@ impl PopupMenu { .disabled(*disabled) .child( h_flex() - .min_h(ITEM_HEIGHT) + .min_h(item_height) .items_center() .gap_x_1() .children(Self::render_icon(has_icon, icon.clone(), window, cx)) @@ -877,7 +902,7 @@ impl PopupMenu { ) }) .disabled(*disabled) - .h(ITEM_HEIGHT) + .h(item_height) .children(Self::render_icon(has_icon, icon.clone(), window, cx)) .child( h_flex() @@ -914,7 +939,7 @@ impl PopupMenu { .items_start() .child( h_flex() - .min_h(ITEM_HEIGHT) + .min_h(item_height) .size_full() .items_center() .gap_x_1()