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:
parent
27d094c590
commit
966ce4c2dd
3 changed files with 90 additions and 80 deletions
|
|
@ -365,19 +365,17 @@ impl Render for LocaleSelector {
|
||||||
.anchor(AnchorCorner::TopRight)
|
.anchor(AnchorCorner::TopRight)
|
||||||
.trigger(Button::new("btn", cx).small().ghost().icon(IconName::Globe))
|
.trigger(Button::new("btn", cx).small().ghost().icon(IconName::Globe))
|
||||||
.content(move |cx| {
|
.content(move |cx| {
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
PopupMenu::build(cx, |this, _cx| {
|
PopupMenu::build(cx, |this, _cx| {
|
||||||
this.track_focus(focus_handle)
|
this.menu_with_check(
|
||||||
.menu_with_check(
|
"English",
|
||||||
"English",
|
locale == "en",
|
||||||
locale == "en",
|
Box::new(SelectLocale("en".into())),
|
||||||
Box::new(SelectLocale("en".into())),
|
)
|
||||||
)
|
.menu_with_check(
|
||||||
.menu_with_check(
|
"简体中文",
|
||||||
"简体中文",
|
locale == "zh-CN",
|
||||||
locale == "zh-CN",
|
Box::new(SelectLocale("zh-CN".into())),
|
||||||
Box::new(SelectLocale("zh-CN".into())),
|
)
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use ui::{
|
||||||
h_flex,
|
h_flex,
|
||||||
input::TextInput,
|
input::TextInput,
|
||||||
popover::{Popover, PopoverContent},
|
popover::{Popover, PopoverContent},
|
||||||
popup_menu::PopupMenu,
|
popup_menu::PopupMenuExt,
|
||||||
prelude::FluentBuilder,
|
prelude::FluentBuilder,
|
||||||
switch::Switch,
|
switch::Switch,
|
||||||
v_flex, Clickable as _, IconName, Sizable,
|
v_flex, Clickable as _, IconName, Sizable,
|
||||||
|
|
@ -103,7 +103,6 @@ impl FocusableView for PopupStory {
|
||||||
impl Render for PopupStory {
|
impl Render for PopupStory {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let form = self.form.clone();
|
let form = self.form.clone();
|
||||||
let focus_handle = self.focus_handle.clone();
|
|
||||||
let window_mode = self.window_mode;
|
let window_mode = self.window_mode;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|
@ -120,10 +119,8 @@ impl Render for PopupStory {
|
||||||
cx.focus(&this.focus_handle);
|
cx.focus(&this.focus_handle);
|
||||||
}))
|
}))
|
||||||
.context_menu({
|
.context_menu({
|
||||||
let focus_handle = focus_handle.clone();
|
move |this, _cx| {
|
||||||
move |menu, _cx| {
|
this.menu("Cut", Box::new(Cut))
|
||||||
menu.track_focus(focus_handle.clone())
|
|
||||||
.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()
|
||||||
|
|
@ -133,7 +130,7 @@ impl Render for PopupStory {
|
||||||
.gap_6()
|
.gap_6()
|
||||||
.child(
|
.child(
|
||||||
Switch::new("switch-window-mode")
|
Switch::new("switch-window-mode")
|
||||||
.checked(self.window_mode)
|
.checked(window_mode)
|
||||||
.label("Use Window Popover")
|
.label("Use Window Popover")
|
||||||
.on_click(cx.listener(|this, checked, _| {
|
.on_click(cx.listener(|this, checked, _| {
|
||||||
this.window_mode = *checked;
|
this.window_mode = *checked;
|
||||||
|
|
@ -146,7 +143,7 @@ impl Render for PopupStory {
|
||||||
.child(
|
.child(
|
||||||
v_flex().gap_4().child(
|
v_flex().gap_4().child(
|
||||||
Popover::new("info-top-left")
|
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"))
|
.trigger(Button::new("info-top-left", cx).label("Top Left"))
|
||||||
.content(|cx| {
|
.content(|cx| {
|
||||||
PopoverContent::new(cx, |cx| {
|
PopoverContent::new(cx, |cx| {
|
||||||
|
|
@ -167,7 +164,7 @@ impl Render for PopupStory {
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Popover::new("info-top-right")
|
Popover::new("info-top-right")
|
||||||
.when(self.window_mode, |this| this.window_mode())
|
.when(window_mode, |this| this.window_mode())
|
||||||
.anchor(AnchorCorner::TopRight)
|
.anchor(AnchorCorner::TopRight)
|
||||||
.trigger(Button::new("info-top-right", cx).label("Top Right"))
|
.trigger(Button::new("info-top-right", cx).label("Top Right"))
|
||||||
.content(|cx| {
|
.content(|cx| {
|
||||||
|
|
@ -192,25 +189,22 @@ impl Render for PopupStory {
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(
|
.child(
|
||||||
Popover::new("popup-menu")
|
Button::new("popup-menu-1", cx)
|
||||||
.when(window_mode, |this| this.window_mode())
|
.icon(IconName::Ellipsis)
|
||||||
.trigger(Button::new("popup-menu-1", cx).icon(IconName::Ellipsis))
|
.popup_menu(|this, _| {
|
||||||
.content(move |cx| {
|
this.menu("Copy", Box::new(Copy))
|
||||||
let focus_handle = focus_handle.clone();
|
.menu("Cut", Box::new(Cut))
|
||||||
PopupMenu::build(cx, |menu, _cx| {
|
.menu("Paste", Box::new(Paste))
|
||||||
menu.track_focus(focus_handle)
|
.separator()
|
||||||
.menu("Copy", Box::new(Copy))
|
.menu_with_icon("Search", IconName::Search, Box::new(SearchAll))
|
||||||
.menu("Cut", Box::new(Cut))
|
.separator()
|
||||||
.menu("Paste", Box::new(Paste))
|
.menu_with_check("Check Menu", true, Box::new(SearchAll))
|
||||||
.separator()
|
.separator()
|
||||||
.menu_with_icon(
|
.link_with_icon(
|
||||||
IconName::Search,
|
"GitHub Repository",
|
||||||
"Search",
|
IconName::GitHub,
|
||||||
Box::new(SearchAll),
|
"https://github.com/huacnlee/gpui-component",
|
||||||
)
|
)
|
||||||
.separator()
|
|
||||||
.menu_with_check("Check Menu", true, Box::new(SearchAll))
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(self.message.clone()),
|
.child(self.message.clone()),
|
||||||
|
|
@ -223,7 +217,7 @@ impl Render for PopupStory {
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(
|
.child(
|
||||||
Popover::new("info-bottom-left")
|
Popover::new("info-bottom-left")
|
||||||
.when(self.window_mode, |this| this.window_mode())
|
.when(window_mode, |this| this.window_mode())
|
||||||
.anchor(AnchorCorner::BottomLeft)
|
.anchor(AnchorCorner::BottomLeft)
|
||||||
.trigger(
|
.trigger(
|
||||||
Button::new("pop", cx).label("Popup with Form").w(px(300.)),
|
Button::new("pop", cx).label("Popup with Form").w(px(300.)),
|
||||||
|
|
@ -232,7 +226,7 @@ impl Render for PopupStory {
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Popover::new("info-bottom-right")
|
Popover::new("info-bottom-right")
|
||||||
.when(self.window_mode, |this| this.window_mode())
|
.when(window_mode, |this| this.window_mode())
|
||||||
.anchor(AnchorCorner::BottomRight)
|
.anchor(AnchorCorner::BottomRight)
|
||||||
.mouse_button(MouseButton::Right)
|
.mouse_button(MouseButton::Right)
|
||||||
.trigger(
|
.trigger(
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gpui::FocusableView;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, prelude::FluentBuilder, px, Action, AnyWindowHandle, AppContext, Context,
|
actions, div, prelude::FluentBuilder, px, Action, AppContext, DismissEvent, EventEmitter,
|
||||||
DismissEvent, EventEmitter, FocusHandle, FocusableView, InteractiveElement, KeyBinding,
|
FocusHandle, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, Render,
|
||||||
ParentElement, Pixels, Render, SharedString, Styled as _, View, ViewContext,
|
SharedString, Styled as _, View, ViewContext, VisualContext as _, WindowContext,
|
||||||
VisualContext as _, WindowContext,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
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]);
|
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 {
|
enum PopupMenuItem {
|
||||||
Separator,
|
Separator,
|
||||||
Item {
|
Item {
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
label: SharedString,
|
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 {
|
pub struct PopupMenu {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
/// The parent window handle
|
|
||||||
window_handle: AnyWindowHandle,
|
|
||||||
action_context: Option<FocusHandle>,
|
|
||||||
menu_items: Vec<PopupMenuItem>,
|
menu_items: Vec<PopupMenuItem>,
|
||||||
has_icon: bool,
|
has_icon: bool,
|
||||||
selected_index: Option<usize>,
|
selected_index: Option<usize>,
|
||||||
|
|
@ -68,8 +78,6 @@ impl PopupMenu {
|
||||||
|
|
||||||
let menu = Self {
|
let menu = Self {
|
||||||
focus_handle,
|
focus_handle,
|
||||||
action_context: None,
|
|
||||||
window_handle: cx.window_handle(),
|
|
||||||
menu_items: Vec::new(),
|
menu_items: Vec::new(),
|
||||||
selected_index: None,
|
selected_index: None,
|
||||||
min_width: px(120.),
|
min_width: px(120.),
|
||||||
|
|
@ -94,28 +102,46 @@ impl PopupMenu {
|
||||||
self
|
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
|
/// Add Menu Item
|
||||||
pub fn menu(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
|
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
|
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
|
/// Add Menu Item with Icon
|
||||||
pub fn menu_with_icon(
|
pub fn menu_with_icon(
|
||||||
mut self,
|
mut self,
|
||||||
icon: impl Into<Icon>,
|
|
||||||
label: impl Into<SharedString>,
|
label: impl Into<SharedString>,
|
||||||
|
icon: impl Into<Icon>,
|
||||||
action: Box<dyn Action>,
|
action: Box<dyn Action>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.add_menu_item(Some(icon.into()), label, action);
|
self.add_menu_item(label, Some(icon.into()), action);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,9 +153,9 @@ impl PopupMenu {
|
||||||
action: Box<dyn Action>,
|
action: Box<dyn Action>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
if checked {
|
if checked {
|
||||||
self.add_menu_item(Some(IconName::Check.into()), label, action);
|
self.add_menu_item(label, Some(IconName::Check.into()), action);
|
||||||
} else {
|
} else {
|
||||||
self.add_menu_item(None, label, action);
|
self.add_menu_item(label, None, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
|
@ -137,11 +163,10 @@ impl PopupMenu {
|
||||||
|
|
||||||
fn add_menu_item(
|
fn add_menu_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
icon: Option<Icon>,
|
|
||||||
label: impl Into<SharedString>,
|
label: impl Into<SharedString>,
|
||||||
|
icon: Option<Icon>,
|
||||||
action: Box<dyn Action>,
|
action: Box<dyn Action>,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
let window_handle = self.window_handle;
|
|
||||||
if icon.is_some() {
|
if icon.is_some() {
|
||||||
self.has_icon = true;
|
self.has_icon = true;
|
||||||
}
|
}
|
||||||
|
|
@ -149,15 +174,9 @@ impl PopupMenu {
|
||||||
self.menu_items.push(PopupMenuItem::Item {
|
self.menu_items.push(PopupMenuItem::Item {
|
||||||
icon,
|
icon,
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
handler: Rc::new(move |handle, cx| {
|
handler: Rc::new(move |cx| {
|
||||||
if let Some(handle) = handle {
|
cx.activate_window();
|
||||||
cx.update_window(window_handle, |_, cx| {
|
cx.dispatch_action(action.boxed_clone());
|
||||||
cx.activate_window();
|
|
||||||
cx.focus(handle);
|
|
||||||
cx.dispatch_action(action.boxed_clone());
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
|
|
@ -184,13 +203,12 @@ impl PopupMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||||
let handle = self.action_context.as_ref();
|
|
||||||
match self.selected_index {
|
match self.selected_index {
|
||||||
Some(index) => {
|
Some(index) => {
|
||||||
let item = self.menu_items.get(index);
|
let item = self.menu_items.get(index);
|
||||||
match item {
|
match item {
|
||||||
Some(PopupMenuItem::Item { handler, .. }) => {
|
Some(PopupMenuItem::Item { handler, .. }) => {
|
||||||
handler(handle, cx);
|
handler(cx);
|
||||||
self.dismiss(&Dismiss, cx)
|
self.dismiss(&Dismiss, cx)
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -290,7 +308,7 @@ impl Render for PopupMenu {
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.when(has_icon, |this| this.pl(px(18.)))
|
.when(has_icon, |this| this.pl(px(19.)).pr_2())
|
||||||
.child(label.clone()),
|
.child(label.clone()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue