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:
parent
46ff43ff36
commit
fd837565c4
4 changed files with 106 additions and 36 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue