diff --git a/crates/story/src/text_story.rs b/crates/story/src/text_story.rs index cb3f4fd2..3fae5868 100644 --- a/crates/story/src/text_story.rs +++ b/crates/story/src/text_story.rs @@ -1,6 +1,6 @@ use gpui::{ - div, px, rems, App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, - SharedString, Styled, Window, + div, px, rems, App, AppContext, Context, Entity, Focusable, IntoElement, Keystroke, + ParentElement, Render, SharedString, Styled, Window, }; use gpui_component::{ @@ -11,7 +11,7 @@ use gpui_component::{ link::Link, red_500, tag::Tag, - v_flex, yellow_500, yellow_800, IconName, Sizable, StyledExt, + v_flex, yellow_500, yellow_800, IconName, Kbd, Sizable, StyledExt, }; use crate::section; @@ -218,5 +218,16 @@ impl Render for TextStory { ), ), ) + .child( + section("Kbd", cx).child( + h_flex() + .gap_2() + .child(Kbd::new(Keystroke::parse("cmd-shift-p").unwrap())) + .child(Kbd::new(Keystroke::parse("cmd-ctrl-t").unwrap())) + .child(Kbd::new(Keystroke::parse("escape").unwrap())) + .child(Kbd::new(Keystroke::parse("backspace").unwrap())) + .child(Kbd::new(Keystroke::parse("enter").unwrap())), + ), + ) } } diff --git a/crates/ui/src/kbd.rs b/crates/ui/src/kbd.rs new file mode 100644 index 00000000..7ad21d1c --- /dev/null +++ b/crates/ui/src/kbd.rs @@ -0,0 +1,108 @@ +use gpui::{div, relative, IntoElement, Keystroke, ParentElement as _, RenderOnce, Styled as _}; + +use crate::ActiveTheme; + +/// A key binding tag +#[derive(IntoElement)] +pub struct Kbd { + stroke: gpui::Keystroke, +} + +impl From for Kbd { + fn from(stroke: Keystroke) -> Self { + Self { stroke } + } +} + +impl Kbd { + pub fn new(stroke: Keystroke) -> Self { + Self { stroke } + } + + /// Return the Platform specific keybinding string by KeyStroke + pub fn format(key: &Keystroke) -> String { + if cfg!(target_os = "macos") { + return format!("{}", key); + } + + let mut parts = vec![]; + if key.modifiers.control { + parts.push("Ctrl"); + } + if key.modifiers.alt { + parts.push("Alt"); + } + if key.modifiers.platform { + parts.push("Win"); + } + if key.modifiers.shift { + parts.push("Shift"); + } + + // Capitalize the first letter + let key = if let Some(first_c) = key.key.chars().next() { + format!("{}{}", first_c.to_uppercase(), &key.key[1..]) + } else { + key.key.to_string() + }; + + parts.push(&key); + parts.join("+") + } +} + +impl RenderOnce for Kbd { + fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement { + div() + .border_1() + .border_color(cx.theme().border) + .text_color(cx.theme().muted_foreground) + .py_0p5() + .px_1() + .rounded_md() + .line_height(relative(1.)) + .text_xs() + .child(Self::format(&self.stroke)) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_format() { + use super::Kbd; + use gpui::Keystroke; + + if cfg!(target_os = "windows") { + assert_eq!(Kbd::format(&Keystroke::parse("a").unwrap()), "A"); + assert_eq!(Kbd::format(&Keystroke::parse("ctrl-a").unwrap()), "Ctrl+A"); + assert_eq!( + Kbd::format(&Keystroke::parse("ctrl-alt-a").unwrap()), + "Ctrl+Alt+A" + ); + assert_eq!( + Kbd::format(&Keystroke::parse("ctrl-alt-shift-a").unwrap()), + "Ctrl+Alt+Shift+A" + ); + assert_eq!( + Kbd::format(&Keystroke::parse("ctrl-alt-shift-win-a").unwrap()), + "Ctrl+Alt+Win+Shift+A" + ); + assert_eq!( + Kbd::format(&Keystroke::parse("ctrl-shift-backspace").unwrap()), + "Ctrl+Shift+Backspace" + ); + } else { + assert_eq!(Kbd::format(&Keystroke::parse("cmd-a").unwrap()), "⌘A"); + assert_eq!(Kbd::format(&Keystroke::parse("cmd-ctrl-a").unwrap()), "^⌘A"); + assert_eq!( + Kbd::format(&Keystroke::parse("cmd-ctrl-shift-a").unwrap()), + "^⌘⇧A" + ); + assert_eq!( + Kbd::format(&Keystroke::parse("cmd-ctrl-shift-alt-a").unwrap()), + "^⌥⌘⇧A" + ); + } + } +} diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 70964eaf..84320c67 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -2,6 +2,7 @@ mod colors; mod event; mod focusable; mod icon; +mod kbd; mod root; mod styled; mod svg_img; @@ -70,6 +71,7 @@ pub use window_border::{window_border, window_paddings, WindowBorder}; pub use colors::*; pub use icon::*; +pub use kbd::*; pub use svg_img::*; pub use theme::*; diff --git a/crates/ui/src/popup_menu.rs b/crates/ui/src/popup_menu.rs index 14be7861..fa67b6e8 100644 --- a/crates/ui/src/popup_menu.rs +++ b/crates/ui/src/popup_menu.rs @@ -1,15 +1,15 @@ use crate::scroll::{Scrollbar, ScrollbarState}; -use crate::StyledExt; use crate::{ button::Button, h_flex, list::ListItem, popover::Popover, v_flex, ActiveTheme, Icon, IconName, Selectable, Sizable as _, }; +use crate::{Kbd, StyledExt}; use gpui::Subscription; use gpui::{ actions, anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement, App, AppContext, Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, - Focusable, InteractiveElement, IntoElement, KeyBinding, Keystroke, ParentElement, Pixels, - Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window, + Focusable, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, Render, + ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window, }; use std::cell::Cell; use std::ops::Deref; @@ -441,7 +441,7 @@ impl PopupMenu { keybinding .keystrokes() .into_iter() - .map(|key| key_shortcut(key.clone())), + .map(|key| Kbd::format(key)), ); return Some(el); @@ -710,64 +710,3 @@ impl Render for PopupMenu { }) } } - -/// Return the Platform specific keybinding string by KeyStroke -pub fn key_shortcut(key: Keystroke) -> String { - if cfg!(target_os = "macos") { - return format!("{}", key); - } - - let mut parts = vec![]; - if key.modifiers.control { - parts.push("Ctrl"); - } - if key.modifiers.alt { - parts.push("Alt"); - } - if key.modifiers.platform { - parts.push("Win"); - } - if key.modifiers.shift { - parts.push("Shift"); - } - - // Capitalize the first letter - let key = if let Some(first_c) = key.key.chars().next() { - format!("{}{}", first_c.to_uppercase(), &key.key[1..]) - } else { - key.key.to_string() - }; - - parts.push(&key); - parts.join("+") -} - -#[cfg(test)] -mod tests { - #[test] - fn test_key_shortcut() { - use super::key_shortcut; - use gpui::Keystroke; - - if cfg!(target_os = "windows") { - assert_eq!(key_shortcut(Keystroke::parse("a").unwrap()), "A"); - assert_eq!(key_shortcut(Keystroke::parse("ctrl-a").unwrap()), "Ctrl+A"); - assert_eq!( - key_shortcut(Keystroke::parse("ctrl-alt-a").unwrap()), - "Ctrl+Alt+A" - ); - assert_eq!( - key_shortcut(Keystroke::parse("ctrl-alt-shift-a").unwrap()), - "Ctrl+Alt+Shift+A" - ); - assert_eq!( - key_shortcut(Keystroke::parse("ctrl-alt-shift-win-a").unwrap()), - "Ctrl+Alt+Win+Shift+A" - ); - assert_eq!( - key_shortcut(Keystroke::parse("ctrl-shift-backspace").unwrap()), - "Ctrl+Shift+Backspace" - ); - } - } -}