Add refinements to the keymap UI (#33816)
This includes mostly polishing up the keystroke editing modal, and some other bits like making the keystroke rendering function more composable. Release Notes: - Added refinements to the keymap UI design. --------- Co-authored-by: Ben Kunkle <ben@zed.dev> Co-authored-by: Ben Kunkle <Ben.kunkle@gmail.com>
This commit is contained in:
parent
fbc4256732
commit
77c4530e12
4 changed files with 233 additions and 163 deletions
|
@ -8,8 +8,8 @@ use fs::Fs;
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext as _, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
AppContext as _, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
FontWeight, Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy, StyledText,
|
Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy, StyledText, Subscription,
|
||||||
Subscription, WeakEntity, actions, div, transparent_black,
|
WeakEntity, actions, div, transparent_black,
|
||||||
};
|
};
|
||||||
use language::{Language, LanguageConfig};
|
use language::{Language, LanguageConfig};
|
||||||
use settings::KeybindSource;
|
use settings::KeybindSource;
|
||||||
|
@ -18,7 +18,7 @@ use util::ResultExt;
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
ActiveTheme as _, App, BorrowAppContext, ContextMenu, ParentElement as _, Render, SharedString,
|
ActiveTheme as _, App, BorrowAppContext, ContextMenu, ParentElement as _, Render, SharedString,
|
||||||
Styled as _, Window, prelude::*, right_click_menu,
|
Styled as _, Tooltip, Window, prelude::*, right_click_menu,
|
||||||
};
|
};
|
||||||
use workspace::{Item, ModalView, SerializableItem, Workspace, register_serializable_item};
|
use workspace::{Item, ModalView, SerializableItem, Workspace, register_serializable_item};
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ impl KeymapEditor {
|
||||||
|
|
||||||
let filter_editor = cx.new(|cx| {
|
let filter_editor = cx.new(|cx| {
|
||||||
let mut editor = Editor::single_line(window, cx);
|
let mut editor = Editor::single_line(window, cx);
|
||||||
editor.set_placeholder_text("Filter action names...", cx);
|
editor.set_placeholder_text("Filter action names…", cx);
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ impl KeymapEditor {
|
||||||
|
|
||||||
let keystroke_text = ui::text_for_keystrokes(key_binding.keystrokes(), cx);
|
let keystroke_text = ui::text_for_keystrokes(key_binding.keystrokes(), cx);
|
||||||
let ui_key_binding = Some(
|
let ui_key_binding = Some(
|
||||||
ui::KeyBinding::new(key_binding.clone(), cx)
|
ui::KeyBinding::new_from_gpui(key_binding.clone(), cx)
|
||||||
.vim_mode(source == Some(settings::KeybindSource::Vim)),
|
.vim_mode(source == Some(settings::KeybindSource::Vim)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -571,7 +571,7 @@ impl Render for KeymapEditor {
|
||||||
let row_count = self.matches.len();
|
let row_count = self.matches.len();
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
|
|
||||||
div()
|
v_flex()
|
||||||
.key_context(self.dispatch_context(window, cx))
|
.key_context(self.dispatch_context(window, cx))
|
||||||
.on_action(cx.listener(Self::select_next))
|
.on_action(cx.listener(Self::select_next))
|
||||||
.on_action(cx.listener(Self::select_previous))
|
.on_action(cx.listener(Self::select_previous))
|
||||||
|
@ -586,9 +586,9 @@ impl Render for KeymapEditor {
|
||||||
.bg(theme.colors().editor_background)
|
.bg(theme.colors().editor_background)
|
||||||
.id("keymap-editor")
|
.id("keymap-editor")
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
|
.pt_4()
|
||||||
.px_4()
|
.px_4()
|
||||||
.v_flex()
|
.gap_4()
|
||||||
.pb_4()
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.key_context({
|
.key_context({
|
||||||
|
@ -596,12 +596,13 @@ impl Render for KeymapEditor {
|
||||||
context.add("BufferSearchBar");
|
context.add("BufferSearchBar");
|
||||||
context
|
context
|
||||||
})
|
})
|
||||||
.w_full()
|
.h_8()
|
||||||
.h_12()
|
.pl_2()
|
||||||
.px_4()
|
.pr_1()
|
||||||
.my_4()
|
.py_1()
|
||||||
.border_2()
|
.border_1()
|
||||||
.border_color(theme.colors().border)
|
.border_color(theme.colors().border)
|
||||||
|
.rounded_lg()
|
||||||
.child(self.filter_editor.clone()),
|
.child(self.filter_editor.clone()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
@ -742,7 +743,7 @@ impl RenderOnce for SyntaxHighlightedText {
|
||||||
|
|
||||||
struct KeybindingEditorModal {
|
struct KeybindingEditorModal {
|
||||||
editing_keybind: ProcessedKeybinding,
|
editing_keybind: ProcessedKeybinding,
|
||||||
keybind_editor: Entity<KeybindInput>,
|
keybind_editor: Entity<KeystrokeInput>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -764,7 +765,7 @@ impl KeybindingEditorModal {
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let keybind_editor = cx.new(KeybindInput::new);
|
let keybind_editor = cx.new(KeystrokeInput::new);
|
||||||
Self {
|
Self {
|
||||||
editing_keybind,
|
editing_keybind,
|
||||||
fs,
|
fs,
|
||||||
|
@ -777,84 +778,65 @@ impl KeybindingEditorModal {
|
||||||
impl Render for KeybindingEditorModal {
|
impl Render for KeybindingEditorModal {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let theme = cx.theme().colors();
|
let theme = cx.theme().colors();
|
||||||
|
|
||||||
return v_flex()
|
return v_flex()
|
||||||
.gap_4()
|
|
||||||
.w(rems(36.))
|
.w(rems(36.))
|
||||||
|
.elevation_3(cx)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.items_center()
|
.pt_2()
|
||||||
.text_center()
|
|
||||||
.bg(theme.background)
|
|
||||||
.border_color(theme.border)
|
|
||||||
.border_2()
|
|
||||||
.px_4()
|
.px_4()
|
||||||
.py_2()
|
.pb_4()
|
||||||
|
.gap_2()
|
||||||
|
.child(Label::new("Input desired keystroke, then hit save"))
|
||||||
|
.child(self.keybind_editor.clone()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.p_2()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
.gap_1()
|
||||||
|
.justify_end()
|
||||||
|
.border_t_1()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
.child(
|
.child(
|
||||||
div()
|
Button::new("cancel", "Cancel")
|
||||||
.text_lg()
|
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
|
||||||
.font_weight(FontWeight::BOLD)
|
|
||||||
.child("Input desired keybinding, then hit save"),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
Button::new("save-btn", "Save Keybinding").on_click(cx.listener(
|
||||||
.w_full()
|
|this, _event, _window, cx| {
|
||||||
.child(self.keybind_editor.clone())
|
let existing_keybind = this.editing_keybind.clone();
|
||||||
.child(
|
let fs = this.fs.clone();
|
||||||
IconButton::new("backspace-btn", ui::IconName::Backspace).on_click(
|
let new_keystrokes = this
|
||||||
cx.listener(|this, _event, _window, cx| {
|
.keybind_editor
|
||||||
this.keybind_editor.update(cx, |editor, cx| {
|
.read_with(cx, |editor, _| editor.keystrokes.clone());
|
||||||
editor.keystrokes.pop();
|
if new_keystrokes.is_empty() {
|
||||||
|
this.error = Some("Keystrokes cannot be empty".to_string());
|
||||||
|
cx.notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let tab_size =
|
||||||
|
cx.global::<settings::SettingsStore>().json_tab_size();
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
if let Err(err) = save_keybinding_update(
|
||||||
|
existing_keybind,
|
||||||
|
&new_keystrokes,
|
||||||
|
&fs,
|
||||||
|
tab_size,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.error = Some(err.to_string());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
}),
|
.log_err();
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(IconButton::new("clear-btn", ui::IconName::Eraser).on_click(
|
|
||||||
cx.listener(|this, _event, _window, cx| {
|
|
||||||
this.keybind_editor.update(cx, |editor, cx| {
|
|
||||||
editor.keystrokes.clear();
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex().w_full().items_center().justify_center().child(
|
|
||||||
Button::new("save-btn", "Save")
|
|
||||||
.label_size(LabelSize::Large)
|
|
||||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
|
||||||
let existing_keybind = this.editing_keybind.clone();
|
|
||||||
let fs = this.fs.clone();
|
|
||||||
let new_keystrokes = this
|
|
||||||
.keybind_editor
|
|
||||||
.read_with(cx, |editor, _| editor.keystrokes.clone());
|
|
||||||
if new_keystrokes.is_empty() {
|
|
||||||
this.error = Some("Keystrokes cannot be empty".to_string());
|
|
||||||
cx.notify();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
let tab_size =
|
})
|
||||||
cx.global::<settings::SettingsStore>().json_tab_size();
|
.detach();
|
||||||
cx.spawn(async move |this, cx| {
|
},
|
||||||
if let Err(err) = save_keybinding_update(
|
)),
|
||||||
existing_keybind,
|
|
||||||
&new_keystrokes,
|
|
||||||
&fs,
|
|
||||||
tab_size,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.error = Some(err.to_string());
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.when_some(self.error.clone(), |this, error| {
|
.when_some(self.error.clone(), |this, error| {
|
||||||
|
@ -879,11 +861,13 @@ async fn save_keybinding_update(
|
||||||
let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
|
let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
|
||||||
.await
|
.await
|
||||||
.context("Failed to load keymap file")?;
|
.context("Failed to load keymap file")?;
|
||||||
|
|
||||||
let existing_keystrokes = existing
|
let existing_keystrokes = existing
|
||||||
.ui_key_binding
|
.ui_key_binding
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|keybinding| keybinding.key_binding.keystrokes())
|
.map(|keybinding| keybinding.keystrokes.as_slice())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let context = existing
|
let context = existing
|
||||||
.context
|
.context
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -927,12 +911,12 @@ async fn save_keybinding_update(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct KeybindInput {
|
struct KeystrokeInput {
|
||||||
keystrokes: Vec<Keystroke>,
|
keystrokes: Vec<Keystroke>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeybindInput {
|
impl KeystrokeInput {
|
||||||
fn new(cx: &mut Context<Self>) -> Self {
|
fn new(cx: &mut Context<Self>) -> Self {
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
Self {
|
Self {
|
||||||
|
@ -1007,16 +991,18 @@ impl KeybindInput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for KeybindInput {
|
impl Focusable for KeystrokeInput {
|
||||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||||
self.focus_handle.clone()
|
self.focus_handle.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for KeybindInput {
|
impl Render for KeystrokeInput {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let colors = cx.theme().colors();
|
let colors = cx.theme().colors();
|
||||||
return div()
|
|
||||||
|
return h_flex()
|
||||||
|
.id("keybinding_input")
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
|
.on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
|
||||||
.on_key_down(cx.listener(Self::on_key_down))
|
.on_key_down(cx.listener(Self::on_key_down))
|
||||||
|
@ -1025,16 +1011,55 @@ impl Render for KeybindInput {
|
||||||
style.border_color = Some(colors.border_focused);
|
style.border_color = Some(colors.border_focused);
|
||||||
style
|
style
|
||||||
})
|
})
|
||||||
.h_12()
|
.py_2()
|
||||||
|
.px_3()
|
||||||
|
.gap_2()
|
||||||
|
.min_h_8()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
.justify_between()
|
||||||
.bg(colors.editor_background)
|
.bg(colors.editor_background)
|
||||||
.border_2()
|
.border_1()
|
||||||
.border_color(colors.border)
|
.rounded_md()
|
||||||
.p_4()
|
.flex_1()
|
||||||
.flex_row()
|
.overflow_hidden()
|
||||||
.text_center()
|
.child(
|
||||||
.justify_center()
|
h_flex()
|
||||||
.child(ui::text_for_keystrokes(&self.keystrokes, cx));
|
.w_full()
|
||||||
|
.min_w_0()
|
||||||
|
.justify_center()
|
||||||
|
.flex_wrap()
|
||||||
|
.gap(ui::DynamicSpacing::Base04.rems(cx))
|
||||||
|
.children(self.keystrokes.iter().map(|keystroke| {
|
||||||
|
h_flex().children(ui::render_keystroke(
|
||||||
|
keystroke,
|
||||||
|
None,
|
||||||
|
Some(rems(0.875).into()),
|
||||||
|
ui::PlatformStyle::platform(),
|
||||||
|
false,
|
||||||
|
))
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_0p5()
|
||||||
|
.flex_none()
|
||||||
|
.child(
|
||||||
|
IconButton::new("backspace-btn", IconName::Delete)
|
||||||
|
.tooltip(Tooltip::text("Delete Keystroke"))
|
||||||
|
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||||
|
this.keystrokes.pop();
|
||||||
|
cx.notify();
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("clear-btn", IconName::Eraser)
|
||||||
|
.tooltip(Tooltip::text("Clear Keystrokes"))
|
||||||
|
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||||
|
this.keystrokes.clear();
|
||||||
|
cx.notify();
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub struct KeyBinding {
|
||||||
/// More than one keystroke produces a chord.
|
/// More than one keystroke produces a chord.
|
||||||
///
|
///
|
||||||
/// This should always contain at least one keystroke.
|
/// This should always contain at least one keystroke.
|
||||||
pub key_binding: gpui::KeyBinding,
|
pub keystrokes: Vec<Keystroke>,
|
||||||
|
|
||||||
/// The [`PlatformStyle`] to use when displaying this keybinding.
|
/// The [`PlatformStyle`] to use when displaying this keybinding.
|
||||||
platform_style: PlatformStyle,
|
platform_style: PlatformStyle,
|
||||||
|
@ -37,7 +37,7 @@ impl KeyBinding {
|
||||||
return Self::for_action_in(action, &focused, window, cx);
|
return Self::for_action_in(action, &focused, window, cx);
|
||||||
}
|
}
|
||||||
let key_binding = window.highest_precedence_binding_for_action(action)?;
|
let key_binding = window.highest_precedence_binding_for_action(action)?;
|
||||||
Some(Self::new(key_binding, cx))
|
Some(Self::new_from_gpui(key_binding, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like `for_action`, but lets you specify the context from which keybindings are matched.
|
/// Like `for_action`, but lets you specify the context from which keybindings are matched.
|
||||||
|
@ -48,7 +48,7 @@ impl KeyBinding {
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let key_binding = window.highest_precedence_binding_for_action_in(action, focus)?;
|
let key_binding = window.highest_precedence_binding_for_action_in(action, focus)?;
|
||||||
Some(Self::new(key_binding, cx))
|
Some(Self::new_from_gpui(key_binding, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_vim_mode(cx: &mut App, enabled: bool) {
|
pub fn set_vim_mode(cx: &mut App, enabled: bool) {
|
||||||
|
@ -59,9 +59,9 @@ impl KeyBinding {
|
||||||
cx.try_global::<VimStyle>().is_some_and(|g| g.0)
|
cx.try_global::<VimStyle>().is_some_and(|g| g.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(key_binding: gpui::KeyBinding, cx: &App) -> Self {
|
pub fn new(keystrokes: Vec<Keystroke>, cx: &App) -> Self {
|
||||||
Self {
|
Self {
|
||||||
key_binding,
|
keystrokes,
|
||||||
platform_style: PlatformStyle::platform(),
|
platform_style: PlatformStyle::platform(),
|
||||||
size: None,
|
size: None,
|
||||||
vim_mode: KeyBinding::is_vim_mode(cx),
|
vim_mode: KeyBinding::is_vim_mode(cx),
|
||||||
|
@ -69,6 +69,10 @@ impl KeyBinding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_from_gpui(key_binding: gpui::KeyBinding, cx: &App) -> Self {
|
||||||
|
Self::new(key_binding.keystrokes().to_vec(), cx)
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the [`PlatformStyle`] for this [`KeyBinding`].
|
/// Sets the [`PlatformStyle`] for this [`KeyBinding`].
|
||||||
pub fn platform_style(mut self, platform_style: PlatformStyle) -> Self {
|
pub fn platform_style(mut self, platform_style: PlatformStyle) -> Self {
|
||||||
self.platform_style = platform_style;
|
self.platform_style = platform_style;
|
||||||
|
@ -92,15 +96,20 @@ impl KeyBinding {
|
||||||
self.vim_mode = enabled;
|
self.vim_mode = enabled;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_key(&self, keystroke: &Keystroke, color: Option<Color>) -> AnyElement {
|
fn render_key(
|
||||||
let key_icon = icon_for_key(keystroke, self.platform_style);
|
keystroke: &Keystroke,
|
||||||
match key_icon {
|
color: Option<Color>,
|
||||||
Some(icon) => KeyIcon::new(icon, color).size(self.size).into_any_element(),
|
platform_style: PlatformStyle,
|
||||||
None => {
|
size: impl Into<Option<AbsoluteLength>>,
|
||||||
let key = util::capitalize(&keystroke.key);
|
) -> AnyElement {
|
||||||
Key::new(&key, color).size(self.size).into_any_element()
|
let key_icon = icon_for_key(keystroke, platform_style);
|
||||||
}
|
match key_icon {
|
||||||
|
Some(icon) => KeyIcon::new(icon, color).size(size).into_any_element(),
|
||||||
|
None => {
|
||||||
|
let key = util::capitalize(&keystroke.key);
|
||||||
|
Key::new(&key, color).size(size).into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,17 +117,12 @@ impl KeyBinding {
|
||||||
impl RenderOnce for KeyBinding {
|
impl RenderOnce for KeyBinding {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let color = self.disabled.then_some(Color::Disabled);
|
let color = self.disabled.then_some(Color::Disabled);
|
||||||
let use_text = self.vim_mode
|
|
||||||
|| matches!(
|
|
||||||
self.platform_style,
|
|
||||||
PlatformStyle::Linux | PlatformStyle::Windows
|
|
||||||
);
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.debug_selector(|| {
|
.debug_selector(|| {
|
||||||
format!(
|
format!(
|
||||||
"KEY_BINDING-{}",
|
"KEY_BINDING-{}",
|
||||||
self.key_binding
|
self.keystrokes
|
||||||
.keystrokes()
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|k| k.key.to_string())
|
.map(|k| k.key.to_string())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
@ -127,35 +131,56 @@ impl RenderOnce for KeyBinding {
|
||||||
})
|
})
|
||||||
.gap(DynamicSpacing::Base04.rems(cx))
|
.gap(DynamicSpacing::Base04.rems(cx))
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
|
.children(self.keystrokes.iter().map(|keystroke| {
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.py_0p5()
|
.py_0p5()
|
||||||
.rounded_xs()
|
.rounded_xs()
|
||||||
.text_color(cx.theme().colors().text_muted)
|
.text_color(cx.theme().colors().text_muted)
|
||||||
.when(use_text, |el| {
|
.children(render_keystroke(
|
||||||
el.child(
|
keystroke,
|
||||||
Key::new(
|
color,
|
||||||
keystroke_text(&keystroke, self.platform_style, self.vim_mode),
|
self.size,
|
||||||
color,
|
self.platform_style,
|
||||||
)
|
self.vim_mode,
|
||||||
.size(self.size),
|
))
|
||||||
)
|
|
||||||
})
|
|
||||||
.when(!use_text, |el| {
|
|
||||||
el.children(render_modifiers(
|
|
||||||
&keystroke.modifiers,
|
|
||||||
self.platform_style,
|
|
||||||
color,
|
|
||||||
self.size,
|
|
||||||
true,
|
|
||||||
))
|
|
||||||
.map(|el| el.child(self.render_key(&keystroke, color)))
|
|
||||||
})
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_keystroke(
|
||||||
|
keystroke: &Keystroke,
|
||||||
|
color: Option<Color>,
|
||||||
|
size: impl Into<Option<AbsoluteLength>>,
|
||||||
|
platform_style: PlatformStyle,
|
||||||
|
vim_mode: bool,
|
||||||
|
) -> Vec<AnyElement> {
|
||||||
|
let use_text = vim_mode
|
||||||
|
|| matches!(
|
||||||
|
platform_style,
|
||||||
|
PlatformStyle::Linux | PlatformStyle::Windows
|
||||||
|
);
|
||||||
|
let size = size.into();
|
||||||
|
|
||||||
|
if use_text {
|
||||||
|
let element = Key::new(keystroke_text(&keystroke, platform_style, vim_mode), color)
|
||||||
|
.size(size)
|
||||||
|
.into_any_element();
|
||||||
|
vec![element]
|
||||||
|
} else {
|
||||||
|
let mut elements = Vec::new();
|
||||||
|
elements.extend(render_modifiers(
|
||||||
|
&keystroke.modifiers,
|
||||||
|
platform_style,
|
||||||
|
color,
|
||||||
|
size,
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
elements.push(render_key(&keystroke, color, platform_style, size));
|
||||||
|
elements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn icon_for_key(keystroke: &Keystroke, platform_style: PlatformStyle) -> Option<IconName> {
|
fn icon_for_key(keystroke: &Keystroke, platform_style: PlatformStyle) -> Option<IconName> {
|
||||||
match keystroke.key.as_str() {
|
match keystroke.key.as_str() {
|
||||||
"left" => Some(IconName::ArrowLeft),
|
"left" => Some(IconName::ArrowLeft),
|
||||||
|
@ -466,7 +491,7 @@ impl Component for KeyBinding {
|
||||||
vec![
|
vec![
|
||||||
single_example(
|
single_example(
|
||||||
"Default",
|
"Default",
|
||||||
KeyBinding::new(
|
KeyBinding::new_from_gpui(
|
||||||
gpui::KeyBinding::new("ctrl-s", gpui::NoAction, None),
|
gpui::KeyBinding::new("ctrl-s", gpui::NoAction, None),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -474,7 +499,7 @@ impl Component for KeyBinding {
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"Mac Style",
|
"Mac Style",
|
||||||
KeyBinding::new(
|
KeyBinding::new_from_gpui(
|
||||||
gpui::KeyBinding::new("cmd-s", gpui::NoAction, None),
|
gpui::KeyBinding::new("cmd-s", gpui::NoAction, None),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -483,7 +508,7 @@ impl Component for KeyBinding {
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"Windows Style",
|
"Windows Style",
|
||||||
KeyBinding::new(
|
KeyBinding::new_from_gpui(
|
||||||
gpui::KeyBinding::new("ctrl-s", gpui::NoAction, None),
|
gpui::KeyBinding::new("ctrl-s", gpui::NoAction, None),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -496,9 +521,12 @@ impl Component for KeyBinding {
|
||||||
"Vim Mode",
|
"Vim Mode",
|
||||||
vec![single_example(
|
vec![single_example(
|
||||||
"Vim Mode Enabled",
|
"Vim Mode Enabled",
|
||||||
KeyBinding::new(gpui::KeyBinding::new("dd", gpui::NoAction, None), cx)
|
KeyBinding::new_from_gpui(
|
||||||
.vim_mode(true)
|
gpui::KeyBinding::new("dd", gpui::NoAction, None),
|
||||||
.into_any_element(),
|
cx,
|
||||||
|
)
|
||||||
|
.vim_mode(true)
|
||||||
|
.into_any_element(),
|
||||||
)],
|
)],
|
||||||
),
|
),
|
||||||
example_group_with_title(
|
example_group_with_title(
|
||||||
|
@ -506,7 +534,7 @@ impl Component for KeyBinding {
|
||||||
vec![
|
vec![
|
||||||
single_example(
|
single_example(
|
||||||
"Multiple Keys",
|
"Multiple Keys",
|
||||||
KeyBinding::new(
|
KeyBinding::new_from_gpui(
|
||||||
gpui::KeyBinding::new("ctrl-k ctrl-b", gpui::NoAction, None),
|
gpui::KeyBinding::new("ctrl-k ctrl-b", gpui::NoAction, None),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -514,7 +542,7 @@ impl Component for KeyBinding {
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"With Shift",
|
"With Shift",
|
||||||
KeyBinding::new(
|
KeyBinding::new_from_gpui(
|
||||||
gpui::KeyBinding::new("shift-cmd-p", gpui::NoAction, None),
|
gpui::KeyBinding::new("shift-cmd-p", gpui::NoAction, None),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -216,7 +216,7 @@ impl Component for KeybindingHint {
|
||||||
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||||
let enter_fallback = gpui::KeyBinding::new("enter", menu::Confirm, None);
|
let enter_fallback = gpui::KeyBinding::new("enter", menu::Confirm, None);
|
||||||
let enter = KeyBinding::for_action(&menu::Confirm, window, cx)
|
let enter = KeyBinding::for_action(&menu::Confirm, window, cx)
|
||||||
.unwrap_or(KeyBinding::new(enter_fallback, cx));
|
.unwrap_or(KeyBinding::new_from_gpui(enter_fallback, cx));
|
||||||
|
|
||||||
let bg_color = cx.theme().colors().surface_background;
|
let bg_color = cx.theme().colors().surface_background;
|
||||||
|
|
||||||
|
|
|
@ -18,16 +18,16 @@ impl Render for KeybindingStory {
|
||||||
Story::container(cx)
|
Story::container(cx)
|
||||||
.child(Story::title_for::<KeyBinding>(cx))
|
.child(Story::title_for::<KeyBinding>(cx))
|
||||||
.child(Story::label("Single Key", cx))
|
.child(Story::label("Single Key", cx))
|
||||||
.child(KeyBinding::new(binding("Z"), cx))
|
.child(KeyBinding::new_from_gpui(binding("Z"), cx))
|
||||||
.child(Story::label("Single Key with Modifier", cx))
|
.child(Story::label("Single Key with Modifier", cx))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(KeyBinding::new(binding("ctrl-c"), cx))
|
.child(KeyBinding::new_from_gpui(binding("ctrl-c"), cx))
|
||||||
.child(KeyBinding::new(binding("alt-c"), cx))
|
.child(KeyBinding::new_from_gpui(binding("alt-c"), cx))
|
||||||
.child(KeyBinding::new(binding("cmd-c"), cx))
|
.child(KeyBinding::new_from_gpui(binding("cmd-c"), cx))
|
||||||
.child(KeyBinding::new(binding("shift-c"), cx)),
|
.child(KeyBinding::new_from_gpui(binding("shift-c"), cx)),
|
||||||
)
|
)
|
||||||
.child(Story::label("Single Key with Modifier (Permuted)", cx))
|
.child(Story::label("Single Key with Modifier (Permuted)", cx))
|
||||||
.child(
|
.child(
|
||||||
|
@ -41,42 +41,59 @@ impl Render for KeybindingStory {
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.py_3()
|
.py_3()
|
||||||
.children(chunk.map(|permutation| {
|
.children(chunk.map(|permutation| {
|
||||||
KeyBinding::new(binding(&(permutation.join("-") + "-x")), cx)
|
KeyBinding::new_from_gpui(
|
||||||
|
binding(&(permutation.join("-") + "-x")),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(Story::label("Single Key with All Modifiers", cx))
|
.child(Story::label("Single Key with All Modifiers", cx))
|
||||||
.child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx))
|
.child(KeyBinding::new_from_gpui(
|
||||||
|
binding("ctrl-alt-cmd-shift-z"),
|
||||||
|
cx,
|
||||||
|
))
|
||||||
.child(Story::label("Chord", cx))
|
.child(Story::label("Chord", cx))
|
||||||
.child(KeyBinding::new(binding("a z"), cx))
|
.child(KeyBinding::new_from_gpui(binding("a z"), cx))
|
||||||
.child(Story::label("Chord with Modifier", cx))
|
.child(Story::label("Chord with Modifier", cx))
|
||||||
.child(KeyBinding::new(binding("ctrl-a shift-z"), cx))
|
.child(KeyBinding::new_from_gpui(binding("ctrl-a shift-z"), cx))
|
||||||
.child(KeyBinding::new(binding("fn-s"), cx))
|
.child(KeyBinding::new_from_gpui(binding("fn-s"), cx))
|
||||||
.child(Story::label("Single Key with All Modifiers (Linux)", cx))
|
.child(Story::label("Single Key with All Modifiers (Linux)", cx))
|
||||||
.child(
|
.child(
|
||||||
KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx)
|
KeyBinding::new_from_gpui(binding("ctrl-alt-cmd-shift-z"), cx)
|
||||||
.platform_style(PlatformStyle::Linux),
|
.platform_style(PlatformStyle::Linux),
|
||||||
)
|
)
|
||||||
.child(Story::label("Chord (Linux)", cx))
|
.child(Story::label("Chord (Linux)", cx))
|
||||||
.child(KeyBinding::new(binding("a z"), cx).platform_style(PlatformStyle::Linux))
|
.child(
|
||||||
|
KeyBinding::new_from_gpui(binding("a z"), cx).platform_style(PlatformStyle::Linux),
|
||||||
|
)
|
||||||
.child(Story::label("Chord with Modifier (Linux)", cx))
|
.child(Story::label("Chord with Modifier (Linux)", cx))
|
||||||
.child(
|
.child(
|
||||||
KeyBinding::new(binding("ctrl-a shift-z"), cx).platform_style(PlatformStyle::Linux),
|
KeyBinding::new_from_gpui(binding("ctrl-a shift-z"), cx)
|
||||||
|
.platform_style(PlatformStyle::Linux),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
KeyBinding::new_from_gpui(binding("fn-s"), cx).platform_style(PlatformStyle::Linux),
|
||||||
)
|
)
|
||||||
.child(KeyBinding::new(binding("fn-s"), cx).platform_style(PlatformStyle::Linux))
|
|
||||||
.child(Story::label("Single Key with All Modifiers (Windows)", cx))
|
.child(Story::label("Single Key with All Modifiers (Windows)", cx))
|
||||||
.child(
|
.child(
|
||||||
KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx)
|
KeyBinding::new_from_gpui(binding("ctrl-alt-cmd-shift-z"), cx)
|
||||||
.platform_style(PlatformStyle::Windows),
|
.platform_style(PlatformStyle::Windows),
|
||||||
)
|
)
|
||||||
.child(Story::label("Chord (Windows)", cx))
|
.child(Story::label("Chord (Windows)", cx))
|
||||||
.child(KeyBinding::new(binding("a z"), cx).platform_style(PlatformStyle::Windows))
|
|
||||||
.child(Story::label("Chord with Modifier (Windows)", cx))
|
|
||||||
.child(
|
.child(
|
||||||
KeyBinding::new(binding("ctrl-a shift-z"), cx)
|
KeyBinding::new_from_gpui(binding("a z"), cx)
|
||||||
|
.platform_style(PlatformStyle::Windows),
|
||||||
|
)
|
||||||
|
.child(Story::label("Chord with Modifier (Windows)", cx))
|
||||||
|
.child(
|
||||||
|
KeyBinding::new_from_gpui(binding("ctrl-a shift-z"), cx)
|
||||||
|
.platform_style(PlatformStyle::Windows),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
KeyBinding::new_from_gpui(binding("fn-s"), cx)
|
||||||
.platform_style(PlatformStyle::Windows),
|
.platform_style(PlatformStyle::Windows),
|
||||||
)
|
)
|
||||||
.child(KeyBinding::new(binding("fn-s"), cx).platform_style(PlatformStyle::Windows))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue