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 Element<ViewState = 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 Element<ViewState = S> {
72 let color = ThemeColor::new(cx);
73
74 div()
75 .px_2()
76 .py_0()
77 .rounded_md()
78 .text_sm()
79 .text_color(color.text)
80 .bg(color.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(
189 &mut self,
190 _view: &mut S,
191 cx: &mut ViewContext<S>,
192 ) -> 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}