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:
parent
2706656e27
commit
5f1b68a431
2 changed files with 81 additions and 27 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue