Skip to content

Commit

Permalink
Introduce KeybindingHint (#24397)
Browse files Browse the repository at this point in the history
- Implements scaling for `ui::Keybinding` and it's component parts
- Adds the `ui::KeybindingHint` component for creating keybinding hints
easily:

![CleanShot 2025-02-04 at 16 59
38@2x](https://github.com/user-attachments/assets/d781e401-8875-4edc-a4b0-5f8750777d86)

Release Notes:

- N/A
  • Loading branch information
iamnbutler authored Feb 6, 2025
1 parent 9c132fe commit 00971fb
Show file tree
Hide file tree
Showing 9 changed files with 390 additions and 16 deletions.
1 change: 1 addition & 0 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5613,6 +5613,7 @@ impl Editor {
} else {
Color::Default
}),
None,
true,
),
))
Expand Down
1 change: 1 addition & 0 deletions crates/editor/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5784,6 +5784,7 @@ fn inline_completion_accept_indicator(
&accept_keystroke.modifiers,
PlatformStyle::platform(),
Some(Color::Default),
None,
false,
))
})
Expand Down
2 changes: 2 additions & 0 deletions crates/ui/src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod image;
mod indent_guides;
mod indicator;
mod keybinding;
mod keybinding_hint;
mod label;
mod list;
mod modal;
Expand Down Expand Up @@ -47,6 +48,7 @@ pub use image::*;
pub use indent_guides::*;
pub use indicator::*;
pub use keybinding::*;
pub use keybinding_hint::*;
pub use label::*;
pub use list::*;
pub use modal::*;
Expand Down
18 changes: 17 additions & 1 deletion crates/ui/src/components/button/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
use gpui::{AnyView, DefiniteLength};

use crate::{
prelude::*, Color, DynamicSpacing, ElevationIndex, IconPosition, KeyBinding, TintColor,
prelude::*, Color, DynamicSpacing, ElevationIndex, IconPosition, KeyBinding,
KeybindingPosition, TintColor,
};
use crate::{
ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle,
Expand Down Expand Up @@ -92,6 +93,7 @@ pub struct Button {
selected_icon: Option<IconName>,
selected_icon_color: Option<Color>,
key_binding: Option<KeyBinding>,
keybinding_position: KeybindingPosition,
alpha: Option<f32>,
}

Expand All @@ -117,6 +119,7 @@ impl Button {
selected_icon: None,
selected_icon_color: None,
key_binding: None,
keybinding_position: KeybindingPosition::default(),
alpha: None,
}
}
Expand Down Expand Up @@ -187,6 +190,15 @@ impl Button {
self
}

/// Sets the position of the keybinding relative to the button label.
///
/// This method allows you to specify where the keybinding should be displayed
/// in relation to the button's label.
pub fn key_binding_position(mut self, position: KeybindingPosition) -> Self {
self.keybinding_position = position;
self
}

/// Sets the alpha property of the color of label.
pub fn alpha(mut self, alpha: f32) -> Self {
self.alpha = Some(alpha);
Expand Down Expand Up @@ -412,6 +424,10 @@ impl RenderOnce for Button {
})
.child(
h_flex()
.when(
self.keybinding_position == KeybindingPosition::Start,
|this| this.flex_row_reverse(),
)
.gap(DynamicSpacing::Base06.rems(cx))
.justify_between()
.child(
Expand Down
7 changes: 7 additions & 0 deletions crates/ui/src/components/button/button_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ pub enum IconPosition {
End,
}

#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum KeybindingPosition {
Start,
#[default]
End,
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum TintColor {
#[default]
Expand Down
4 changes: 4 additions & 0 deletions crates/ui/src/components/icon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub enum IconSize {
Medium,
/// 48px
XLarge,
Custom(Pixels),
}

impl IconSize {
Expand All @@ -80,6 +81,7 @@ impl IconSize {
IconSize::Small => rems_from_px(14.),
IconSize::Medium => rems_from_px(16.),
IconSize::XLarge => rems_from_px(48.),
IconSize::Custom(size) => rems_from_px(size.into()),
}
}

Expand All @@ -96,6 +98,8 @@ impl IconSize {
IconSize::Small => DynamicSpacing::Base02.px(cx),
IconSize::Medium => DynamicSpacing::Base02.px(cx),
IconSize::XLarge => DynamicSpacing::Base02.px(cx),
// TODO: Wire into dynamic spacing
IconSize::Custom(size) => px(size.into()),
};

(icon_size, padding)
Expand Down
63 changes: 49 additions & 14 deletions crates/ui/src/components/keybinding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct KeyBinding {

/// The [`PlatformStyle`] to use when displaying this keybinding.
platform_style: PlatformStyle,
size: Option<Pixels>,
}

impl KeyBinding {
Expand Down Expand Up @@ -47,6 +48,7 @@ impl KeyBinding {
Self {
key_binding,
platform_style: PlatformStyle::platform(),
size: None,
}
}

Expand All @@ -55,6 +57,12 @@ impl KeyBinding {
self.platform_style = platform_style;
self
}

/// Sets the size for this [`KeyBinding`].
pub fn size(mut self, size: Pixels) -> Self {
self.size = Some(size);
self
}
}

impl RenderOnce for KeyBinding {
Expand Down Expand Up @@ -83,9 +91,12 @@ impl RenderOnce for KeyBinding {
&keystroke.modifiers,
self.platform_style,
None,
self.size,
false,
))
.map(|el| el.child(render_key(&keystroke, self.platform_style, None)))
.map(|el| {
el.child(render_key(&keystroke, self.platform_style, None, self.size))
})
}))
}
}
Expand All @@ -94,11 +105,14 @@ pub fn render_key(
keystroke: &Keystroke,
platform_style: PlatformStyle,
color: Option<Color>,
size: Option<Pixels>,
) -> AnyElement {
let key_icon = icon_for_key(keystroke, platform_style);
match key_icon {
Some(icon) => KeyIcon::new(icon, color).into_any_element(),
None => Key::new(capitalize(&keystroke.key), color).into_any_element(),
Some(icon) => KeyIcon::new(icon, color).size(size).into_any_element(),
None => Key::new(capitalize(&keystroke.key), color)
.size(size)
.into_any_element(),
}
}

Expand Down Expand Up @@ -130,6 +144,7 @@ pub fn render_modifiers(
modifiers: &Modifiers,
platform_style: PlatformStyle,
color: Option<Color>,
size: Option<Pixels>,
standalone: bool,
) -> impl Iterator<Item = AnyElement> {
enum KeyOrIcon {
Expand Down Expand Up @@ -200,35 +215,35 @@ pub fn render_modifiers(
PlatformStyle::Windows => vec![modifier.windows, KeyOrIcon::Key("+")],
})
.map(move |key_or_icon| match key_or_icon {
KeyOrIcon::Key(key) => Key::new(key, color).into_any_element(),
KeyOrIcon::Icon(icon) => KeyIcon::new(icon, color).into_any_element(),
KeyOrIcon::Key(key) => Key::new(key, color).size(size).into_any_element(),
KeyOrIcon::Icon(icon) => KeyIcon::new(icon, color).size(size).into_any_element(),
})
}

#[derive(IntoElement)]
pub struct Key {
key: SharedString,
color: Option<Color>,
size: Option<Pixels>,
}

impl RenderOnce for Key {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let single_char = self.key.len() == 1;
let size = self.size.unwrap_or(px(14.));
let size_f32: f32 = size.into();

div()
.py_0()
.map(|this| {
if single_char {
this.w(rems_from_px(14.))
.flex()
.flex_none()
.justify_center()
this.w(size).flex().flex_none().justify_center()
} else {
this.px_0p5()
}
})
.h(rems_from_px(14.))
.text_ui(cx)
.h(rems_from_px(size_f32))
.text_size(size)
.line_height(relative(1.))
.text_color(self.color.unwrap_or(Color::Muted).color(cx))
.child(self.key.clone())
Expand All @@ -240,27 +255,47 @@ impl Key {
Self {
key: key.into(),
color,
size: None,
}
}

pub fn size(mut self, size: impl Into<Option<Pixels>>) -> Self {
self.size = size.into();
self
}
}

#[derive(IntoElement)]
pub struct KeyIcon {
icon: IconName,
color: Option<Color>,
size: Option<Pixels>,
}

impl RenderOnce for KeyIcon {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
let size = self
.size
.unwrap_or(IconSize::Small.rems().to_pixels(window.rem_size()));

Icon::new(self.icon)
.size(IconSize::XSmall)
.size(IconSize::Custom(size))
.color(self.color.unwrap_or(Color::Muted))
}
}

impl KeyIcon {
pub fn new(icon: IconName, color: Option<Color>) -> Self {
Self { icon, color }
Self {
icon,
color,
size: None,
}
}

pub fn size(mut self, size: impl Into<Option<Pixels>>) -> Self {
self.size = size.into();
self
}
}

Expand Down
Loading

0 comments on commit 00971fb

Please sign in to comment.