diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index 65d145ebd0..b5ff8783de 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -1,6 +1,30 @@ use crate::{h_flex, prelude::*, Icon, IconName, IconSize}; use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke}; +/// The way a [`KeyBinding`] should be displayed. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub enum KeyBindingDisplay { + /// Display in macOS style. + Mac, + /// Display in Linux style. + Linux, + /// Display in Windows style. + Windows, +} + +impl KeyBindingDisplay { + /// Returns the [`KeyBindingDisplay`] for the current platform. + pub const fn platform() -> Self { + if cfg!(target_os = "linux") { + KeyBindingDisplay::Linux + } else if cfg!(target_os = "windows") { + KeyBindingDisplay::Windows + } else { + KeyBindingDisplay::Mac + } + } +} + #[derive(IntoElement, Clone)] pub struct KeyBinding { /// A keybinding consists of a key and a set of modifier keys. @@ -8,41 +32,9 @@ pub struct KeyBinding { /// /// This should always contain at least one element. key_binding: gpui::KeyBinding, -} -impl RenderOnce for KeyBinding { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_flex() - .flex_none() - .gap_2() - .children(self.key_binding.keystrokes().iter().map(|keystroke| { - let key_icon = Self::icon_for_key(keystroke); - - h_flex() - .flex_none() - .gap_0p5() - .p_0p5() - .rounded_sm() - .text_color(cx.theme().colors().text_muted) - .when(keystroke.modifiers.function, |el| el.child(Key::new("fn"))) - .when(keystroke.modifiers.control, |el| { - el.child(KeyIcon::new(IconName::Control)) - }) - .when(keystroke.modifiers.alt, |el| { - el.child(KeyIcon::new(IconName::Option)) - }) - .when(keystroke.modifiers.command, |el| { - el.child(KeyIcon::new(IconName::Command)) - }) - .when(keystroke.modifiers.shift, |el| { - el.child(KeyIcon::new(IconName::Shift)) - }) - .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon))) - .when(key_icon.is_none(), |el| { - el.child(Key::new(keystroke.key.to_uppercase().clone())) - }) - })) - } + /// How keybindings should be displayed. + display: KeyBindingDisplay, } impl KeyBinding { @@ -82,7 +74,67 @@ impl KeyBinding { } pub fn new(key_binding: gpui::KeyBinding) -> Self { - Self { key_binding } + Self { + key_binding, + display: KeyBindingDisplay::platform(), + } + } + + /// Sets how this [`KeyBinding`] should be displayed. + pub fn display(mut self, display: KeyBindingDisplay) -> Self { + self.display = display; + self + } +} + +impl RenderOnce for KeyBinding { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + h_flex() + .flex_none() + .gap_2() + .children(self.key_binding.keystrokes().iter().map(|keystroke| { + let key_icon = Self::icon_for_key(keystroke); + + h_flex() + .flex_none() + .gap_0p5() + .p_0p5() + .rounded_sm() + .text_color(cx.theme().colors().text_muted) + .when(keystroke.modifiers.function, |el| match self.display { + KeyBindingDisplay::Mac => el.child(Key::new("fn")), + KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => { + el.child(Key::new("Fn")) + } + }) + .when(keystroke.modifiers.control, |el| match self.display { + KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Control)), + KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => { + el.child(Key::new("Ctrl")) + } + }) + .when(keystroke.modifiers.alt, |el| match self.display { + KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Option)), + KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => { + el.child(Key::new("Alt")) + } + }) + .when(keystroke.modifiers.command, |el| match self.display { + KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Command)), + KeyBindingDisplay::Linux => el.child(Key::new("Super")), + KeyBindingDisplay::Windows => el.child(Key::new("Win")), + }) + .when(keystroke.modifiers.shift, |el| match self.display { + KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Option)), + KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => { + el.child(Key::new("Shift")) + } + }) + .map(|el| match key_icon { + Some(icon) => el.child(KeyIcon::new(icon)), + None => el.child(Key::new(keystroke.key.to_uppercase())), + }) + })) } } diff --git a/crates/ui/src/components/stories/keybinding.rs b/crates/ui/src/components/stories/keybinding.rs index f7a9a86cd0..48be6ad82e 100644 --- a/crates/ui/src/components/stories/keybinding.rs +++ b/crates/ui/src/components/stories/keybinding.rs @@ -1,10 +1,9 @@ use gpui::NoAction; use gpui::Render; use itertools::Itertools; -use story::Story; +use story::{Story, StoryContainer}; -use crate::prelude::*; -use crate::KeyBinding; +use crate::{prelude::*, KeyBinding, KeyBindingDisplay}; pub struct KeybindingStory; @@ -16,23 +15,28 @@ impl Render for KeybindingStory { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2); - Story::container() - .child(Story::title_for::()) - .child(Story::label("Single Key")) - .child(KeyBinding::new(binding("Z"))) - .child(Story::label("Single Key with Modifier")) - .child( - div() - .flex() - .gap_3() - .child(KeyBinding::new(binding("ctrl-c"))) - .child(KeyBinding::new(binding("alt-c"))) - .child(KeyBinding::new(binding("cmd-c"))) - .child(KeyBinding::new(binding("shift-c"))), - ) - .child(Story::label("Single Key with Modifier (Permuted)")) - .child( - div().flex().flex_col().children( + StoryContainer::new( + "KeyBinding", + "crates/ui/src/components/stories/keybinding.rs", + ) + .child(Story::label("Single Key")) + .child(KeyBinding::new(binding("Z"))) + .child(Story::label("Single Key with Modifier")) + .child( + div() + .flex() + .gap_3() + .child(KeyBinding::new(binding("ctrl-c"))) + .child(KeyBinding::new(binding("alt-c"))) + .child(KeyBinding::new(binding("cmd-c"))) + .child(KeyBinding::new(binding("shift-c"))), + ) + .child(Story::label("Single Key with Modifier (Permuted)")) + .child( + div() + .flex() + .flex_col() + .children( all_modifier_permutations .chunks(4) .into_iter() @@ -46,13 +50,27 @@ impl Render for KeybindingStory { })) }), ), - ) - .child(Story::label("Single Key with All Modifiers")) - .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z"))) - .child(Story::label("Chord")) - .child(KeyBinding::new(binding("a z"))) - .child(Story::label("Chord with Modifier")) - .child(KeyBinding::new(binding("ctrl-a shift-z"))) - .child(KeyBinding::new(binding("fn-s"))) + ) + .child(Story::label("Single Key with All Modifiers")) + .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z"))) + .child(Story::label("Chord")) + .child(KeyBinding::new(binding("a z"))) + .child(Story::label("Chord with Modifier")) + .child(KeyBinding::new(binding("ctrl-a shift-z"))) + .child(KeyBinding::new(binding("fn-s"))) + .child(Story::label("Single Key with All Modifiers (Linux)")) + .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")).display(KeyBindingDisplay::Linux)) + .child(Story::label("Chord (Linux)")) + .child(KeyBinding::new(binding("a z")).display(KeyBindingDisplay::Linux)) + .child(Story::label("Chord with Modifier (Linux)")) + .child(KeyBinding::new(binding("ctrl-a shift-z")).display(KeyBindingDisplay::Linux)) + .child(KeyBinding::new(binding("fn-s")).display(KeyBindingDisplay::Linux)) + .child(Story::label("Single Key with All Modifiers (Windows)")) + .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")).display(KeyBindingDisplay::Windows)) + .child(Story::label("Chord (Windows)")) + .child(KeyBinding::new(binding("a z")).display(KeyBindingDisplay::Windows)) + .child(Story::label("Chord with Modifier (Windows)")) + .child(KeyBinding::new(binding("ctrl-a shift-z")).display(KeyBindingDisplay::Windows)) + .child(KeyBinding::new(binding("fn-s")).display(KeyBindingDisplay::Windows)) } }