1use std::time::Duration;
16
17use crate::action::Action;
18use crate::binding::KeyPropagation;
19use crate::hotkey::Hotkey;
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[cfg_attr(feature = "serde", serde(transparent))]
31pub struct LayerName(Box<str>);
32
33impl LayerName {
34 #[must_use]
36 pub fn new(value: impl Into<Box<str>>) -> Self {
37 Self(value.into())
38 }
39
40 #[must_use]
42 pub fn as_str(&self) -> &str {
43 &self.0
44 }
45}
46
47impl From<&str> for LayerName {
48 fn from(value: &str) -> Self {
49 Self::new(value)
50 }
51}
52
53impl From<String> for LayerName {
54 fn from(value: String) -> Self {
55 Self::new(value)
56 }
57}
58
59impl std::fmt::Display for LayerName {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.write_str(self.as_str())
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
89#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
90#[non_exhaustive]
91pub enum UnmatchedKeys {
92 #[default]
94 Fallthrough,
95 Swallow,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Default)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102pub struct LayerOptions {
103 oneshot: Option<usize>,
105 unmatched: UnmatchedKeys,
107 timeout: Option<Duration>,
109 description: Option<Box<str>>,
111}
112
113impl LayerOptions {
114 #[must_use]
116 pub const fn oneshot(&self) -> Option<usize> {
117 self.oneshot
118 }
119
120 #[must_use]
122 pub const fn unmatched(&self) -> UnmatchedKeys {
123 self.unmatched
124 }
125
126 #[must_use]
128 pub const fn timeout(&self) -> Option<Duration> {
129 self.timeout
130 }
131
132 #[must_use]
134 pub fn description(&self) -> Option<&str> {
135 self.description.as_deref()
136 }
137
138 #[must_use]
140 pub const fn with_unmatched(mut self, behavior: UnmatchedKeys) -> Self {
141 self.unmatched = behavior;
142 self
143 }
144}
145
146#[derive(Debug)]
148pub(crate) struct LayerBinding {
149 pub(crate) hotkey: Hotkey,
150 pub(crate) action: Action,
151 pub(crate) propagation: KeyPropagation,
152}
153
154pub(crate) struct StoredLayer {
156 pub(crate) bindings: Vec<LayerBinding>,
157 pub(crate) options: LayerOptions,
158}
159
160impl std::fmt::Debug for StoredLayer {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 f.debug_struct("StoredLayer")
163 .field("bindings", &self.bindings.len())
164 .field("options", &self.options)
165 .finish()
166 }
167}
168
169pub struct Layer {
222 name: LayerName,
223 bindings: Vec<LayerBinding>,
224 options: LayerOptions,
225}
226
227impl Layer {
228 #[must_use]
230 pub fn new(name: impl Into<LayerName>) -> Self {
231 Self {
232 name: name.into(),
233 bindings: Vec::new(),
234 options: LayerOptions::default(),
235 }
236 }
237
238 #[must_use]
240 pub fn bind(mut self, hotkey: impl Into<Hotkey>, action: impl Into<Action>) -> Self {
241 self.bindings.push(LayerBinding {
242 hotkey: hotkey.into(),
243 action: action.into(),
244 propagation: KeyPropagation::default(),
245 });
246 self
247 }
248
249 #[must_use]
251 pub fn swallow(mut self) -> Self {
252 self.options.unmatched = UnmatchedKeys::Swallow;
253 self
254 }
255
256 #[must_use]
258 pub fn oneshot(mut self, depth: usize) -> Self {
259 self.options.oneshot = Some(depth);
260 self
261 }
262
263 #[must_use]
265 pub fn timeout(mut self, duration: Duration) -> Self {
266 self.options.timeout = Some(duration);
267 self
268 }
269
270 #[must_use]
274 pub fn description(mut self, description: impl Into<Box<str>>) -> Self {
275 self.options.description = Some(description.into());
276 self
277 }
278
279 #[must_use]
281 pub fn name(&self) -> &LayerName {
282 &self.name
283 }
284
285 #[must_use]
287 pub fn options(&self) -> &LayerOptions {
288 &self.options
289 }
290
291 #[must_use]
293 pub fn binding_count(&self) -> usize {
294 self.bindings.len()
295 }
296
297 #[must_use]
299 pub(crate) fn into_parts(self) -> (LayerName, Vec<LayerBinding>, LayerOptions) {
300 (self.name, self.bindings, self.options)
301 }
302}
303
304impl std::fmt::Debug for Layer {
305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306 f.debug_struct("Layer")
307 .field("name", &self.name)
308 .field("bindings", &self.bindings.len())
309 .field("options", &self.options)
310 .finish()
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use std::time::Duration;
317
318 use super::*;
319 use crate::action::Action;
320 use crate::hotkey::Modifier;
321 use crate::key::Key;
322
323 #[test]
324 fn layer_new_creates_with_name() {
325 let layer = Layer::new("nav");
326 assert_eq!(layer.name().as_str(), "nav");
327 }
328
329 #[test]
330 fn layer_new_has_empty_bindings() {
331 let layer = Layer::new("test");
332 assert_eq!(layer.binding_count(), 0);
333 }
334
335 #[test]
336 fn layer_new_has_default_options() {
337 let layer = Layer::new("test");
338 assert_eq!(*layer.options(), LayerOptions::default());
339 }
340
341 #[test]
342 fn layer_bind_adds_binding() {
343 let layer = Layer::new("nav").bind(Key::H, Action::Suppress);
344 assert_eq!(layer.binding_count(), 1);
345 }
346
347 #[test]
348 fn layer_bind_multiple_bindings() {
349 let layer = Layer::new("nav")
350 .bind(Key::H, Action::Suppress)
351 .bind(Key::J, Action::Suppress)
352 .bind(Key::K, Action::Suppress)
353 .bind(Key::L, Action::Suppress);
354 assert_eq!(layer.binding_count(), 4);
355 }
356
357 #[test]
358 fn layer_bind_preserves_hotkey() {
359 let layer = Layer::new("nav").bind(
360 Hotkey::new(Key::H).modifier(Modifier::Ctrl),
361 Action::Suppress,
362 );
363 let (_, bindings, _) = layer.into_parts();
364 assert_eq!(bindings.len(), 1);
365 assert_eq!(bindings[0].hotkey.key(), Key::H);
366 assert_eq!(bindings[0].hotkey.modifiers(), &[Modifier::Ctrl]);
367 }
368
369 #[test]
370 fn layer_bind_accepts_closure() {
371 let layer = Layer::new("test").bind(Key::A, || println!("fired"));
372 assert_eq!(layer.binding_count(), 1);
373 }
374
375 #[test]
376 fn layer_swallow_sets_option() {
377 let layer = Layer::new("test").swallow();
378 assert_eq!(layer.options().unmatched(), UnmatchedKeys::Swallow);
379 }
380
381 #[test]
382 fn layer_oneshot_sets_depth() {
383 let layer = Layer::new("test").oneshot(3);
384 assert_eq!(layer.options().oneshot(), Some(3));
385 }
386
387 #[test]
388 fn layer_timeout_sets_duration() {
389 let duration = Duration::from_secs(5);
390 let layer = Layer::new("test").timeout(duration);
391 assert_eq!(layer.options().timeout(), Some(duration));
392 }
393
394 #[test]
395 fn layer_builder_chains_all_options() {
396 let layer = Layer::new("nav")
397 .bind(Key::H, Action::Suppress)
398 .bind(Key::J, Action::Suppress)
399 .description("Navigation keys")
400 .swallow()
401 .oneshot(1)
402 .timeout(Duration::from_millis(500));
403
404 assert_eq!(layer.name().as_str(), "nav");
405 assert_eq!(layer.binding_count(), 2);
406 assert_eq!(layer.options().description(), Some("Navigation keys"));
407 assert_eq!(layer.options().unmatched(), UnmatchedKeys::Swallow);
408 assert_eq!(layer.options().oneshot(), Some(1));
409 assert_eq!(layer.options().timeout(), Some(Duration::from_millis(500)));
410 }
411
412 #[test]
413 fn layer_options_default_is_fallthrough_no_oneshot_no_timeout_no_description() {
414 let options = LayerOptions::default();
415 assert_eq!(options.oneshot(), None);
416 assert_eq!(options.unmatched(), UnmatchedKeys::Fallthrough);
417 assert_eq!(options.timeout(), None);
418 assert_eq!(options.description(), None);
419 }
420
421 #[test]
422 fn layer_name_from_string() {
423 let layer = Layer::new(String::from("dynamic"));
424 assert_eq!(layer.name().as_str(), "dynamic");
425 }
426
427 #[test]
428 fn layer_into_parts_decomposes() {
429 let layer = Layer::new("nav").bind(Key::H, Action::Suppress).swallow();
430
431 let (name, bindings, options) = layer.into_parts();
432 assert_eq!(name.as_str(), "nav");
433 assert_eq!(bindings.len(), 1);
434 assert_eq!(options.unmatched(), UnmatchedKeys::Swallow);
435 }
436
437 #[test]
438 fn layer_description_sets_label() {
439 let layer = Layer::new("nav").description("Navigation keys");
440 assert_eq!(layer.options().description(), Some("Navigation keys"));
441 }
442
443 #[test]
444 fn layer_description_preserved_in_into_parts() {
445 let layer = Layer::new("nav")
446 .bind(Key::H, Action::Suppress)
447 .description("Navigation keys");
448
449 let (_, _, options) = layer.into_parts();
450 assert_eq!(options.description(), Some("Navigation keys"));
451 }
452}