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