kbd: Add Kdb to show keystroke. (#695)
<img width="627" alt="image" src="https://github.com/user-attachments/assets/c2a6f385-8820-4629-be57-bb5958e531ce" />
This commit is contained in:
parent
c5416c4774
commit
8611a23aae
4 changed files with 128 additions and 68 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, rems, App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render,
|
div, px, rems, App, AppContext, Context, Entity, Focusable, IntoElement, Keystroke,
|
||||||
SharedString, Styled, Window,
|
ParentElement, Render, SharedString, Styled, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
|
|
@ -11,7 +11,7 @@ use gpui_component::{
|
||||||
link::Link,
|
link::Link,
|
||||||
red_500,
|
red_500,
|
||||||
tag::Tag,
|
tag::Tag,
|
||||||
v_flex, yellow_500, yellow_800, IconName, Sizable, StyledExt,
|
v_flex, yellow_500, yellow_800, IconName, Kbd, Sizable, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::section;
|
use crate::section;
|
||||||
|
|
@ -218,5 +218,16 @@ impl Render for TextStory {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
section("Kbd", cx).child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(Kbd::new(Keystroke::parse("cmd-shift-p").unwrap()))
|
||||||
|
.child(Kbd::new(Keystroke::parse("cmd-ctrl-t").unwrap()))
|
||||||
|
.child(Kbd::new(Keystroke::parse("escape").unwrap()))
|
||||||
|
.child(Kbd::new(Keystroke::parse("backspace").unwrap()))
|
||||||
|
.child(Kbd::new(Keystroke::parse("enter").unwrap())),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
108
crates/ui/src/kbd.rs
Normal file
108
crates/ui/src/kbd.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
use gpui::{div, relative, IntoElement, Keystroke, ParentElement as _, RenderOnce, Styled as _};
|
||||||
|
|
||||||
|
use crate::ActiveTheme;
|
||||||
|
|
||||||
|
/// A key binding tag
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct Kbd {
|
||||||
|
stroke: gpui::Keystroke,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Keystroke> for Kbd {
|
||||||
|
fn from(stroke: Keystroke) -> Self {
|
||||||
|
Self { stroke }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Kbd {
|
||||||
|
pub fn new(stroke: Keystroke) -> Self {
|
||||||
|
Self { stroke }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the Platform specific keybinding string by KeyStroke
|
||||||
|
pub fn format(key: &Keystroke) -> String {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
return format!("{}", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parts = vec![];
|
||||||
|
if key.modifiers.control {
|
||||||
|
parts.push("Ctrl");
|
||||||
|
}
|
||||||
|
if key.modifiers.alt {
|
||||||
|
parts.push("Alt");
|
||||||
|
}
|
||||||
|
if key.modifiers.platform {
|
||||||
|
parts.push("Win");
|
||||||
|
}
|
||||||
|
if key.modifiers.shift {
|
||||||
|
parts.push("Shift");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capitalize the first letter
|
||||||
|
let key = if let Some(first_c) = key.key.chars().next() {
|
||||||
|
format!("{}{}", first_c.to_uppercase(), &key.key[1..])
|
||||||
|
} else {
|
||||||
|
key.key.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
parts.push(&key);
|
||||||
|
parts.join("+")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for Kbd {
|
||||||
|
fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
|
||||||
|
div()
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().border)
|
||||||
|
.text_color(cx.theme().muted_foreground)
|
||||||
|
.py_0p5()
|
||||||
|
.px_1()
|
||||||
|
.rounded_md()
|
||||||
|
.line_height(relative(1.))
|
||||||
|
.text_xs()
|
||||||
|
.child(Self::format(&self.stroke))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn test_format() {
|
||||||
|
use super::Kbd;
|
||||||
|
use gpui::Keystroke;
|
||||||
|
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
assert_eq!(Kbd::format(&Keystroke::parse("a").unwrap()), "A");
|
||||||
|
assert_eq!(Kbd::format(&Keystroke::parse("ctrl-a").unwrap()), "Ctrl+A");
|
||||||
|
assert_eq!(
|
||||||
|
Kbd::format(&Keystroke::parse("ctrl-alt-a").unwrap()),
|
||||||
|
"Ctrl+Alt+A"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Kbd::format(&Keystroke::parse("ctrl-alt-shift-a").unwrap()),
|
||||||
|
"Ctrl+Alt+Shift+A"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Kbd::format(&Keystroke::parse("ctrl-alt-shift-win-a").unwrap()),
|
||||||
|
"Ctrl+Alt+Win+Shift+A"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Kbd::format(&Keystroke::parse("ctrl-shift-backspace").unwrap()),
|
||||||
|
"Ctrl+Shift+Backspace"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert_eq!(Kbd::format(&Keystroke::parse("cmd-a").unwrap()), "⌘A");
|
||||||
|
assert_eq!(Kbd::format(&Keystroke::parse("cmd-ctrl-a").unwrap()), "^⌘A");
|
||||||
|
assert_eq!(
|
||||||
|
Kbd::format(&Keystroke::parse("cmd-ctrl-shift-a").unwrap()),
|
||||||
|
"^⌘⇧A"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Kbd::format(&Keystroke::parse("cmd-ctrl-shift-alt-a").unwrap()),
|
||||||
|
"^⌥⌘⇧A"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ mod colors;
|
||||||
mod event;
|
mod event;
|
||||||
mod focusable;
|
mod focusable;
|
||||||
mod icon;
|
mod icon;
|
||||||
|
mod kbd;
|
||||||
mod root;
|
mod root;
|
||||||
mod styled;
|
mod styled;
|
||||||
mod svg_img;
|
mod svg_img;
|
||||||
|
|
@ -70,6 +71,7 @@ pub use window_border::{window_border, window_paddings, WindowBorder};
|
||||||
|
|
||||||
pub use colors::*;
|
pub use colors::*;
|
||||||
pub use icon::*;
|
pub use icon::*;
|
||||||
|
pub use kbd::*;
|
||||||
pub use svg_img::*;
|
pub use svg_img::*;
|
||||||
pub use theme::*;
|
pub use theme::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
use crate::scroll::{Scrollbar, ScrollbarState};
|
use crate::scroll::{Scrollbar, ScrollbarState};
|
||||||
use crate::StyledExt;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
button::Button, h_flex, list::ListItem, popover::Popover, v_flex, ActiveTheme, Icon, IconName,
|
button::Button, h_flex, list::ListItem, popover::Popover, v_flex, ActiveTheme, Icon, IconName,
|
||||||
Selectable, Sizable as _,
|
Selectable, Sizable as _,
|
||||||
};
|
};
|
||||||
|
use crate::{Kbd, StyledExt};
|
||||||
use gpui::Subscription;
|
use gpui::Subscription;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement, App,
|
actions, anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement, App,
|
||||||
AppContext, Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle,
|
AppContext, Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle,
|
||||||
Focusable, InteractiveElement, IntoElement, KeyBinding, Keystroke, ParentElement, Pixels,
|
Focusable, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, Render,
|
||||||
Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window,
|
ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window,
|
||||||
};
|
};
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
@ -441,7 +441,7 @@ impl PopupMenu {
|
||||||
keybinding
|
keybinding
|
||||||
.keystrokes()
|
.keystrokes()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|key| key_shortcut(key.clone())),
|
.map(|key| Kbd::format(key)),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Some(el);
|
return Some(el);
|
||||||
|
|
@ -710,64 +710,3 @@ impl Render for PopupMenu {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the Platform specific keybinding string by KeyStroke
|
|
||||||
pub fn key_shortcut(key: Keystroke) -> String {
|
|
||||||
if cfg!(target_os = "macos") {
|
|
||||||
return format!("{}", key);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut parts = vec![];
|
|
||||||
if key.modifiers.control {
|
|
||||||
parts.push("Ctrl");
|
|
||||||
}
|
|
||||||
if key.modifiers.alt {
|
|
||||||
parts.push("Alt");
|
|
||||||
}
|
|
||||||
if key.modifiers.platform {
|
|
||||||
parts.push("Win");
|
|
||||||
}
|
|
||||||
if key.modifiers.shift {
|
|
||||||
parts.push("Shift");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capitalize the first letter
|
|
||||||
let key = if let Some(first_c) = key.key.chars().next() {
|
|
||||||
format!("{}{}", first_c.to_uppercase(), &key.key[1..])
|
|
||||||
} else {
|
|
||||||
key.key.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
parts.push(&key);
|
|
||||||
parts.join("+")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn test_key_shortcut() {
|
|
||||||
use super::key_shortcut;
|
|
||||||
use gpui::Keystroke;
|
|
||||||
|
|
||||||
if cfg!(target_os = "windows") {
|
|
||||||
assert_eq!(key_shortcut(Keystroke::parse("a").unwrap()), "A");
|
|
||||||
assert_eq!(key_shortcut(Keystroke::parse("ctrl-a").unwrap()), "Ctrl+A");
|
|
||||||
assert_eq!(
|
|
||||||
key_shortcut(Keystroke::parse("ctrl-alt-a").unwrap()),
|
|
||||||
"Ctrl+Alt+A"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
key_shortcut(Keystroke::parse("ctrl-alt-shift-a").unwrap()),
|
|
||||||
"Ctrl+Alt+Shift+A"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
key_shortcut(Keystroke::parse("ctrl-alt-shift-win-a").unwrap()),
|
|
||||||
"Ctrl+Alt+Win+Shift+A"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
key_shortcut(Keystroke::parse("ctrl-shift-backspace").unwrap()),
|
|
||||||
"Ctrl+Shift+Backspace"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue