Add KeyBinding to MenuItem. (#164)

<img width="370" alt="image"
src="https://github.com/user-attachments/assets/a0e98260-75ac-4a79-bf24-80d2c82f9507">
This commit is contained in:
Jason Lee 2024-08-16 18:57:43 +08:00 committed by GitHub
parent 46ff43ff36
commit fd837565c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 106 additions and 36 deletions

View file

@ -51,6 +51,7 @@ use ui::{divider::Divider, h_flex, label::Label, v_flex};
pub fn init(cx: &mut AppContext) {
input_story::init(cx);
dropdown_story::init(cx);
popup_story::init(cx);
}
pub fn section(title: impl IntoElement, cx: &WindowContext) -> Div {

View file

@ -1,6 +1,6 @@
use gpui::{
actions, div, px, AnchorCorner, AppContext, DismissEvent, Element, EventEmitter, FocusHandle,
FocusableView, InteractiveElement, IntoElement, MouseButton, MouseDownEvent,
FocusableView, InteractiveElement, IntoElement, KeyBinding, MouseButton, MouseDownEvent,
ParentElement as _, Render, Styled as _, View, ViewContext, VisualContext, WindowContext,
};
use ui::{
@ -21,6 +21,15 @@ actions!(
[Copy, Paste, Cut, SearchAll, ToggleWindowMode]
);
pub fn init(cx: &mut AppContext) {
cx.bind_keys([
KeyBinding::new("cmd-c", Copy, None),
KeyBinding::new("cmd-v", Paste, None),
KeyBinding::new("cmd-x", Cut, None),
KeyBinding::new("cmd-shift-f", SearchAll, None),
])
}
struct Form {
input1: View<TextInput>,
}
@ -80,17 +89,25 @@ impl PopupStory {
}
}
fn on_copy(&mut self, _: &Copy, _: &mut ViewContext<Self>) {
fn on_copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
self.message = "You have clicked copy".to_string();
cx.notify()
}
fn on_cut(&mut self, _: &Cut, _: &mut ViewContext<Self>) {
fn on_cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
self.message = "You have clicked cut".to_string();
cx.notify()
}
fn on_paste(&mut self, _: &Paste, _: &mut ViewContext<Self>) {
fn on_paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
self.message = "You have clicked paste".to_string();
cx.notify()
}
fn on_search_all(&mut self, _: &SearchAll, _: &mut ViewContext<Self>) {
fn on_search_all(&mut self, _: &SearchAll, cx: &mut ViewContext<Self>) {
self.message = "You have clicked search all".to_string();
cx.notify()
}
fn on_toggle_window_mode(&mut self, _: &ToggleWindowMode, cx: &mut ViewContext<Self>) {
self.window_mode = !self.window_mode;
cx.notify()
}
}
@ -111,6 +128,7 @@ impl Render for PopupStory {
.on_action(cx.listener(Self::on_cut))
.on_action(cx.listener(Self::on_paste))
.on_action(cx.listener(Self::on_search_all))
.on_action(cx.listener(Self::on_toggle_window_mode))
.p_4()
.mb_5()
.size_full()
@ -191,14 +209,18 @@ impl Render for PopupStory {
.child(
Button::new("popup-menu-1", cx)
.icon(IconName::Ellipsis)
.popup_menu(|this, _| {
.popup_menu(move |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))
.menu_with_check(
"Window Mode",
window_mode,
Box::new(ToggleWindowMode),
)
.separator()
.link_with_icon(
"GitHub Repository",

View file

@ -1,11 +1,12 @@
use std::ops::Deref;
use std::rc::Rc;
use gpui::FocusableView;
use gpui::{
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 gpui::{rems, FocusableView};
use crate::{
button::Button, h_flex, list::ListItem, popover::Popover, theme::ActiveTheme, v_flex, Icon,
@ -41,6 +42,7 @@ enum PopupMenuItem {
Item {
icon: Option<Icon>,
label: SharedString,
action: Option<Box<dyn Action>>,
handler: Rc<dyn Fn(&mut WindowContext)>,
},
}
@ -114,6 +116,7 @@ impl PopupMenu {
self.menu_items.push(PopupMenuItem::Item {
icon: None,
label: label.into(),
action: None,
handler: Rc::new(move |cx| cx.open_url(&href)),
});
self
@ -130,6 +133,7 @@ impl PopupMenu {
self.menu_items.push(PopupMenuItem::Item {
icon: Some(icon.into()),
label: label.into(),
action: None,
handler: Rc::new(move |cx| cx.open_url(&href)),
});
self
@ -174,6 +178,7 @@ impl PopupMenu {
self.menu_items.push(PopupMenuItem::Item {
icon,
label: label.into(),
action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| {
cx.activate_window();
cx.dispatch_action(action.boxed_clone());
@ -246,6 +251,26 @@ impl PopupMenu {
fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent);
}
fn render_keybinding(
action: Option<Box<dyn Action>>,
cx: &ViewContext<Self>,
) -> Option<impl IntoElement> {
if let Some(action) = action {
if let Some(keybinding) = cx.bindings_for_action(action.deref()).first() {
let el = div().text_color(cx.theme().muted_foreground).children(
keybinding
.keystrokes()
.into_iter()
.map(|keystroke| format!("{}", keystroke)),
);
return Some(el);
}
}
return None;
}
}
impl FluentBuilder for PopupMenu {}
@ -278,7 +303,8 @@ impl Render for PopupMenu {
.min_w(self.min_width)
.p_1()
.gap_y_0p5()
.bg(cx.theme().menu)
.min_w(rems(8.))
.text_color(cx.theme().popover_foreground)
.children(self.menu_items.iter_mut().enumerate().map(|(ix, item)| {
let this = ListItem::new(("menu-item", ix))
.p_0()
@ -287,31 +313,57 @@ impl Render for PopupMenu {
PopupMenuItem::Separator => this.disabled(true).child(
div()
.h(px(1.))
.w_full()
.mx_neg_1()
.my_px()
.border_0()
.bg(cx.theme().border),
.bg(cx.theme().muted),
),
PopupMenuItem::Item { icon, label, .. } => {
this.py(px(2.)).px_2().rounded_md().text_sm().child(
h_flex()
.size_full()
.items_center()
.map(|this| {
this.child(div().absolute().text_sm().map(|this| {
if let Some(icon) = icon {
this.child(icon.clone().small().clone())
} else {
this.children(icon_placeholder.clone())
}
}))
})
.child(
div()
.when(has_icon, |this| this.pl(px(19.)).pr_2())
.child(label.clone()),
),
)
PopupMenuItem::Item {
icon,
label,
action,
..
} => {
let action = action.as_ref().map(|action| action.boxed_clone());
let key = Self::render_keybinding(action, cx);
this.relative()
.py_1p5()
.px_2()
.rounded_md()
.text_sm()
.line_height(rems(1.25))
.items_center()
.child(
h_flex()
.items_center()
.gap_x_1p5()
.when(has_icon, |this| {
this.child(
h_flex()
.w_3p5()
.h_3p5()
.items_center()
.justify_center()
.text_sm()
.map(|this| {
if let Some(icon) = icon {
this.child(icon.clone().small().clone())
} else {
this.children(icon_placeholder.clone())
}
}),
)
})
.child(
h_flex()
.flex_1()
.items_center()
.justify_between()
.child(label.clone())
.children(key),
),
)
}
}
}))

View file

@ -162,7 +162,6 @@ struct Colors {
pub list_active: Hsla,
pub list_head: Hsla,
pub link: Hsla,
pub menu: Hsla,
}
impl Colors {
@ -204,7 +203,6 @@ impl Colors {
list_active: hsl(240.0, 7., 88.0).opacity(0.75),
list_head: hsl(0.0, 0.0, 100.),
link: hsl(221.0, 83.0, 53.0),
menu: hsl(0.0, 0.0, 97.0),
}
}
@ -246,7 +244,6 @@ impl Colors {
list_active: hsl(240.0, 3.7, 15.0),
list_head: hsl(0.0, 0.0, 6.0),
link: hsl(221.0, 83.0, 53.0),
menu: hsl(300.0, 2.0, 12.),
}
}
}
@ -312,7 +309,6 @@ pub struct Theme {
pub link: Hsla,
pub link_hover: Hsla,
pub link_active: Hsla,
pub menu: Hsla,
pub skeleton: Hsla,
}
@ -391,7 +387,6 @@ impl From<Colors> for Theme {
link: colors.link,
link_hover: colors.link.lighten(0.2),
link_active: colors.link.darken(0.2),
menu: colors.menu,
skeleton: hsla(colors.primary.h, colors.primary.s, colors.primary.l, 0.1),
}
}