menu: Improve Menu to avoid invalid or duplicate separators. (#276)
This commit is contained in:
parent
7a8eaefd30
commit
709c706adf
2 changed files with 125 additions and 88 deletions
|
|
@ -162,10 +162,12 @@ impl Render for PopupStory {
|
||||||
}))
|
}))
|
||||||
.context_menu({
|
.context_menu({
|
||||||
move |this, cx| {
|
move |this, cx| {
|
||||||
this.menu("Cut", Box::new(Cut))
|
this.separator()
|
||||||
|
.menu("Cut", Box::new(Cut))
|
||||||
.menu("Copy", Box::new(Copy))
|
.menu("Copy", Box::new(Copy))
|
||||||
.menu("Paste", Box::new(Paste))
|
.menu("Paste", Box::new(Paste))
|
||||||
.separator()
|
.separator()
|
||||||
|
.separator()
|
||||||
.submenu("Settings", cx, move |menu, _| {
|
.submenu("Settings", cx, move |menu, _| {
|
||||||
menu.menu_with_check(
|
menu.menu_with_check(
|
||||||
"Toggle Window Mode",
|
"Toggle Window Mode",
|
||||||
|
|
@ -179,6 +181,7 @@ impl Render for PopupStory {
|
||||||
})
|
})
|
||||||
.separator()
|
.separator()
|
||||||
.menu("Search All", Box::new(SearchAll))
|
.menu("Search All", Box::new(SearchAll))
|
||||||
|
.separator()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.gap_6()
|
.gap_6()
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@ impl PopupMenuItem {
|
||||||
!matches!(self, PopupMenuItem::Separator)
|
!matches!(self, PopupMenuItem::Separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_separator(&self) -> bool {
|
||||||
|
matches!(self, PopupMenuItem::Separator)
|
||||||
|
}
|
||||||
|
|
||||||
fn has_icon(&self) -> bool {
|
fn has_icon(&self) -> bool {
|
||||||
matches!(self, PopupMenuItem::Item { icon: Some(_), .. })
|
matches!(self, PopupMenuItem::Item { icon: Some(_), .. })
|
||||||
}
|
}
|
||||||
|
|
@ -232,6 +236,14 @@ impl PopupMenu {
|
||||||
|
|
||||||
/// Add a separator Menu Item
|
/// Add a separator Menu Item
|
||||||
pub fn separator(mut self) -> Self {
|
pub fn separator(mut self) -> Self {
|
||||||
|
if self.menu_items.is_empty() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(PopupMenuItem::Separator) = self.menu_items.last() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
self.menu_items.push(PopupMenuItem::Separator);
|
self.menu_items.push(PopupMenuItem::Separator);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -411,6 +423,7 @@ impl Render for PopupMenu {
|
||||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
||||||
let view = cx.view().clone();
|
let view = cx.view().clone();
|
||||||
let has_icon = self.menu_items.iter().any(|item| item.has_icon());
|
let has_icon = self.menu_items.iter().any(|item| item.has_icon());
|
||||||
|
let items_count = self.menu_items.len();
|
||||||
let max_width = self.max_width;
|
let max_width = self.max_width;
|
||||||
let bounds = self.bounds;
|
let bounds = self.bounds;
|
||||||
|
|
||||||
|
|
@ -438,103 +451,124 @@ impl Render for PopupMenu {
|
||||||
.absolute()
|
.absolute()
|
||||||
.size_full()
|
.size_full()
|
||||||
})
|
})
|
||||||
.children(self.menu_items.iter_mut().enumerate().map(|(ix, item)| {
|
.children(
|
||||||
let group_id = format!("item:{}", ix);
|
self.menu_items
|
||||||
let this = ListItem::new(("menu-item", ix))
|
.iter_mut()
|
||||||
.group(group_id.clone())
|
.enumerate()
|
||||||
.relative()
|
// Skip last separator
|
||||||
.text_sm()
|
.filter(|(ix, item)| !(*ix == items_count - 1 && item.is_separator()))
|
||||||
.py_0()
|
.map(|(ix, item)| {
|
||||||
.px_2()
|
let group_id = format!("item:{}", ix);
|
||||||
.h(px(28.))
|
|
||||||
.rounded_md()
|
|
||||||
.items_center()
|
|
||||||
.on_mouse_enter(cx.listener(move |this, _, cx| {
|
|
||||||
this.hovered_menu_ix = Some(ix);
|
|
||||||
cx.notify();
|
|
||||||
}));
|
|
||||||
match item {
|
|
||||||
PopupMenuItem::Separator => this.h_auto().p_0().disabled(true).child(
|
|
||||||
div()
|
|
||||||
.rounded_none()
|
|
||||||
.h(px(1.))
|
|
||||||
.mx_neg_1()
|
|
||||||
.my_0p5()
|
|
||||||
.bg(cx.theme().muted),
|
|
||||||
),
|
|
||||||
PopupMenuItem::Item {
|
|
||||||
icon,
|
|
||||||
label,
|
|
||||||
action,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let action = action.as_ref().map(|action| action.boxed_clone());
|
|
||||||
let key = Self::render_keybinding(action, cx);
|
|
||||||
|
|
||||||
this.on_click(cx.listener(move |this, _, cx| this.on_click(ix, cx)))
|
let this = ListItem::new(("menu-item", ix))
|
||||||
.child(
|
.group(group_id.clone())
|
||||||
h_flex()
|
.relative()
|
||||||
.items_center()
|
.text_sm()
|
||||||
.gap_x_1p5()
|
.py_0()
|
||||||
.children(Self::render_icon(has_icon, icon.clone(), cx))
|
.px_2()
|
||||||
|
.h(px(28.))
|
||||||
|
.rounded_md()
|
||||||
|
.items_center()
|
||||||
|
.on_mouse_enter(cx.listener(move |this, _, cx| {
|
||||||
|
this.hovered_menu_ix = Some(ix);
|
||||||
|
cx.notify();
|
||||||
|
}));
|
||||||
|
|
||||||
|
match item {
|
||||||
|
PopupMenuItem::Separator => this.h_auto().p_0().disabled(true).child(
|
||||||
|
div()
|
||||||
|
.rounded_none()
|
||||||
|
.h(px(1.))
|
||||||
|
.mx_neg_1()
|
||||||
|
.my_0p5()
|
||||||
|
.bg(cx.theme().muted),
|
||||||
|
),
|
||||||
|
PopupMenuItem::Item {
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
action,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let action = action.as_ref().map(|action| action.boxed_clone());
|
||||||
|
let key = Self::render_keybinding(action, cx);
|
||||||
|
|
||||||
|
this.on_click(cx.listener(move |this, _, cx| this.on_click(ix, cx)))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_1()
|
|
||||||
.gap_2()
|
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.gap_x_1p5()
|
||||||
.child(label.clone())
|
.children(Self::render_icon(has_icon, icon.clone(), cx))
|
||||||
.children(key),
|
.child(
|
||||||
),
|
h_flex()
|
||||||
)
|
.flex_1()
|
||||||
}
|
.gap_2()
|
||||||
PopupMenuItem::Submenu { icon, label, menu } => this
|
.items_center()
|
||||||
.when(self.hovered_menu_ix == Some(ix), |this| this.selected(true))
|
.justify_between()
|
||||||
.child(
|
.child(label.clone())
|
||||||
h_flex()
|
.children(key),
|
||||||
.items_start()
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PopupMenuItem::Submenu { icon, label, menu } => this
|
||||||
|
.when(self.hovered_menu_ix == Some(ix), |this| this.selected(true))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.size_full()
|
.items_start()
|
||||||
.items_center()
|
|
||||||
.gap_x_1p5()
|
|
||||||
.children(Self::render_icon(has_icon, icon.clone(), cx))
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_1()
|
.size_full()
|
||||||
.gap_2()
|
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.gap_x_1p5()
|
||||||
.child(label.clone())
|
.children(Self::render_icon(
|
||||||
.child(IconName::ChevronRight),
|
has_icon,
|
||||||
),
|
icon.clone(),
|
||||||
)
|
cx,
|
||||||
.when_some(self.hovered_menu_ix, |this, hovered_ix| {
|
))
|
||||||
let (anchor, left) =
|
.child(
|
||||||
if cx.bounds().size.width - bounds.origin.x < max_width {
|
h_flex()
|
||||||
(AnchorCorner::TopRight, -px(15.))
|
.flex_1()
|
||||||
} else {
|
.gap_2()
|
||||||
(AnchorCorner::TopLeft, bounds.size.width - px(10.))
|
.items_center()
|
||||||
};
|
.justify_between()
|
||||||
|
.child(label.clone())
|
||||||
|
.child(IconName::ChevronRight),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.when_some(self.hovered_menu_ix, |this, hovered_ix| {
|
||||||
|
let (anchor, left) = if cx.bounds().size.width
|
||||||
|
- bounds.origin.x
|
||||||
|
< max_width
|
||||||
|
{
|
||||||
|
(AnchorCorner::TopRight, -px(15.))
|
||||||
|
} else {
|
||||||
|
(AnchorCorner::TopLeft, bounds.size.width - px(10.))
|
||||||
|
};
|
||||||
|
|
||||||
let top = if bounds.origin.y + bounds.size.height
|
let top = if bounds.origin.y + bounds.size.height
|
||||||
> cx.bounds().size.height
|
> cx.bounds().size.height
|
||||||
{
|
{
|
||||||
px(32.)
|
px(32.)
|
||||||
} else {
|
} else {
|
||||||
-px(10.)
|
-px(10.)
|
||||||
};
|
};
|
||||||
|
|
||||||
if hovered_ix == ix {
|
if hovered_ix == ix {
|
||||||
this.child(anchored().anchor(anchor).child(
|
this.child(
|
||||||
div().occlude().top(top).left(left).child(menu.clone()),
|
anchored().anchor(anchor).child(
|
||||||
))
|
div()
|
||||||
} else {
|
.occlude()
|
||||||
this
|
.top(top)
|
||||||
}
|
.left(left)
|
||||||
}),
|
.child(menu.clone()),
|
||||||
),
|
),
|
||||||
}
|
)
|
||||||
}))
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue