ui: Extend KeyBinding with support for displaying keybindings for other platforms (#9192)

This PR extends the `KeyBinding` component with support for displaying
keybindings for platforms other than macOS.

<img width="824" alt="Screenshot 2024-03-11 at 2 41 59 PM"
src="https://github.com/zed-industries/zed/assets/1486634/7108b17d-dfc3-42ee-9bfd-c58b334d7374">

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-03-11 15:03:55 -04:00 committed by GitHub
parent dfcc143ead
commit f2aa183512
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 133 additions and 63 deletions

View file

@ -1,6 +1,30 @@
use crate::{h_flex, prelude::*, Icon, IconName, IconSize}; use crate::{h_flex, prelude::*, Icon, IconName, IconSize};
use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke}; 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)] #[derive(IntoElement, Clone)]
pub struct KeyBinding { pub struct KeyBinding {
/// A keybinding consists of a key and a set of modifier keys. /// 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. /// This should always contain at least one element.
key_binding: gpui::KeyBinding, key_binding: gpui::KeyBinding,
}
impl RenderOnce for KeyBinding { /// How keybindings should be displayed.
fn render(self, cx: &mut WindowContext) -> impl IntoElement { display: KeyBindingDisplay,
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()))
})
}))
}
} }
impl KeyBinding { impl KeyBinding {
@ -82,7 +74,67 @@ impl KeyBinding {
} }
pub fn new(key_binding: gpui::KeyBinding) -> Self { 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())),
})
}))
} }
} }

View file

@ -1,10 +1,9 @@
use gpui::NoAction; use gpui::NoAction;
use gpui::Render; use gpui::Render;
use itertools::Itertools; use itertools::Itertools;
use story::Story; use story::{Story, StoryContainer};
use crate::prelude::*; use crate::{prelude::*, KeyBinding, KeyBindingDisplay};
use crate::KeyBinding;
pub struct KeybindingStory; pub struct KeybindingStory;
@ -16,23 +15,28 @@ impl Render for KeybindingStory {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2); let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
Story::container() StoryContainer::new(
.child(Story::title_for::<KeyBinding>()) "KeyBinding",
.child(Story::label("Single Key")) "crates/ui/src/components/stories/keybinding.rs",
.child(KeyBinding::new(binding("Z"))) )
.child(Story::label("Single Key with Modifier")) .child(Story::label("Single Key"))
.child( .child(KeyBinding::new(binding("Z")))
div() .child(Story::label("Single Key with Modifier"))
.flex() .child(
.gap_3() div()
.child(KeyBinding::new(binding("ctrl-c"))) .flex()
.child(KeyBinding::new(binding("alt-c"))) .gap_3()
.child(KeyBinding::new(binding("cmd-c"))) .child(KeyBinding::new(binding("ctrl-c")))
.child(KeyBinding::new(binding("shift-c"))), .child(KeyBinding::new(binding("alt-c")))
) .child(KeyBinding::new(binding("cmd-c")))
.child(Story::label("Single Key with Modifier (Permuted)")) .child(KeyBinding::new(binding("shift-c"))),
.child( )
div().flex().flex_col().children( .child(Story::label("Single Key with Modifier (Permuted)"))
.child(
div()
.flex()
.flex_col()
.children(
all_modifier_permutations all_modifier_permutations
.chunks(4) .chunks(4)
.into_iter() .into_iter()
@ -46,13 +50,27 @@ impl Render for KeybindingStory {
})) }))
}), }),
), ),
) )
.child(Story::label("Single Key with All Modifiers")) .child(Story::label("Single Key with All Modifiers"))
.child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z"))) .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
.child(Story::label("Chord")) .child(Story::label("Chord"))
.child(KeyBinding::new(binding("a z"))) .child(KeyBinding::new(binding("a z")))
.child(Story::label("Chord with Modifier")) .child(Story::label("Chord with Modifier"))
.child(KeyBinding::new(binding("ctrl-a shift-z"))) .child(KeyBinding::new(binding("ctrl-a shift-z")))
.child(KeyBinding::new(binding("fn-s"))) .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))
} }
} }