Skip to main content

rmk_types/
combo.rs

1//! Combo configuration types shared between firmware and protocol layers.
2
3use heapless::Vec;
4use postcard::experimental::max_size::MaxSize;
5use serde::{Deserialize, Serialize};
6
7use crate::action::KeyAction;
8use crate::constants::COMBO_SIZE;
9
10/// Configuration data for a combo.
11///
12/// A combo triggers an output action when a set of keys are pressed simultaneously.
13/// The maximum number of trigger keys is determined by `COMBO_SIZE` (from `constants.rs`,
14/// generated at build time from `keyboard.toml` on firmware or fixed upper bound on host).
15/// Actions are stored in a Vec — only meaningful keys are present (no `KeyAction::No` padding).
16///
17/// Note: `COMBO_SIZE` is a **wire-format** capacity — on firmware it equals
18/// `COMBO_MAX_LENGTH` (from `keyboard.toml`), on host it's a fixed upper bound.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[cfg_attr(feature = "defmt", derive(defmt::Format))]
21#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
22#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
23pub struct Combo {
24    #[cfg_attr(feature = "wasm", tsify(type = "KeyAction[]"))]
25    pub actions: Vec<KeyAction, COMBO_SIZE>,
26    pub output: KeyAction,
27    pub layer: Option<u8>,
28}
29
30impl MaxSize for Combo {
31    const POSTCARD_MAX_SIZE: usize = crate::heapless_vec_max_size::<KeyAction, COMBO_SIZE>()
32        + KeyAction::POSTCARD_MAX_SIZE
33        + Option::<u8>::POSTCARD_MAX_SIZE;
34}
35
36impl Combo {
37    /// Create a new combo from an iterator of key actions.
38    ///
39    /// Actions equal to `KeyAction::No` are filtered out. If there are more
40    /// non-No actions than `COMBO_SIZE`, excess actions are silently dropped.
41    pub fn new<I: IntoIterator<Item = KeyAction>>(actions: I, output: KeyAction, layer: Option<u8>) -> Self {
42        let mut combo_actions = Vec::new();
43        for action in actions {
44            if action != KeyAction::No && combo_actions.push(action).is_err() {
45                break;
46            }
47        }
48        Self {
49            actions: combo_actions,
50            output,
51            layer,
52        }
53    }
54
55    /// Get an empty combo.
56    pub fn empty() -> Self {
57        Self {
58            actions: Vec::new(),
59            output: KeyAction::No,
60            layer: None,
61        }
62    }
63
64    /// Returns the number of key actions in the combo.
65    pub fn size(&self) -> usize {
66        self.actions.len()
67    }
68
69    /// Find the index of a key action in the combo.
70    pub fn find_key_action_index(&self, key_action: &KeyAction) -> Option<usize> {
71        self.actions.iter().position(|a| a == key_action)
72    }
73
74    /// Check whether the combo contains the given key action.
75    pub fn contains(&self, key_action: &KeyAction) -> bool {
76        self.actions.contains(key_action)
77    }
78}