clean up components
This commit is contained in:
@@ -2,22 +2,22 @@ use std::rc::Rc;
|
||||
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
Action, Anchor, AnyElement, App, AppContext, Axis, Bounds, ClickEvent, Context, DismissEvent,
|
||||
Edges, Entity, EventEmitter, FocusHandle, Focusable, Half, InteractiveElement, IntoElement,
|
||||
KeyBinding, MouseDownEvent, ParentElement, Pixels, Point, Render, ScrollHandle, SharedString,
|
||||
StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, anchored, div, px, rems,
|
||||
Action, Anchor, App, AppContext, Axis, Bounds, ClickEvent, Context, DismissEvent, Edges,
|
||||
Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding,
|
||||
MouseDownEvent, ParentElement, Pixels, Point, Render, SharedString, Styled, WeakEntity, Window,
|
||||
anchored, div, px, rems,
|
||||
};
|
||||
use gpui_base::actions::{Cancel, Confirm, SelectDown, SelectLeft, SelectRight, SelectUp};
|
||||
use theme::{ActiveTheme, Side};
|
||||
|
||||
use crate::kbd::Kbd;
|
||||
use crate::menu::menu_item::MenuItemElement;
|
||||
use crate::scroll::ScrollableElement;
|
||||
use crate::{ElementExt, Icon, IconName, Sizable as _, Size, StyledExt, h_flex, v_flex};
|
||||
use crate::{Disableable, ElementExt, Icon, IconName, Sizable as _, StyledExt, h_flex, v_flex};
|
||||
|
||||
const CONTEXT: &str = "PopupMenu";
|
||||
const MAX_WIDTH: Pixels = px(500.);
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
pub(crate) fn init(cx: &mut App) {
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("enter", Confirm { secondary: false }, Some(CONTEXT)),
|
||||
KeyBinding::new("escape", Cancel, Some(CONTEXT)),
|
||||
@@ -28,7 +28,7 @@ pub fn init(cx: &mut App) {
|
||||
]);
|
||||
}
|
||||
|
||||
/// An menu item in a popup menu.
|
||||
/// A menu item in a popup menu.
|
||||
pub enum PopupMenuItem {
|
||||
/// A menu separator item.
|
||||
Separator,
|
||||
@@ -40,26 +40,11 @@ pub enum PopupMenuItem {
|
||||
label: SharedString,
|
||||
disabled: bool,
|
||||
checked: bool,
|
||||
is_link: bool,
|
||||
action: Option<Box<dyn Action>>,
|
||||
// For link item
|
||||
#[allow(clippy::type_complexity)]
|
||||
handler: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
},
|
||||
/// A menu item with custom element render.
|
||||
ElementItem {
|
||||
icon: Option<Icon>,
|
||||
disabled: bool,
|
||||
checked: bool,
|
||||
action: Option<Box<dyn Action>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
handler: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
},
|
||||
/// A submenu item that opens another popup menu.
|
||||
///
|
||||
/// NOTE: This is only supported when the parent menu is not `scrollable`.
|
||||
Submenu {
|
||||
icon: Option<Icon>,
|
||||
label: SharedString,
|
||||
@@ -80,14 +65,13 @@ impl PopupMenuItem {
|
||||
disabled: false,
|
||||
checked: false,
|
||||
action: None,
|
||||
is_link: false,
|
||||
handler: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new submenu item that opens another popup menu.
|
||||
#[inline]
|
||||
pub fn submenu(label: impl Into<SharedString>, menu: Entity<PopupMenu>) -> Self {
|
||||
fn submenu(label: impl Into<SharedString>, menu: Entity<PopupMenu>) -> Self {
|
||||
PopupMenuItem::Submenu {
|
||||
icon: None,
|
||||
label: label.into(),
|
||||
@@ -98,27 +82,22 @@ impl PopupMenuItem {
|
||||
|
||||
/// Create a separator menu item.
|
||||
#[inline]
|
||||
pub fn separator() -> Self {
|
||||
fn separator() -> Self {
|
||||
PopupMenuItem::Separator
|
||||
}
|
||||
|
||||
/// Creates a label menu item.
|
||||
/// Create a label menu item.
|
||||
#[inline]
|
||||
pub fn label(label: impl Into<SharedString>) -> Self {
|
||||
fn label(label: impl Into<SharedString>) -> Self {
|
||||
PopupMenuItem::Label(label.into())
|
||||
}
|
||||
|
||||
/// Set the icon for the menu item.
|
||||
///
|
||||
/// Only works for [`PopupMenuItem::Item`], [`PopupMenuItem::ElementItem`] and [`PopupMenuItem::Submenu`].
|
||||
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
|
||||
fn icon(mut self, icon: impl Into<Icon>) -> Self {
|
||||
match &mut self {
|
||||
PopupMenuItem::Item { icon: i, .. } => {
|
||||
*i = Some(icon.into());
|
||||
}
|
||||
PopupMenuItem::ElementItem { icon: i, .. } => {
|
||||
*i = Some(icon.into());
|
||||
}
|
||||
PopupMenuItem::Submenu { icon: i, .. } => {
|
||||
*i = Some(icon.into());
|
||||
}
|
||||
@@ -128,32 +107,19 @@ impl PopupMenuItem {
|
||||
}
|
||||
|
||||
/// Set the action for the menu item.
|
||||
///
|
||||
/// Only works for [`PopupMenuItem::Item`] and [`PopupMenuItem::ElementItem`].
|
||||
pub fn action(mut self, action: Box<dyn Action>) -> Self {
|
||||
match &mut self {
|
||||
PopupMenuItem::Item { action: a, .. } => {
|
||||
*a = Some(action);
|
||||
}
|
||||
PopupMenuItem::ElementItem { action: a, .. } => {
|
||||
*a = Some(action);
|
||||
}
|
||||
_ => {}
|
||||
fn action(mut self, action: Box<dyn Action>) -> Self {
|
||||
if let PopupMenuItem::Item { action: a, .. } = &mut self {
|
||||
*a = Some(action);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the disabled state for the menu item.
|
||||
///
|
||||
/// Only works for [`PopupMenuItem::Item`], [`PopupMenuItem::ElementItem`] and [`PopupMenuItem::Submenu`].
|
||||
pub fn disabled(mut self, disabled: bool) -> Self {
|
||||
fn disabled(mut self, disabled: bool) -> Self {
|
||||
match &mut self {
|
||||
PopupMenuItem::Item { disabled: d, .. } => {
|
||||
*d = disabled;
|
||||
}
|
||||
PopupMenuItem::ElementItem { disabled: d, .. } => {
|
||||
*d = disabled;
|
||||
}
|
||||
PopupMenuItem::Submenu { disabled: d, .. } => {
|
||||
*d = disabled;
|
||||
}
|
||||
@@ -162,57 +128,37 @@ impl PopupMenuItem {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set checked state for the menu item.
|
||||
///
|
||||
/// NOTE: If `check_side` is [`Side::Left`], the icon will replace with a check icon.
|
||||
pub fn checked(mut self, checked: bool) -> Self {
|
||||
match &mut self {
|
||||
PopupMenuItem::Item { checked: c, .. } => {
|
||||
*c = checked;
|
||||
}
|
||||
PopupMenuItem::ElementItem { checked: c, .. } => {
|
||||
*c = checked;
|
||||
}
|
||||
_ => {}
|
||||
/// Set the checked state for the menu item.
|
||||
fn checked(mut self, checked: bool) -> Self {
|
||||
if let PopupMenuItem::Item { checked: c, .. } = &mut self {
|
||||
*c = checked;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a click handler for the menu item.
|
||||
///
|
||||
/// Only works for [`PopupMenuItem::Item`] and [`PopupMenuItem::ElementItem`].
|
||||
pub fn on_click<F>(mut self, handler: F) -> Self
|
||||
where
|
||||
F: Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
{
|
||||
match &mut self {
|
||||
PopupMenuItem::Item { handler: h, .. } => {
|
||||
*h = Some(Rc::new(handler));
|
||||
}
|
||||
PopupMenuItem::ElementItem { handler: h, .. } => {
|
||||
*h = Some(Rc::new(handler));
|
||||
}
|
||||
_ => {}
|
||||
if let PopupMenuItem::Item { handler: h, .. } = &mut self {
|
||||
*h = Some(Rc::new(handler));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_clickable(&self) -> bool {
|
||||
!matches!(self, PopupMenuItem::Separator)
|
||||
&& matches!(
|
||||
self,
|
||||
PopupMenuItem::Item {
|
||||
disabled: false,
|
||||
..
|
||||
} | PopupMenuItem::ElementItem {
|
||||
disabled: false,
|
||||
..
|
||||
} | PopupMenuItem::Submenu {
|
||||
disabled: false,
|
||||
..
|
||||
}
|
||||
)
|
||||
matches!(
|
||||
self,
|
||||
PopupMenuItem::Item {
|
||||
disabled: false,
|
||||
..
|
||||
} | PopupMenuItem::Submenu {
|
||||
disabled: false,
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -220,14 +166,9 @@ impl PopupMenuItem {
|
||||
matches!(self, PopupMenuItem::Separator)
|
||||
}
|
||||
|
||||
fn has_left_icon(&self, check_side: Side) -> bool {
|
||||
fn has_left_icon(&self) -> bool {
|
||||
match self {
|
||||
PopupMenuItem::Item { icon, checked, .. } => {
|
||||
icon.is_some() || (check_side.is_left() && *checked)
|
||||
}
|
||||
PopupMenuItem::ElementItem { icon, checked, .. } => {
|
||||
icon.is_some() || (check_side.is_left() && *checked)
|
||||
}
|
||||
PopupMenuItem::Item { icon, checked, .. } => icon.is_some() || *checked,
|
||||
PopupMenuItem::Submenu { icon, .. } => icon.is_some(),
|
||||
_ => false,
|
||||
}
|
||||
@@ -235,66 +176,38 @@ impl PopupMenuItem {
|
||||
|
||||
#[inline]
|
||||
fn is_checked(&self) -> bool {
|
||||
match self {
|
||||
PopupMenuItem::Item { checked, .. } => *checked,
|
||||
PopupMenuItem::ElementItem { checked, .. } => *checked,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, PopupMenuItem::Item { checked: true, .. })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PopupMenu {
|
||||
pub(crate) focus_handle: FocusHandle,
|
||||
pub(crate) menu_items: Vec<PopupMenuItem>,
|
||||
|
||||
/// The focus handle of Entity to handle actions.
|
||||
pub(crate) action_context: Option<FocusHandle>,
|
||||
|
||||
focus_handle: FocusHandle,
|
||||
menu_items: Vec<PopupMenuItem>,
|
||||
axis: Axis,
|
||||
selected_index: Option<usize>,
|
||||
min_width: Option<Pixels>,
|
||||
max_width: Option<Pixels>,
|
||||
max_height: Option<Pixels>,
|
||||
bounds: Bounds<Pixels>,
|
||||
size: Size,
|
||||
check_side: Side,
|
||||
|
||||
/// The parent menu of this menu, if this is a submenu
|
||||
parent_menu: Option<WeakEntity<Self>>,
|
||||
scrollable: bool,
|
||||
external_link_icon: bool,
|
||||
scroll_handle: ScrollHandle,
|
||||
|
||||
/// This will update on render
|
||||
submenu_anchor: (Anchor, Pixels),
|
||||
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl PopupMenu {
|
||||
pub(crate) fn new(cx: &mut App) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
action_context: None,
|
||||
parent_menu: None,
|
||||
menu_items: Vec::new(),
|
||||
selected_index: None,
|
||||
axis: Axis::Vertical,
|
||||
min_width: None,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
check_side: Side::Left,
|
||||
bounds: Bounds::default(),
|
||||
scrollable: false,
|
||||
scroll_handle: ScrollHandle::default(),
|
||||
external_link_icon: true,
|
||||
size: Size::default(),
|
||||
submenu_anchor: (Anchor::TopLeft, Pixels::ZERO),
|
||||
_subscriptions: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(
|
||||
pub(crate) fn build(
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
f: impl FnOnce(Self, &mut Window, &mut Context<PopupMenu>) -> Self,
|
||||
@@ -302,45 +215,15 @@ impl PopupMenu {
|
||||
cx.new(|cx| f(Self::new(cx), window, cx))
|
||||
}
|
||||
|
||||
/// Set min width of the popup menu, default is 120px
|
||||
pub fn min_w(mut self, width: impl Into<Pixels>) -> Self {
|
||||
self.min_width = Some(width.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set max height of the popup menu, default is half of the window height
|
||||
pub fn max_h(mut self, height: impl Into<Pixels>) -> Self {
|
||||
self.max_height = Some(height.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the axis of children to horizontal.
|
||||
pub fn horizontal(mut self) -> Self {
|
||||
self.axis = Axis::Horizontal;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the menu to be scrollable to show vertical scrollbar.
|
||||
///
|
||||
/// NOTE: If this is true, the sub-menus will cannot be support.
|
||||
pub fn scrollable(mut self, scrollable: bool) -> Self {
|
||||
self.scrollable = scrollable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add Menu Item
|
||||
pub fn menu(self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
|
||||
self.menu_with_disabled(label, action, false)
|
||||
}
|
||||
|
||||
/// Add Menu Item with disabled state
|
||||
pub fn menu_with_disabled(
|
||||
mut self,
|
||||
label: impl Into<SharedString>,
|
||||
action: Box<dyn Action>,
|
||||
disabled: bool,
|
||||
) -> Self {
|
||||
self.add_menu_item(label, None, action, disabled, false);
|
||||
pub fn menu(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
|
||||
self.add_menu_item(label, None, action, false, false);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -399,19 +282,7 @@ impl PopupMenu {
|
||||
|
||||
/// Add a Submenu
|
||||
pub fn submenu(
|
||||
self,
|
||||
label: impl Into<SharedString>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
||||
) -> Self {
|
||||
self.submenu_with_icon(None, label, window, cx, f)
|
||||
}
|
||||
|
||||
/// Add a Submenu item with icon
|
||||
pub fn submenu_with_icon(
|
||||
mut self,
|
||||
icon: Option<Icon>,
|
||||
label: impl Into<SharedString>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -423,9 +294,7 @@ impl PopupMenu {
|
||||
view.parent_menu = Some(parent_menu);
|
||||
});
|
||||
|
||||
self.menu_items.push(
|
||||
PopupMenuItem::submenu(label, submenu).when_some(icon, |this, icon| this.icon(icon)),
|
||||
);
|
||||
self.menu_items.push(PopupMenuItem::submenu(label, submenu));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -436,13 +305,6 @@ impl PopupMenu {
|
||||
self
|
||||
}
|
||||
|
||||
/// Use small size, the menu item will have smaller height.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn small(mut self) -> Self {
|
||||
self.size = Size::Small;
|
||||
self
|
||||
}
|
||||
|
||||
fn add_menu_item(
|
||||
&mut self,
|
||||
label: impl Into<SharedString>,
|
||||
@@ -461,7 +323,7 @@ impl PopupMenu {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn active_submenu(&self) -> Option<Entity<PopupMenu>> {
|
||||
fn active_submenu(&self) -> Option<Entity<PopupMenu>> {
|
||||
if let Some(ix) = self.selected_index
|
||||
&& let Some(item) = self.menu_items.get(ix)
|
||||
{
|
||||
@@ -489,52 +351,24 @@ impl PopupMenu {
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(index) = self.selected_index {
|
||||
let item = self.menu_items.get(index);
|
||||
match item {
|
||||
Some(PopupMenuItem::Item {
|
||||
handler, action, ..
|
||||
}) => {
|
||||
if let Some(handler) = handler {
|
||||
handler(&ClickEvent::default(), window, cx);
|
||||
} else if let Some(action) = action.as_ref() {
|
||||
self.dispatch_confirm_action(action.as_ref(), window, cx);
|
||||
}
|
||||
|
||||
self.dismiss(&Cancel, window, cx)
|
||||
}
|
||||
Some(PopupMenuItem::ElementItem {
|
||||
handler, action, ..
|
||||
}) => {
|
||||
if let Some(handler) = handler {
|
||||
handler(&ClickEvent::default(), window, cx);
|
||||
} else if let Some(action) = action.as_ref() {
|
||||
self.dispatch_confirm_action(action.as_ref(), window, cx);
|
||||
}
|
||||
self.dismiss(&Cancel, window, cx)
|
||||
}
|
||||
_ => {}
|
||||
if let Some(index) = self.selected_index
|
||||
&& let Some(PopupMenuItem::Item {
|
||||
handler, action, ..
|
||||
}) = self.menu_items.get(index)
|
||||
{
|
||||
if let Some(handler) = handler {
|
||||
handler(&ClickEvent::default(), window, cx);
|
||||
} else if let Some(action) = action.as_ref() {
|
||||
window.dispatch_action(action.boxed_clone(), cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_confirm_action(
|
||||
&self,
|
||||
action: &dyn Action,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(context) = self.action_context.as_ref() {
|
||||
context.focus(window, cx);
|
||||
self.dismiss(&Cancel, window, cx)
|
||||
}
|
||||
|
||||
window.dispatch_action(action.boxed_clone(), cx);
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut Context<Self>) {
|
||||
if self.selected_index != Some(ix) {
|
||||
self.selected_index = Some(ix);
|
||||
self.scroll_handle.scroll_to_item(ix);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -675,6 +509,7 @@ impl PopupMenu {
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::only_used_in_recursion)]
|
||||
fn dismiss(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.active_submenu().is_some() {
|
||||
return;
|
||||
@@ -682,20 +517,17 @@ impl PopupMenu {
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
|
||||
// Focus back to the previous focused handle.
|
||||
if let Some(action_context) = self.action_context.as_ref() {
|
||||
window.focus(action_context, cx);
|
||||
}
|
||||
|
||||
let Some(parent_menu) = self.parent_menu.clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Dismiss parent menu, when this menu is dismissed
|
||||
_ = parent_menu.update(cx, |view, cx| {
|
||||
if let Err(error) = parent_menu.update(cx, |view, cx| {
|
||||
view.selected_index = None;
|
||||
view.dismiss(&Cancel, window, cx);
|
||||
});
|
||||
}) {
|
||||
log::warn!("Failed to dismiss parent menu: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_dismiss(
|
||||
@@ -732,16 +564,7 @@ impl PopupMenu {
|
||||
) -> Option<Kbd> {
|
||||
let action = action?;
|
||||
|
||||
match self
|
||||
.action_context
|
||||
.as_ref()
|
||||
.and_then(|handle| Kbd::binding_for_action_in(action.as_ref(), handle, window))
|
||||
{
|
||||
Some(kbd) => Some(kbd),
|
||||
// Fallback to App level key binding
|
||||
None => Kbd::binding_for_action(action.as_ref(), None, window),
|
||||
}
|
||||
.map(|this| {
|
||||
Kbd::binding_for_action(action.as_ref(), None, window).map(|this| {
|
||||
this.p_0()
|
||||
.flex_nowrap()
|
||||
.border_0()
|
||||
@@ -771,16 +594,10 @@ impl PopupMenu {
|
||||
Some(icon)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn max_width(&self) -> Pixels {
|
||||
self.max_width.unwrap_or(px(500.))
|
||||
}
|
||||
|
||||
/// Calculate the anchor corner and left offset for child submenu
|
||||
fn update_submenu_menu_anchor(&mut self, window: &Window) {
|
||||
let bounds = self.bounds;
|
||||
let max_width = self.max_width();
|
||||
let (anchor, left) = if max_width + bounds.origin.x > window.bounds().size.width {
|
||||
let (anchor, left) = if MAX_WIDTH + bounds.origin.x > window.bounds().size.width {
|
||||
(Anchor::TopRight, -px(16.))
|
||||
} else {
|
||||
(Anchor::TopLeft, bounds.size.width - px(8.))
|
||||
@@ -803,12 +620,7 @@ impl PopupMenu {
|
||||
cx: &mut Context<Self>,
|
||||
) -> MenuItemElement {
|
||||
let has_left_icon = options.has_left_icon;
|
||||
let is_left_check = options.check_side.is_left() && item.is_checked();
|
||||
let right_check_icon = if options.check_side.is_right() && item.is_checked() {
|
||||
Some(Icon::new(IconName::Check).xsmall())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let is_checked = item.is_checked();
|
||||
|
||||
let selected = self.selected_index == Some(ix);
|
||||
const EDGE_PADDING: Pixels = px(4.);
|
||||
@@ -817,17 +629,14 @@ impl PopupMenu {
|
||||
let is_submenu = matches!(item, PopupMenuItem::Submenu { .. });
|
||||
let group_name = format!("{}:item-{}", cx.entity().entity_id(), ix);
|
||||
|
||||
let (item_height, radius) = match self.size {
|
||||
Size::Small => (px(20.), options.radius.half()),
|
||||
_ => (px(26.), options.radius),
|
||||
};
|
||||
let item_height = px(26.);
|
||||
|
||||
let this = MenuItemElement::new(ix, &group_name)
|
||||
.relative()
|
||||
.text_sm()
|
||||
.py_0()
|
||||
.px(INNER_PADDING)
|
||||
.rounded(radius)
|
||||
.rounded(options.radius)
|
||||
.items_center()
|
||||
.selected(selected)
|
||||
.on_hover(cx.listener(move |this, hovered, _, cx| {
|
||||
@@ -860,33 +669,13 @@ impl PopupMenu {
|
||||
.child(label.clone()),
|
||||
),
|
||||
),
|
||||
PopupMenuItem::ElementItem {
|
||||
render, disabled, ..
|
||||
} => this
|
||||
.when(!disabled, |this| {
|
||||
this.on_click(
|
||||
cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx)),
|
||||
)
|
||||
})
|
||||
.disabled(*disabled)
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
.min_h(item_height)
|
||||
.items_center()
|
||||
.gap_x_2()
|
||||
.child((render)(window, cx))
|
||||
.children(right_check_icon.map(|icon| icon.ml_3())),
|
||||
),
|
||||
PopupMenuItem::Item {
|
||||
icon,
|
||||
label,
|
||||
action,
|
||||
disabled,
|
||||
is_link,
|
||||
..
|
||||
} => {
|
||||
let show_link_icon = *is_link && self.external_link_icon;
|
||||
let action = action.as_ref().map(|action| action.boxed_clone());
|
||||
let key = self.render_key_binding(action, window, cx);
|
||||
|
||||
@@ -900,7 +689,7 @@ impl PopupMenu {
|
||||
.gap_x_2()
|
||||
.children(Self::render_icon(
|
||||
has_left_icon,
|
||||
is_left_check,
|
||||
is_checked,
|
||||
icon.clone(),
|
||||
window,
|
||||
cx,
|
||||
@@ -911,22 +700,7 @@ impl PopupMenu {
|
||||
.gap_3()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.when(!show_link_icon, |this| this.child(label.clone()))
|
||||
.children(right_check_icon)
|
||||
.when(show_link_icon, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(label.clone())
|
||||
.child(
|
||||
Icon::new(IconName::Link)
|
||||
.xsmall()
|
||||
.text_color(cx.theme().text_muted),
|
||||
),
|
||||
)
|
||||
})
|
||||
.child(label.clone())
|
||||
.children(key),
|
||||
)
|
||||
}
|
||||
@@ -1002,7 +776,6 @@ impl Focusable for PopupMenu {
|
||||
#[derive(Clone, Copy)]
|
||||
struct RenderOptions {
|
||||
has_left_icon: bool,
|
||||
check_side: Side,
|
||||
radius: Pixels,
|
||||
}
|
||||
|
||||
@@ -1013,20 +786,10 @@ impl Render for PopupMenu {
|
||||
let view = cx.entity().clone();
|
||||
let items_count = self.menu_items.len();
|
||||
|
||||
let max_width = self.max_width();
|
||||
let max_height = self.max_height.unwrap_or_else(|| {
|
||||
let window_half_height = window.window_bounds().get_bounds().size.height * 0.5;
|
||||
window_half_height.min(px(450.))
|
||||
});
|
||||
|
||||
let has_left_icon = self
|
||||
.menu_items
|
||||
.iter()
|
||||
.any(|item| item.has_left_icon(self.check_side));
|
||||
let has_left_icon = self.menu_items.iter().any(|item| item.has_left_icon());
|
||||
|
||||
let options = RenderOptions {
|
||||
has_left_icon,
|
||||
check_side: self.check_side,
|
||||
radius: cx.theme().radius.min(px(8.)),
|
||||
};
|
||||
|
||||
@@ -1051,17 +814,11 @@ impl Render for PopupMenu {
|
||||
.p_1()
|
||||
.gap_y_0p5()
|
||||
.min_w(rems(8.))
|
||||
.when_some(self.min_width, |this, min_width| this.min_w(min_width))
|
||||
.max_w(max_width)
|
||||
.max_w(MAX_WIDTH)
|
||||
.map(|this| match self.axis {
|
||||
Axis::Horizontal => this.flex().flex_row().items_center(),
|
||||
Axis::Vertical => this.flex().flex_col(),
|
||||
})
|
||||
.when(self.scrollable, |this| {
|
||||
this.max_h(max_height)
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(&self.scroll_handle)
|
||||
})
|
||||
.children(
|
||||
self.menu_items
|
||||
.iter()
|
||||
@@ -1072,9 +829,5 @@ impl Render for PopupMenu {
|
||||
)
|
||||
.on_prepaint(move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds)),
|
||||
)
|
||||
.when(self.scrollable, |this| {
|
||||
// TODO: When the menu is limited by `overflow_y_scroll`, the sub-menu will cannot be displayed.
|
||||
this.vertical_scrollbar(&self.scroll_handle)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user