Refine keybindings (#3368)

[[PR Description]]

Refine the `Keybinding` component.

Still some issues:

![CleanShot 2023-11-28 at 15 15
38@2x](https://github.com/zed-industries/zed/assets/1714999/4fc1dde4-fe65-4e1d-acf5-6faefa12f053)

Lots of things moving so want to get this in.

Changes:

- use icons for some keys & modifiers
- updates some icons
- updates some state colors

Release Notes:

- N/A

Zed 2 Release Notes:

- Keybindings now use icons for common keys and modifiers
This commit is contained in:
Nate Butler 2023-11-28 15:22:38 -05:00 committed by GitHub
commit d8fd422cf4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 132 additions and 38 deletions

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00001 12L3.5 7.50001M8.00001 12L12.5 7.50001M8.00001 12L8.00001 3.00001" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 272 B

View file

@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.125 6.99344L6.35938 3.63281M3.125 6.99344L6.35938 10.3672M3.125 6.99344H11" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 7.50001L8 3M3.5 7.50001L8 12M3.5 7.50001H12.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 275 B

After

Width:  |  Height:  |  Size: 248 B

View file

@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8906 7.00125L7.64062 3.64062M10.8906 7.00125L7.64062 10.375M10.8906 7.00125H3" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 7.5L8 12M12.5 7.5L8 3M12.5 7.5L3.5 7.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 242 B

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99999 3.00001L12.5 7.50001M7.99999 3.00001L3.49999 7.50001M7.99999 3.00001L7.99999 12" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 286 B

3
assets/icons/command.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 3.625C11 2.86561 11.6156 2.25 12.375 2.25C13.1344 2.25 13.75 2.86561 13.75 3.625C13.75 4.38401 13.135 4.99939 12.3761 5C12.3758 5 12.3754 5 12.375 5H11V3.625ZM9.75 5V3.625C9.75 2.17525 10.9253 1 12.375 1C13.8247 1 15 2.17525 15 3.625C15 4.98872 13.9601 6.10955 12.63 6.23777V6.25H12.3766C12.376 6.25 12.3755 6.25 12.375 6.25H11V9.75H12.375C13.8247 9.75 15 10.9253 15 12.375C15 13.8247 13.8247 15 12.375 15C11.0113 15 9.89045 13.9601 9.76223 12.63H9.75V12.3773L9.75 12.375V11H6.25V12.375C6.25 13.8247 5.07475 15 3.625 15C2.17525 15 1 13.8247 1 12.375C1 11.0113 2.03991 9.89045 3.37 9.76223V9.75H3.62274L3.625 9.75H5L5 6.25H3.625C2.17525 6.25 1 5.07475 1 3.625C1 2.17525 2.17525 1 3.625 1C4.98872 1 6.10955 2.03991 6.23777 3.37H6.25L6.25 5L9.75 5ZM9.75 6.25L6.25 6.25L6.25 9.75H9.75V6.25ZM3.625 11H5V12.375C5 13.1344 4.38439 13.75 3.625 13.75C2.86561 13.75 2.25 13.1344 2.25 12.375C2.25 11.6162 2.86472 11.0009 3.62336 11L3.625 11ZM11 12.3766C11.0009 13.1353 11.6162 13.75 12.375 13.75C13.1344 13.75 13.75 13.1344 13.75 12.375C13.75 11.6156 13.1344 11 12.375 11H11V12.375L11 12.3766ZM3.625 5C2.86561 5 2.25 4.38439 2.25 3.625C2.25 2.86561 2.86561 2.25 3.625 2.25C4.38439 2.25 5 2.86561 5 3.625V5H3.625Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

3
assets/icons/control.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 6.12488L7.64656 1.97853C7.84183 1.78328 8.1584 1.78329 8.35366 1.97854L12.5 6.12488" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 262 B

3
assets/icons/option.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.35606 1.005H1.62545C1.28002 1.005 1 1.28502 1 1.63044C1 1.97587 1.28002 2.25589 1.62545 2.25589L5.35606 2.25589C5.62311 2.25589 5.8607 2.42545 5.94752 2.67799L9.75029 13.7387C10.0108 14.4963 10.7235 15.005 11.5247 15.005H14.3746C14.72 15.005 15 14.725 15 14.3796C15 14.0341 14.72 13.7541 14.3746 13.7541H11.5247C11.2576 13.7541 11.02 13.5845 10.9332 13.332L7.13046 2.27128C6.86998 1.51366 6.15721 1.005 5.35606 1.005ZM14.3745 1.005H9.75125C9.40582 1.005 9.1258 1.28502 9.1258 1.63044C9.1258 1.97587 9.40582 2.25589 9.75125 2.25589L14.3745 2.25589C14.72 2.25589 15 1.97587 15 1.63044C15 1.28502 14.72 1.005 14.3745 1.005Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 792 B

3
assets/icons/return.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.375 1.63C8.375 1.28482 8.65482 1.005 9 1.005H12.375C13.8247 1.005 15 2.18025 15 3.63V7.625C15 9.07474 13.8247 10.25 12.375 10.25H3.13388L6.07194 13.1881C6.31602 13.4321 6.31602 13.8279 6.07194 14.0719C5.82786 14.316 5.43214 14.316 5.18806 14.0719L1.18306 10.0669C0.938981 9.82286 0.938981 9.42714 1.18306 9.18306L5.18306 5.18306C5.42714 4.93898 5.82286 4.93898 6.06694 5.18306C6.31102 5.42714 6.31102 5.82286 6.06694 6.06694L3.13388 9H12.375C13.1344 9 13.75 8.38439 13.75 7.625V3.63C13.75 2.87061 13.1344 2.255 12.375 2.255H9C8.65482 2.255 8.375 1.97518 8.375 1.63Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 737 B

3
assets/icons/shift.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.46475 7.99652L7.85304 2.15921C7.93223 2.07342 8.06777 2.07341 8.14696 2.15921L13.5352 7.99652C13.7126 8.18869 13.5763 8.5 13.3148 8.5H10.5V13.7C10.5 13.8657 10.3657 14 10.2 14H5.8C5.63431 14 5.5 13.8657 5.5 13.7V8.5H2.6852C2.42367 8.5 2.28737 8.18869 2.46475 7.99652Z" stroke="black" stroke-width="1.25"/>
</svg>

After

Width:  |  Height:  |  Size: 421 B

View file

@ -79,7 +79,7 @@ impl Render for CommandPalette {
type Element = Div;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
v_stack().w_96().child(self.picker.clone())
v_stack().min_w_96().child(self.picker.clone())
}
}
@ -303,6 +303,7 @@ impl PickerDelegate for CommandPaletteDelegate {
Some(
ListItem::new(ix).inset(true).selected(selected).child(
h_stack()
.w_full()
.justify_between()
.child(HighlightedLabel::new(
command.name.clone(),

View file

@ -23,15 +23,15 @@ impl ThemeColors {
surface_background: neutral().light().step_2(),
background: neutral().light().step_1(),
element_background: neutral().light().step_3(),
element_hover: neutral().light().step_4(),
element_active: neutral().light().step_5(),
element_selected: neutral().light().step_5(),
element_hover: neutral().light_alpha().step_4(),
element_active: neutral().light_alpha().step_5(),
element_selected: neutral().light_alpha().step_5(),
element_disabled: neutral().light_alpha().step_3(),
drop_target_background: blue().light_alpha().step_2(),
ghost_element_background: system.transparent,
ghost_element_hover: neutral().light().step_4(),
ghost_element_active: neutral().light().step_5(),
ghost_element_selected: neutral().light().step_5(),
ghost_element_hover: neutral().light_alpha().step_4(),
ghost_element_active: neutral().light_alpha().step_5(),
ghost_element_selected: neutral().light_alpha().step_5(),
ghost_element_disabled: neutral().light_alpha().step_3(),
text: yellow().light().step_9(),
text_muted: neutral().light().step_11(),
@ -95,15 +95,15 @@ impl ThemeColors {
surface_background: neutral().dark().step_2(),
background: neutral().dark().step_1(),
element_background: neutral().dark().step_3(),
element_hover: neutral().dark().step_4(),
element_active: neutral().dark().step_5(),
element_selected: neutral().dark().step_5(),
element_hover: neutral().dark_alpha().step_4(),
element_active: neutral().dark_alpha().step_5(),
element_selected: neutral().dark_alpha().step_5(),
element_disabled: neutral().dark_alpha().step_3(),
drop_target_background: blue().dark_alpha().step_2(),
ghost_element_background: system.transparent,
ghost_element_hover: neutral().dark().step_4(),
ghost_element_active: neutral().dark().step_5(),
ghost_element_selected: neutral().dark().step_5(),
ghost_element_hover: neutral().dark_alpha().step_4(),
ghost_element_active: neutral().dark_alpha().step_5(),
ghost_element_selected: neutral().dark_alpha().step_5(),
ghost_element_disabled: neutral().dark_alpha().step_3(),
text: neutral().dark().step_12(),
text_muted: neutral().dark().step_11(),

View file

@ -20,7 +20,7 @@ pub fn one_family() -> ThemeFamily {
pub(crate) fn one_dark() -> Theme {
let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.);
let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
let elevated_surface = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
let elevated_surface = hsla(225. / 360., 12. / 100., 17. / 100., 1.);
let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0);
let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0);
@ -48,7 +48,7 @@ pub(crate) fn one_dark() -> Theme {
elevated_surface_background: elevated_surface,
surface_background: bg,
background: bg,
element_background: elevated_surface,
element_background: hsla(223.0 / 360., 13. / 100., 21. / 100., 1.0),
element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),

View file

@ -14,6 +14,8 @@ pub enum IconSize {
pub enum Icon {
Ai,
ArrowLeft,
ArrowUp,
ArrowDown,
ArrowRight,
ArrowUpRight,
AtSign,
@ -71,6 +73,11 @@ pub enum Icon {
Terminal,
WholeWord,
XCircle,
Command,
Control,
Shift,
Option,
Return,
}
impl Icon {
@ -79,6 +86,8 @@ impl Icon {
Icon::Ai => "icons/ai.svg",
Icon::ArrowLeft => "icons/arrow_left.svg",
Icon::ArrowRight => "icons/arrow_right.svg",
Icon::ArrowUp => "icons/arrow_up.svg",
Icon::ArrowDown => "icons/arrow_down.svg",
Icon::ArrowUpRight => "icons/arrow_up_right.svg",
Icon::AtSign => "icons/at-sign.svg",
Icon::AudioOff => "icons/speaker-off.svg",
@ -135,6 +144,11 @@ impl Icon {
Icon::Terminal => "icons/terminal.svg",
Icon::WholeWord => "icons/word_search.svg",
Icon::XCircle => "icons/error.svg",
Icon::Command => "icons/command.svg",
Icon::Control => "icons/control.svg",
Icon::Shift => "icons/shift.svg",
Icon::Option => "icons/option.svg",
Icon::Return => "icons/return.svg",
}
}
}
@ -151,8 +165,8 @@ impl RenderOnce for IconElement {
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let svg_size = match self.size {
IconSize::Small => rems(0.75),
IconSize::Medium => rems(0.9375),
IconSize::Small => rems(14. / 16.),
IconSize::Medium => rems(16. / 16.),
};
svg()

View file

@ -1,5 +1,5 @@
use crate::prelude::*;
use gpui::{Action, Div, IntoElement};
use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
use gpui::{relative, rems, Action, Div, IntoElement, Keystroke};
#[derive(IntoElement, Clone)]
pub struct KeyBinding {
@ -13,20 +13,36 @@ pub struct KeyBinding {
impl RenderOnce for KeyBinding {
type Rendered = Div;
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
div()
.flex()
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
h_stack()
.flex_none()
.gap_2()
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
div()
.flex()
.gap_1()
let key_icon = Self::icon_for_key(&keystroke);
h_stack()
.flex_none()
.gap_0p5()
.bg(cx.theme().colors().element_background)
.p_0p5()
.rounded_sm()
.when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))
.when(keystroke.modifiers.control, |el| el.child(Key::new("^")))
.when(keystroke.modifiers.alt, |el| el.child(Key::new("")))
.when(keystroke.modifiers.command, |el| el.child(Key::new("")))
.when(keystroke.modifiers.shift, |el| el.child(Key::new("")))
.child(Key::new(keystroke.key.clone()))
.when(keystroke.modifiers.control, |el| {
el.child(KeyIcon::new(Icon::Control))
})
.when(keystroke.modifiers.alt, |el| {
el.child(KeyIcon::new(Icon::Option))
})
.when(keystroke.modifiers.command, |el| {
el.child(KeyIcon::new(Icon::Command))
})
.when(keystroke.modifiers.shift, |el| {
el.child(KeyIcon::new(Icon::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()))
})
}))
}
}
@ -39,6 +55,22 @@ impl KeyBinding {
Some(Self::new(key_binding))
}
fn icon_for_key(keystroke: &Keystroke) -> Option<Icon> {
let mut icon: Option<Icon> = None;
if keystroke.key == "left".to_string() {
icon = Some(Icon::ArrowLeft);
} else if keystroke.key == "right".to_string() {
icon = Some(Icon::ArrowRight);
} else if keystroke.key == "up".to_string() {
icon = Some(Icon::ArrowUp);
} else if keystroke.key == "down".to_string() {
icon = Some(Icon::ArrowDown);
}
icon
}
pub fn new(key_binding: gpui::KeyBinding) -> Self {
Self { key_binding }
}
@ -53,13 +85,18 @@ impl RenderOnce for Key {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let single_char = self.key.len() == 1;
div()
.px_2()
.py_0()
.rounded_md()
.text_ui_sm()
.when(single_char, |el| {
el.w(rems(14. / 16.)).flex().flex_none().justify_center()
})
.when(!single_char, |el| el.px_0p5())
.h(rems(14. / 16.))
.text_ui()
.line_height(relative(1.))
.text_color(cx.theme().colors().text)
.bg(cx.theme().colors().element_background)
.child(self.key.clone())
}
}
@ -69,3 +106,24 @@ impl Key {
Self { key: key.into() }
}
}
#[derive(IntoElement)]
pub struct KeyIcon {
icon: Icon,
}
impl RenderOnce for KeyIcon {
type Rendered = Div;
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
div()
.w(rems(14. / 16.))
.child(IconElement::new(self.icon).size(IconSize::Small))
}
}
impl KeyIcon {
pub fn new(icon: Icon) -> Self {
Self { icon }
}
}