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, 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()))
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: String,
62}
63
64impl<S: 'static + Send + Sync> Key<S> {
65 pub fn new<K>(key: K) -> Self
66 where
67 K: Into<String>,
68 {
69 Self {
70 state_type: PhantomData,
71 key: key.into(),
72 }
73 }
74
75 fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
76 let theme = theme(cx);
77
78 div()
79 .px_2()
80 .py_0()
81 .rounded_md()
82 .text_sm()
83 .text_color(theme.lowest.on.default.foreground)
84 .fill(theme.lowest.on.default.background)
85 .child(self.key.clone())
86 }
87}
88
89// NOTE: The order the modifier keys appear in this enum impacts the order in
90// which they are rendered in the UI.
91#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
92pub enum ModifierKey {
93 Control,
94 Alt,
95 Command,
96 Shift,
97}
98
99impl ModifierKey {
100 /// Returns the glyph for the [`ModifierKey`].
101 pub fn glyph(&self) -> char {
102 match self {
103 Self::Control => '^',
104 Self::Alt => '⌥',
105 Self::Command => '⌘',
106 Self::Shift => '⇧',
107 }
108 }
109}
110
111#[derive(Clone)]
112pub struct ModifierKeys(HashSet<ModifierKey>);
113
114impl ModifierKeys {
115 pub fn new() -> Self {
116 Self(HashSet::new())
117 }
118
119 pub fn all() -> Self {
120 Self(HashSet::from_iter(ModifierKey::iter()))
121 }
122
123 pub fn add(mut self, modifier: ModifierKey) -> Self {
124 self.0.insert(modifier);
125 self
126 }
127
128 pub fn control(mut self, control: bool) -> Self {
129 if control {
130 self.0.insert(ModifierKey::Control);
131 } else {
132 self.0.remove(&ModifierKey::Control);
133 }
134
135 self
136 }
137
138 pub fn alt(mut self, alt: bool) -> Self {
139 if alt {
140 self.0.insert(ModifierKey::Alt);
141 } else {
142 self.0.remove(&ModifierKey::Alt);
143 }
144
145 self
146 }
147
148 pub fn command(mut self, command: bool) -> Self {
149 if command {
150 self.0.insert(ModifierKey::Command);
151 } else {
152 self.0.remove(&ModifierKey::Command);
153 }
154
155 self
156 }
157
158 pub fn shift(mut self, shift: bool) -> Self {
159 if shift {
160 self.0.insert(ModifierKey::Shift);
161 } else {
162 self.0.remove(&ModifierKey::Shift);
163 }
164
165 self
166 }
167}
168
169#[cfg(feature = "stories")]
170pub use stories::*;
171
172#[cfg(feature = "stories")]
173mod stories {
174 use itertools::Itertools;
175
176 use crate::Story;
177
178 use super::*;
179
180 #[derive(Element)]
181 pub struct KeybindingStory<S: 'static + Send + Sync + Clone> {
182 state_type: PhantomData<S>,
183 }
184
185 impl<S: 'static + Send + Sync + Clone> KeybindingStory<S> {
186 pub fn new() -> Self {
187 Self {
188 state_type: PhantomData,
189 }
190 }
191
192 fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
193 let all_modifier_permutations = ModifierKey::iter().permutations(2);
194
195 Story::container(cx)
196 .child(Story::title_for::<_, Keybinding<S>>(cx))
197 .child(Story::label(cx, "Single Key"))
198 .child(Keybinding::new("Z".to_string(), ModifierKeys::new()))
199 .child(Story::label(cx, "Single Key with Modifier"))
200 .child(
201 div()
202 .flex()
203 .gap_3()
204 .children(ModifierKey::iter().map(|modifier| {
205 Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier))
206 })),
207 )
208 .child(Story::label(cx, "Single Key with Modifier (Permuted)"))
209 .child(
210 div().flex().flex_col().children(
211 all_modifier_permutations
212 .chunks(4)
213 .into_iter()
214 .map(|chunk| {
215 div()
216 .flex()
217 .gap_4()
218 .py_3()
219 .children(chunk.map(|permutation| {
220 let mut modifiers = ModifierKeys::new();
221
222 for modifier in permutation {
223 modifiers = modifiers.add(modifier);
224 }
225
226 Keybinding::new("X".to_string(), modifiers)
227 }))
228 }),
229 ),
230 )
231 .child(Story::label(cx, "Single Key with All Modifiers"))
232 .child(Keybinding::new("Z".to_string(), ModifierKeys::all()))
233 .child(Story::label(cx, "Chord"))
234 .child(Keybinding::new_chord(
235 ("A".to_string(), ModifierKeys::new()),
236 ("Z".to_string(), ModifierKeys::new()),
237 ))
238 .child(Story::label(cx, "Chord with Modifier"))
239 .child(Keybinding::new_chord(
240 ("A".to_string(), ModifierKeys::new().control(true)),
241 ("Z".to_string(), ModifierKeys::new().shift(true)),
242 ))
243 }
244 }
245}