Add link, link_with_icon to PopupMenu, and remove track_focus. (#121)

- Add `popup_menu` quick API to Button to let easy to use.
This commit is contained in:
Jason Lee 2024-08-08 19:50:10 +08:00 committed by GitHub
parent 27d094c590
commit 966ce4c2dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 90 additions and 80 deletions

View file

@ -365,19 +365,17 @@ impl Render for LocaleSelector {
.anchor(AnchorCorner::TopRight)
.trigger(Button::new("btn", cx).small().ghost().icon(IconName::Globe))
.content(move |cx| {
let focus_handle = focus_handle.clone();
PopupMenu::build(cx, |this, _cx| {
this.track_focus(focus_handle)
.menu_with_check(
"English",
locale == "en",
Box::new(SelectLocale("en".into())),
)
.menu_with_check(
"简体中文",
locale == "zh-CN",
Box::new(SelectLocale("zh-CN".into())),
)
this.menu_with_check(
"English",
locale == "en",
Box::new(SelectLocale("en".into())),
)
.menu_with_check(
"简体中文",
locale == "zh-CN",
Box::new(SelectLocale("zh-CN".into())),
)
})
}),
)

View file

@ -10,7 +10,7 @@ use ui::{
h_flex,
input::TextInput,
popover::{Popover, PopoverContent},
popup_menu::PopupMenu,
popup_menu::PopupMenuExt,
prelude::FluentBuilder,
switch::Switch,
v_flex, Clickable as _, IconName, Sizable,
@ -103,7 +103,6 @@ impl FocusableView for PopupStory {
impl Render for PopupStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let form = self.form.clone();
let focus_handle = self.focus_handle.clone();
let window_mode = self.window_mode;
v_flex()
@ -120,10 +119,8 @@ impl Render for PopupStory {
cx.focus(&this.focus_handle);
}))
.context_menu({
let focus_handle = focus_handle.clone();
move |menu, _cx| {
menu.track_focus(focus_handle.clone())
.menu("Cut", Box::new(Cut))
move |this, _cx| {
this.menu("Cut", Box::new(Cut))
.menu("Copy", Box::new(Copy))
.menu("Paste", Box::new(Paste))
.separator()
@ -133,7 +130,7 @@ impl Render for PopupStory {
.gap_6()
.child(
Switch::new("switch-window-mode")
.checked(self.window_mode)
.checked(window_mode)
.label("Use Window Popover")
.on_click(cx.listener(|this, checked, _| {
this.window_mode = *checked;
@ -146,7 +143,7 @@ impl Render for PopupStory {
.child(
v_flex().gap_4().child(
Popover::new("info-top-left")
.when(self.window_mode, |this| this.window_mode())
.when(window_mode, |this| this.window_mode())
.trigger(Button::new("info-top-left", cx).label("Top Left"))
.content(|cx| {
PopoverContent::new(cx, |cx| {
@ -167,7 +164,7 @@ impl Render for PopupStory {
)
.child(
Popover::new("info-top-right")
.when(self.window_mode, |this| this.window_mode())
.when(window_mode, |this| this.window_mode())
.anchor(AnchorCorner::TopRight)
.trigger(Button::new("info-top-right", cx).label("Top Right"))
.content(|cx| {
@ -192,25 +189,22 @@ impl Render for PopupStory {
h_flex()
.gap_3()
.child(
Popover::new("popup-menu")
.when(window_mode, |this| this.window_mode())
.trigger(Button::new("popup-menu-1", cx).icon(IconName::Ellipsis))
.content(move |cx| {
let focus_handle = focus_handle.clone();
PopupMenu::build(cx, |menu, _cx| {
menu.track_focus(focus_handle)
.menu("Copy", Box::new(Copy))
.menu("Cut", Box::new(Cut))
.menu("Paste", Box::new(Paste))
.separator()
.menu_with_icon(
IconName::Search,
"Search",
Box::new(SearchAll),
)
.separator()
.menu_with_check("Check Menu", true, Box::new(SearchAll))
})
Button::new("popup-menu-1", cx)
.icon(IconName::Ellipsis)
.popup_menu(|this, _| {
this.menu("Copy", Box::new(Copy))
.menu("Cut", Box::new(Cut))
.menu("Paste", Box::new(Paste))
.separator()
.menu_with_icon("Search", IconName::Search, Box::new(SearchAll))
.separator()
.menu_with_check("Check Menu", true, Box::new(SearchAll))
.separator()
.link_with_icon(
"GitHub Repository",
IconName::GitHub,
"https://github.com/huacnlee/gpui-component",
)
}),
)
.child(self.message.clone()),
@ -223,7 +217,7 @@ impl Render for PopupStory {
.justify_between()
.child(
Popover::new("info-bottom-left")
.when(self.window_mode, |this| this.window_mode())
.when(window_mode, |this| this.window_mode())
.anchor(AnchorCorner::BottomLeft)
.trigger(
Button::new("pop", cx).label("Popup with Form").w(px(300.)),
@ -232,7 +226,7 @@ impl Render for PopupStory {
)
.child(
Popover::new("info-bottom-right")
.when(self.window_mode, |this| this.window_mode())
.when(window_mode, |this| this.window_mode())
.anchor(AnchorCorner::BottomRight)
.mouse_button(MouseButton::Right)
.trigger(

View file

@ -1,14 +1,15 @@
use std::rc::Rc;
use gpui::FocusableView;
use gpui::{
actions, div, prelude::FluentBuilder, px, Action, AnyWindowHandle, AppContext, Context,
DismissEvent, EventEmitter, FocusHandle, FocusableView, InteractiveElement, KeyBinding,
ParentElement, Pixels, Render, SharedString, Styled as _, View, ViewContext,
VisualContext as _, WindowContext,
actions, div, prelude::FluentBuilder, px, Action, AppContext, DismissEvent, EventEmitter,
FocusHandle, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, Render,
SharedString, Styled as _, View, ViewContext, VisualContext as _, WindowContext,
};
use crate::{
h_flex, list::ListItem, styled_ext::Sizable as _, theme::ActiveTheme, v_flex, Icon, IconName,
button::Button, h_flex, list::ListItem, popover::Popover, styled_ext::Sizable as _,
theme::ActiveTheme, v_flex, Icon, IconName, Selectable,
};
actions!(menu, [Confirm, Dismiss, SelectNext, SelectPrev]);
@ -23,12 +24,24 @@ pub fn init(cx: &mut AppContext) {
]);
}
pub trait PopupMenuExt: Selectable + IntoElement + 'static {
fn popup_menu(
self,
f: impl Fn(PopupMenu, &mut WindowContext) -> PopupMenu + 'static,
) -> Popover<PopupMenu> {
Popover::new("popup-menu")
.trigger(self)
.content(move |cx| PopupMenu::build(cx, |menu, cx| f(menu, cx)))
}
}
impl PopupMenuExt for Button {}
enum PopupMenuItem {
Separator,
Item {
icon: Option<Icon>,
label: SharedString,
handler: Rc<dyn Fn(Option<&FocusHandle>, &mut WindowContext)>,
handler: Rc<dyn Fn(&mut WindowContext)>,
},
}
@ -44,9 +57,6 @@ impl PopupMenuItem {
pub struct PopupMenu {
focus_handle: FocusHandle,
/// The parent window handle
window_handle: AnyWindowHandle,
action_context: Option<FocusHandle>,
menu_items: Vec<PopupMenuItem>,
has_icon: bool,
selected_index: Option<usize>,
@ -68,8 +78,6 @@ impl PopupMenu {
let menu = Self {
focus_handle,
action_context: None,
window_handle: cx.window_handle(),
menu_items: Vec::new(),
selected_index: None,
min_width: px(120.),
@ -94,28 +102,46 @@ impl PopupMenu {
self
}
/// You must set content (FocusHandle) with the parent view, if the menu action is listening on the parent view.
/// When the Menu Item confirmed, the parent view will be focused again to ensure to receive the action.
#[must_use]
pub fn track_focus(mut self, focus_handle: FocusHandle) -> Self {
self.action_context = Some(focus_handle);
self
}
/// Add Menu Item
pub fn menu(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
self.add_menu_item(None, label, action);
self.add_menu_item(label, None, action);
self
}
/// Add Menu to open link
pub fn link(mut self, label: impl Into<SharedString>, href: impl Into<String>) -> Self {
let href = href.into();
self.menu_items.push(PopupMenuItem::Item {
icon: None,
label: label.into(),
handler: Rc::new(move |cx| cx.open_url(&href)),
});
self
}
/// Add Menu to open link
pub fn link_with_icon(
mut self,
label: impl Into<SharedString>,
icon: impl Into<Icon>,
href: impl Into<String>,
) -> Self {
let href = href.into();
self.menu_items.push(PopupMenuItem::Item {
icon: Some(icon.into()),
label: label.into(),
handler: Rc::new(move |cx| cx.open_url(&href)),
});
self
}
/// Add Menu Item with Icon
pub fn menu_with_icon(
mut self,
icon: impl Into<Icon>,
label: impl Into<SharedString>,
icon: impl Into<Icon>,
action: Box<dyn Action>,
) -> Self {
self.add_menu_item(Some(icon.into()), label, action);
self.add_menu_item(label, Some(icon.into()), action);
self
}
@ -127,9 +153,9 @@ impl PopupMenu {
action: Box<dyn Action>,
) -> Self {
if checked {
self.add_menu_item(Some(IconName::Check.into()), label, action);
self.add_menu_item(label, Some(IconName::Check.into()), action);
} else {
self.add_menu_item(None, label, action);
self.add_menu_item(label, None, action);
}
self
@ -137,11 +163,10 @@ impl PopupMenu {
fn add_menu_item(
&mut self,
icon: Option<Icon>,
label: impl Into<SharedString>,
icon: Option<Icon>,
action: Box<dyn Action>,
) -> &mut Self {
let window_handle = self.window_handle;
if icon.is_some() {
self.has_icon = true;
}
@ -149,15 +174,9 @@ impl PopupMenu {
self.menu_items.push(PopupMenuItem::Item {
icon,
label: label.into(),
handler: Rc::new(move |handle, cx| {
if let Some(handle) = handle {
cx.update_window(window_handle, |_, cx| {
cx.activate_window();
cx.focus(handle);
cx.dispatch_action(action.boxed_clone());
})
.unwrap();
}
handler: Rc::new(move |cx| {
cx.activate_window();
cx.dispatch_action(action.boxed_clone());
}),
});
self
@ -184,13 +203,12 @@ impl PopupMenu {
}
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
let handle = self.action_context.as_ref();
match self.selected_index {
Some(index) => {
let item = self.menu_items.get(index);
match item {
Some(PopupMenuItem::Item { handler, .. }) => {
handler(handle, cx);
handler(cx);
self.dismiss(&Dismiss, cx)
}
_ => {}
@ -290,7 +308,7 @@ impl Render for PopupMenu {
})
.child(
div()
.when(has_icon, |this| this.pl(px(18.)))
.when(has_icon, |this| this.pl(px(19.)).pr_2())
.child(label.clone()),
),
)