rmk_types/
modifier.rs

1//! Modifier keys and their operations.
2//!
3//! This module provides efficient handling of keyboard modifier states using
4//! bitfield structures. It supports both left and right variants of all
5//! standard modifiers (Ctrl, Shift, Alt, GUI).
6use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
7
8use bitfield_struct::bitfield;
9use postcard::experimental::max_size::MaxSize;
10use serde::{Deserialize, Serialize};
11
12/// The bit representation of the modifier combination.
13#[bitfield(u8, order = Lsb, defmt = cfg(feature = "defmt"))]
14#[derive(Serialize, Deserialize, MaxSize, Eq, PartialEq)]
15
16pub struct ModifierCombination {
17    #[bits(1)]
18    pub left_ctrl: bool,
19    #[bits(1)]
20    pub left_shift: bool,
21    #[bits(1)]
22    pub left_alt: bool,
23    #[bits(1)]
24    pub left_gui: bool,
25    #[bits(1)]
26    pub right_ctrl: bool,
27    #[bits(1)]
28    pub right_shift: bool,
29    #[bits(1)]
30    pub right_alt: bool,
31    #[bits(1)]
32    pub right_gui: bool,
33}
34
35impl BitOr for ModifierCombination {
36    type Output = Self;
37
38    fn bitor(self, rhs: Self) -> Self::Output {
39        Self::from_bits(self.into_bits() | rhs.into_bits())
40    }
41}
42
43impl BitAnd for ModifierCombination {
44    type Output = Self;
45
46    fn bitand(self, rhs: Self) -> Self::Output {
47        Self::from_bits(self.into_bits() & rhs.into_bits())
48    }
49}
50
51impl BitAndAssign for ModifierCombination {
52    fn bitand_assign(&mut self, rhs: Self) {
53        *self = *self & rhs;
54    }
55}
56
57impl BitOrAssign for ModifierCombination {
58    fn bitor_assign(&mut self, rhs: Self) {
59        *self = *self | rhs;
60    }
61}
62
63impl Not for ModifierCombination {
64    type Output = Self;
65
66    fn not(self) -> Self::Output {
67        Self::from_bits(!self.into_bits())
68    }
69}
70
71impl ModifierCombination {
72    pub const LCTRL: Self = Self::new().with_left_ctrl(true);
73    pub const LSHIFT: Self = Self::new().with_left_shift(true);
74    pub const LALT: Self = Self::new().with_left_alt(true);
75    pub const LGUI: Self = Self::new().with_left_gui(true);
76
77    pub const RCTRL: Self = Self::new().with_right_ctrl(true);
78    pub const RSHIFT: Self = Self::new().with_right_shift(true);
79    pub const RALT: Self = Self::new().with_right_alt(true);
80    pub const RGUI: Self = Self::new().with_right_gui(true);
81
82    pub const fn new_from(right: bool, gui: bool, alt: bool, shift: bool, ctrl: bool) -> Self {
83        if right {
84            ModifierCombination::new()
85                .with_right_gui(gui)
86                .with_right_alt(alt)
87                .with_right_shift(shift)
88                .with_right_ctrl(ctrl)
89        } else {
90            ModifierCombination::new()
91                .with_left_gui(gui)
92                .with_left_alt(alt)
93                .with_left_shift(shift)
94                .with_left_ctrl(ctrl)
95        }
96    }
97
98    #[allow(clippy::too_many_arguments)]
99    pub const fn new_from_vals(
100        left_ctrl: bool,
101        left_shift: bool,
102        left_alt: bool,
103        left_gui: bool,
104        right_ctrl: bool,
105        right_shift: bool,
106        right_alt: bool,
107        right_gui: bool,
108    ) -> Self {
109        ModifierCombination::new()
110            .with_left_ctrl(left_ctrl)
111            .with_left_shift(left_shift)
112            .with_left_alt(left_alt)
113            .with_left_gui(left_gui)
114            .with_right_ctrl(right_ctrl)
115            .with_right_shift(right_shift)
116            .with_right_alt(right_alt)
117            .with_right_gui(right_gui)
118    }
119
120    /// Convert current modifier into packed bits:
121    ///
122    /// | bit4 | bit3 | bit2 | bit1 | bit0 |
123    /// | --- | --- | --- | --- | --- |
124    /// | L/R | GUI | ALT |SHIFT| CTRL|
125    ///
126    /// WARN: Since the packed version cannot represent the state that BOTH left and right modifier is present,
127    /// the left side has higher priority
128    pub const fn into_packed_bits(self) -> u8 {
129        let bits = self.into_bits();
130        if bits == 0 {
131            return 0;
132        }
133        let left_bits = bits & 0x0F; // Extract left side modifiers (bits 0-3)
134        let right_bits = bits >> 4; // Extract right side modifiers (bits 4-7)
135
136        // If left side has any modifiers, use left; otherwise use right with bit 4 set
137        if left_bits != 0 {
138            left_bits
139        } else {
140            right_bits | 0x10 // Set bit 4 to indicate right side
141        }
142    }
143
144    /// Convert packed bits back into ModifierCombination:
145    ///
146    /// | bit4 | bit3 | bit2 | bit1 | bit0 |
147    /// | --- | --- | --- | --- | --- |
148    /// | L/R | GUI | ALT |SHIFT| CTRL|
149    ///
150    /// If bit4 is 0, modifiers are applied to left side, otherwise right side
151    pub const fn from_packed_bits(bits: u8) -> Self {
152        let modifier_bits = bits & 0x0F; // Extract modifier bits (0-3)
153        let is_right = (bits & 0x10) != 0; // Check if bit 4 is set (right side)
154
155        if is_right {
156            Self::from_bits(modifier_bits << 4) // Shift to right side position
157        } else {
158            Self::from_bits(modifier_bits) // Use as left side
159        }
160    }
161}