1use std::collections::HashSet;
2use std::marker::PhantomData;
3
4use strum::{EnumIter, IntoEnumIterator};
5
6use crate::prelude::*;
7
8#[derive(Element)]
9pub struct Keybinding<S: 'static + Send + Sync> {
10 state_type: PhantomData<S>,
11
12 /// A keybinding consists of a key and a set of modifier keys.
13 /// More then one keybinding produces a chord.
14 ///
15 /// This should always contain at least one element.
16 keybinding: Vec<(String, ModifierKeys)>,
17}
18
19impl<S: 'static + Send + Sync> Keybinding<S> {
20 pub fn new(key: String, modifiers: ModifierKeys) -> Self {
21 Self {
22 state_type: PhantomData,
23 keybinding: vec![(key, modifiers)],
24 }
25 }
26
27 pub fn new_chord(
28 first_note: (String, ModifierKeys),
29 second_note: (String, ModifierKeys),
30 ) -> Self {
31 Self {
32 state_type: PhantomData,
33 keybinding: vec![first_note, second_note],
34 }
35 }
36
37 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl IntoAnyElement<S> {
38 div()
39 .flex()
40 .gap_2()
41 .children(self.keybinding.iter().map(|(key, modifiers)| {
42 div()
43 .flex()
44 .gap_1()
45 .children(ModifierKey::iter().filter_map(|modifier| {
46 if modifiers.0.contains(&modifier) {
47 Some(Key::new(modifier.glyph().to_string()))
48 } else {
49 None
50 }
51 }))
52 .child(Key::new(key.clone()))
53 }))
54 }
55}
56
57#[derive(Element)]
58pub struct Key<S: 'static + Send + Sync> {
59 state_type: PhantomData<S>,
60 key: SharedString,
61}
62
63impl<S: 'static + Send + Sync> Key<S> {
64 pub fn new(key: impl Into<SharedString>) -> Self {
65 Self {
66 state_type: PhantomData,
67 key: key.into(),
68 }
69 }
70
71 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl IntoAnyElement<S> {
72 let theme = theme(cx);
73
74 div()
75 .px_2()
76 .py_0()
77 .rounded_md()
78 .text_sm()
79 .text_color(theme.text)
80 .bg(theme.filled_element)
81 .child(self.key.clone())
82 }
83}
84
85// NOTE: The order the modifier keys appear in this enum impacts the order in
86// which they are rendered in the UI.
87#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
88pub enum ModifierKey {
89 Control,
90 Alt,
91 Command,
92 Shift,
93}
94
95impl ModifierKey {
96 /// Returns the glyph for the [`ModifierKey`].
97 pub fn glyph(&self) -> char {
98 match self {
99 Self::Control => '^',
100 Self::Alt => '⌥',
101 Self::Command => '⌘',
102 Self::Shift => '⇧',
103 }
104 }
105}
106
107#[derive(Clone)]
108pub struct ModifierKeys(HashSet<ModifierKey>);
109
110impl ModifierKeys {
111 pub fn new() -> Self {
112 Self(HashSet::new())
113 }
114
115 pub fn all() -> Self {
116 Self(HashSet::from_iter(ModifierKey::iter()))
117 }
118
119 pub fn add(mut self, modifier: ModifierKey) -> Self {
120 self.0.insert(modifier);
121 self
122 }
123
124 pub fn control(mut self, control: bool) -> Self {
125 if control {
126 self.0.insert(ModifierKey::Control);
127 } else {
128 self.0.remove(&ModifierKey::Control);
129 }
130
131 self
132 }
133
134 pub fn alt(mut self, alt: bool) -> Self {
135 if alt {
136 self.0.insert(ModifierKey::Alt);
137 } else {
138 self.0.remove(&ModifierKey::Alt);
139 }
140
141 self
142 }
143
144 pub fn command(mut self, command: bool) -> Self {
145 if command {
146 self.0.insert(ModifierKey::Command);
147 } else {
148 self.0.remove(&ModifierKey::Command);
149 }
150
151 self
152 }
153
154 pub fn shift(mut self, shift: bool) -> Self {
155 if shift {
156 self.0.insert(ModifierKey::Shift);
157 } else {
158 self.0.remove(&ModifierKey::Shift);
159 }
160
161 self
162 }
163}
164
165#[cfg(feature = "stories")]
166pub use stories::*;
167
168#[cfg(feature = "stories")]
169mod stories {
170 use itertools::Itertools;
171
172 use crate::Story;
173
174 use super::*;
175
176 #[derive(Element)]
177 pub struct KeybindingStory<S: 'static + Send + Sync + Clone> {
178 state_type: PhantomData<S>,
179 }
180
181 impl<S: 'static + Send + Sync + Clone> KeybindingStory<S> {
182 pub fn new() -> Self {
183 Self {
184 state_type: PhantomData,
185 }
186 }
187
188 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl IntoAnyElement<S> {
189 let all_modifier_permutations = ModifierKey::iter().permutations(2);
190
191 Story::container(cx)
192 .child(Story::title_for::<_, Keybinding<S>>(cx))
193 .child(Story::label(cx, "Single Key"))
194 .child(Keybinding::new("Z".to_string(), ModifierKeys::new()))
195 .child(Story::label(cx, "Single Key with Modifier"))
196 .child(
197 div()
198 .flex()
199 .gap_3()
200 .children(ModifierKey::iter().map(|modifier| {
201 Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier))
202 })),
203 )
204 .child(Story::label(cx, "Single Key with Modifier (Permuted)"))
205 .child(
206 div().flex().flex_col().children(
207 all_modifier_permutations
208 .chunks(4)
209 .into_iter()
210 .map(|chunk| {
211 div()
212 .flex()
213 .gap_4()
214 .py_3()
215 .children(chunk.map(|permutation| {
216 let mut modifiers = ModifierKeys::new();
217
218 for modifier in permutation {
219 modifiers = modifiers.add(modifier);
220 }
221
222 Keybinding::new("X".to_string(), modifiers)
223 }))
224 }),
225 ),
226 )
227 .child(Story::label(cx, "Single Key with All Modifiers"))
228 .child(Keybinding::new("Z".to_string(), ModifierKeys::all()))
229 .child(Story::label(cx, "Chord"))
230 .child(Keybinding::new_chord(
231 ("A".to_string(), ModifierKeys::new()),
232 ("Z".to_string(), ModifierKeys::new()),
233 ))
234 .child(Story::label(cx, "Chord with Modifier"))
235 .child(Keybinding::new_chord(
236 ("A".to_string(), ModifierKeys::new().control(true)),
237 ("Z".to_string(), ModifierKeys::new().shift(true)),
238 ))
239 }
240 }
241}