menu: Add menu_with_element to add a custom render item. (#330)

<img width="230" alt="image"
src="https://github.com/user-attachments/assets/e12dbde4-c02e-4445-b8ff-5bf0b54c7aac">
This commit is contained in:
Jason Lee 2024-10-10 20:22:25 +08:00 committed by GitHub
parent 2706656e27
commit 5f1b68a431
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 81 additions and 27 deletions

View file

@ -14,6 +14,7 @@ use ui::{
popover::{Popover, PopoverContent}, popover::{Popover, PopoverContent},
popup_menu::PopupMenuExt, popup_menu::PopupMenuExt,
switch::Switch, switch::Switch,
theme::ActiveTheme as _,
v_flex, ContextModal, IconName, Sizable, v_flex, ContextModal, IconName, Sizable,
}; };
@ -274,6 +275,18 @@ impl Render for PopupStory {
Box::new(ToggleWindowMode), Box::new(ToggleWindowMode),
) )
.separator() .separator()
.menu_with_element(
|cx| {
v_flex().gap_1().child("Custom Element").child(
div()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child("THis is sub-title"),
)
},
Box::new(Info(0)),
)
.separator()
.submenu("Links", cx, |menu, _| { .submenu("Links", cx, |menu, _| {
menu.link_with_icon( menu.link_with_icon(
"GitHub Repository", "GitHub Repository",

View file

@ -8,8 +8,8 @@ use gpui::{
SharedString, Styled as _, View, ViewContext, VisualContext as _, WindowContext, SharedString, Styled as _, View, ViewContext, VisualContext as _, WindowContext,
}; };
use gpui::{ use gpui::{
anchored, canvas, rems, AnchorCorner, Bounds, Edges, FocusableView, Keystroke, ScrollHandle, anchored, canvas, rems, AnchorCorner, AnyElement, Bounds, Edges, FocusableView, Keystroke,
StatefulInteractiveElement, WeakView, ScrollHandle, StatefulInteractiveElement, WeakView,
}; };
use crate::scroll::{Scrollbar, ScrollbarState}; use crate::scroll::{Scrollbar, ScrollbarState};
@ -53,6 +53,10 @@ enum PopupMenuItem {
action: Option<Box<dyn Action>>, action: Option<Box<dyn Action>>,
handler: Rc<dyn Fn(&mut WindowContext)>, handler: Rc<dyn Fn(&mut WindowContext)>,
}, },
ElementItem {
render: Box<dyn Fn(&mut WindowContext) -> AnyElement + 'static>,
handler: Rc<dyn Fn(&mut WindowContext)>,
},
Submenu { Submenu {
icon: Option<Icon>, icon: Option<Icon>,
label: SharedString, label: SharedString,
@ -214,23 +218,23 @@ impl PopupMenu {
self self
} }
fn add_menu_item( /// Add Menu Item with custom element render.
&mut self, pub fn menu_with_element<F, E>(mut self, builder: F, action: Box<dyn Action>) -> Self
label: impl Into<SharedString>, where
icon: Option<Icon>, F: Fn(&mut WindowContext) -> E + 'static,
action: Box<dyn Action>, E: IntoElement,
) -> &mut Self { {
if icon.is_some() { self.menu_items.push(PopupMenuItem::ElementItem {
self.has_icon = true; render: Box::new(move |cx| builder(cx).into_any_element()),
handler: self.wrap_handler(action),
});
self
} }
fn wrap_handler(&self, action: Box<dyn Action>) -> Rc<dyn Fn(&mut WindowContext)> {
let action_focus_handle = self.action_focus_handle.clone(); let action_focus_handle = self.action_focus_handle.clone();
self.menu_items.push(PopupMenuItem::Item { Rc::new(move |cx| {
icon,
label: label.into(),
action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| {
cx.activate_window(); cx.activate_window();
// Focus back to the user expected focus handle // Focus back to the user expected focus handle
@ -250,7 +254,24 @@ impl PopupMenu {
} }
cx.dispatch_action(action.boxed_clone()); cx.dispatch_action(action.boxed_clone());
}), })
}
fn add_menu_item(
&mut self,
label: impl Into<SharedString>,
icon: Option<Icon>,
action: Box<dyn Action>,
) -> &mut Self {
if icon.is_some() {
self.has_icon = true;
}
self.menu_items.push(PopupMenuItem::Item {
icon,
label: label.into(),
action: Some(action.boxed_clone()),
handler: self.wrap_handler(action),
}); });
self self
} }
@ -336,6 +357,10 @@ impl PopupMenu {
handler(cx); handler(cx);
self.dismiss(&Dismiss, cx) self.dismiss(&Dismiss, cx)
} }
Some(PopupMenuItem::ElementItem { handler, .. }) => {
handler(cx);
self.dismiss(&Dismiss, cx)
}
_ => {} _ => {}
} }
} }
@ -422,7 +447,7 @@ impl PopupMenu {
.text_sm() .text_sm()
.map(|this| { .map(|this| {
if let Some(icon) = icon { if let Some(icon) = icon {
this.child(icon.clone().small().clone()) this.child(icon.clone().small())
} else { } else {
this.children(icon_placeholder.clone()) this.children(icon_placeholder.clone())
} }
@ -451,6 +476,8 @@ impl Render for PopupMenu {
let window_haft_height = cx.window_bounds().get_bounds().size.height * 0.5; let window_haft_height = cx.window_bounds().get_bounds().size.height * 0.5;
let max_height = window_haft_height.min(px(450.)); let max_height = window_haft_height.min(px(450.));
const ITEM_HEIGHT: Pixels = px(26.);
v_flex() v_flex()
.id("popup-menu") .id("popup-menu")
.key_context("PopupMenu") .key_context("PopupMenu")
@ -521,6 +548,20 @@ impl Render for PopupMenu {
.bg(cx.theme().muted), .bg(cx.theme().muted),
) )
} }
PopupMenuItem::ElementItem { render, .. } => this
.on_click(cx.listener(move |this, _, cx| {
this.on_click(ix, cx)
}))
.child(
h_flex()
.min_h(ITEM_HEIGHT)
.items_center()
.gap_x_1p5()
.children(Self::render_icon(
has_icon, None, cx,
))
.child((render)(cx)),
),
PopupMenuItem::Item { PopupMenuItem::Item {
icon, icon,
label, label,
@ -537,7 +578,7 @@ impl Render for PopupMenu {
})) }))
.child( .child(
h_flex() h_flex()
.h(px(26.)) .h(ITEM_HEIGHT)
.items_center() .items_center()
.gap_x_1p5() .gap_x_1p5()
.children(Self::render_icon( .children(Self::render_icon(