Skip to content

Commit

Permalink
add toggle layer action
Browse files Browse the repository at this point in the history
  • Loading branch information
Univa committed Nov 29, 2023
1 parent 87990f4 commit 6038338
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 8 deletions.
4 changes: 2 additions & 2 deletions docs/src/content/docs/features/feature-via-vial.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ No menu for RGB matrix is provided. RGB backlight animations still need to be im

- Basic keycodes (Basic tab in Via/Vial, or those available in HID keyboard reports)
- Lighting keycodes, except for `QK_BACKLIGHT_TOGGLE_BREATHING`. RGB keycodes only work for underglow, not an RGB backlight matrix.
- Momentary layers, and default layers (MO(x) and DF(x))
- Momentary layers (`MO(x)`), default layers (`DF(x)`), toggle layers (`TG(x)`)
- Custom keycodes (`customKeycodes` in your JSON definition)
- Certain media keycodes. Support for this must be enabled manually. Check the ["Media Keys" doc](../feature-media-keys/)
- QK_OUTPUT_BLUETOOTH and QK_OUTPUT_USB
Expand All @@ -424,7 +424,7 @@ For more information on how these keycodes get converted into `keyberon` actions

# To-do List

- [ ] Tap dance, one shot, layer toggling, one shot layer keycodes (and other keycodes in the "Layers" submenu)
- [ ] Tap-toggle, one shot layer keycodes (and other keycodes in the "Layers" submenu)
- [ ] Dynamic keymap macros (Via)
- [ ] QMK settings (Vial)
- [ ] Dynamic keymap tap dance, combo, key overrides (Vial)
10 changes: 10 additions & 0 deletions keyberon/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ where
/// Fn key. If several layer actions are hold at the same time,
/// the last pressed defines the current layer.
Layer(usize),
/// Switch the current layer until the layer gets toggled again.
/// Make sure to also include a ToggleLayer(x) on layer x, otherwise
/// you will be stuck on that layer. If multiple layers are toggled on,
/// the last toggled layer will be the currently active one.
ToggleLayer(usize),
/// Change the default layer.
DefaultLayer(usize),
/// Perform different actions on key hold/tap (see [`HoldTapAction`]).
Expand Down Expand Up @@ -247,6 +252,11 @@ pub const fn m<T, K>(kcs: &'static &'static [K]) -> Action<T, K> {
Action::MultipleKeyCodes(kcs)
}

/// A shortcut to create a `Action::ToggleLayer`, useful to create compact layout.
pub const fn t<T, K>(layer: usize) -> Action<T, K> {
Action::ToggleLayer(layer)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
148 changes: 143 additions & 5 deletions keyberon/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ impl<T: Copy> CustomEvent<T> {
#[derive(Debug, Eq, PartialEq)]
enum State<T: 'static + Copy, K: 'static + Copy> {
NormalKey { keycode: K, coord: (u8, u8) },
LayerModifier { value: usize, coord: (u8, u8) },
MomentaryLayerModifier { value: usize, coord: (u8, u8) },
ToggleLayerModifier { value: usize },
Custom { value: T, coord: (u8, u8) },
}
impl<T: 'static + Copy, K: 'static + Copy> Copy for State<T, K> {}
Expand All @@ -202,7 +203,7 @@ impl<T: 'static + Copy, K: 'static + Copy> State<T, K> {
}
fn release(&self, c: (u8, u8), custom: &mut CustomEvent<T>) -> Option<Self> {
match *self {
NormalKey { coord, .. } | LayerModifier { coord, .. } if coord == c => None,
NormalKey { coord, .. } | MomentaryLayerModifier { coord, .. } if coord == c => None,
Custom { value, coord } if coord == c => {
custom.update(CustomEvent::Release(value));
None
Expand All @@ -212,7 +213,8 @@ impl<T: 'static + Copy, K: 'static + Copy> State<T, K> {
}
fn get_layer(&self) -> Option<usize> {
match self {
LayerModifier { value, .. } => Some(*value),
MomentaryLayerModifier { value, .. } => Some(*value),
ToggleLayerModifier { value, .. } => Some(*value),
_ => None,
}
}
Expand Down Expand Up @@ -522,7 +524,22 @@ impl<const C: usize, const R: usize, const L: usize, T: 'static + Copy, K: 'stat
}
Layer(value) => {
self.tap_hold_tracker.coord = coord;
let _ = self.states.push(LayerModifier { value, coord });
let _ = self.states.push(MomentaryLayerModifier { value, coord });
}
ToggleLayer(value) => {
self.tap_hold_tracker.coord = coord;
let mut removed = false;
self.states.retain(|s| {
if matches!(s, ToggleLayerModifier { value: layer } if *layer == value) {
removed = true;
false
} else {
true
}
});
if !removed {
let _ = self.states.push(ToggleLayerModifier { value });
}
}
DefaultLayer(value) => {
self.tap_hold_tracker.coord = coord;
Expand Down Expand Up @@ -561,7 +578,7 @@ mod test {
use super::{Event::*, Layout, *};
use crate::action::Action::*;
use crate::action::HoldTapConfig;
use crate::action::{k, l, m};
use crate::action::{k, l, m, t};
use crate::key_code::KeyCode;
use crate::key_code::KeyCode::*;
use std::collections::BTreeSet;
Expand Down Expand Up @@ -1242,4 +1259,125 @@ mod test {
assert_keys(&[Enter], layout.keycodes());
}
}

#[test]
fn toggle_multiple_layers() {
static LAYERS: Layers<2, 1, 5> = [
[[t(1), l(2)]],
[[k(A), t(1)]],
[[t(3), k(B)]],
[[t(3), t(4)]],
[[t(4), t(3)]],
];
let mut layout = Layout::new(LAYERS);
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(0, layout.current_layer());
assert_keys(&[], layout.keycodes());

// toggle L1
layout.event(Press(0, 0));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(1, layout.current_layer());
layout.event(Release(0, 0));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(1, layout.current_layer());
assert_keys(&[], layout.keycodes());

// press and release A
layout.event(Press(0, 0));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_keys(&[A], layout.keycodes());
layout.event(Release(0, 0));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_keys(&[], layout.keycodes());

// toggle L1 to disable
layout.event(Press(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(0, layout.current_layer());
layout.event(Release(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(0, layout.current_layer());
assert_keys(&[], layout.keycodes());

// press L2
layout.event(Press(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(2, layout.current_layer());
assert_keys(&[], layout.keycodes());

// toggle L3 on L2
layout.event(Press(0, 0));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(3, layout.current_layer());
assert_keys(&[], layout.keycodes());

// release L2, should stay on L3
layout.event(Release(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(3, layout.current_layer());
assert_keys(&[], layout.keycodes());

// press and release L4 on L3
layout.event(Press(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(4, layout.current_layer());
layout.event(Release(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(4, layout.current_layer());

// toggle L3 from L4, should stay on L4
layout.event(Press(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(4, layout.current_layer());
layout.event(Release(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(4, layout.current_layer());

// toggle L4 to disable, should be back to L0
layout.event(Press(0, 0));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(0, layout.current_layer());
assert_keys(&[], layout.keycodes());

// press L2
layout.event(Press(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(2, layout.current_layer());
assert_keys(&[], layout.keycodes());

// toggle L3 on L2
layout.event(Press(0, 0));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(3, layout.current_layer());
assert_keys(&[], layout.keycodes());

// release L2, should stay on L3
layout.event(Release(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(3, layout.current_layer());
assert_keys(&[], layout.keycodes());

// press and release L4 on L3
layout.event(Press(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(4, layout.current_layer());
layout.event(Release(0, 1));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(4, layout.current_layer());

// toggle L4 to disable, should be back to L3
layout.event(Press(0, 0));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(3, layout.current_layer());
layout.event(Release(0, 0));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(3, layout.current_layer());

// toggle L3 to disable, back to L0
layout.event(Press(0, 0));
assert_eq!(CustomEvent::NoEvent, layout.tick());
assert_eq!(0, layout.current_layer());
assert_keys(&[], layout.keycodes());
}
}
20 changes: 19 additions & 1 deletion rumcake/src/via/protocol_12/keycodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ enum QMKKeycodeRanges {
QK_MOMENTARY_MAX = 0x523F,
QK_DEF_LAYER = 0x5240,
QK_DEF_LAYER_MAX = 0x525F,
QK_TOGGLE_LAYER = 0x5260, // TODO: unhandled
QK_TOGGLE_LAYER = 0x5260,
QK_TOGGLE_LAYER_MAX = 0x527F,
QK_ONE_SHOT_LAYER = 0x5280, // TODO: unhandled, switch to kanata keyberon fork
QK_ONE_SHOT_LAYER_MAX = 0x529F,
Expand Down Expand Up @@ -922,6 +922,16 @@ pub(crate) fn convert_action_to_keycode(action: Action<Keycode>) -> u16 {
UNKNOWN_KEYCODE
}
}
Action::ToggleLayer(layer) => {
if (layer as u16)
<= QMKKeycodeRanges::QK_TOGGLE_LAYER_MAX as u16
- QMKKeycodeRanges::QK_TOGGLE_LAYER as u16
{
QMKKeycodeRanges::QK_TOGGLE_LAYER as u16 + layer as u16
} else {
UNKNOWN_KEYCODE
}
}
Action::HoldTap(_) => todo!(),
Action::Custom(key) => match key {
Keycode::Custom(id) => {
Expand Down Expand Up @@ -1323,6 +1333,14 @@ pub(crate) fn convert_keycode_to_action(keycode: u16) -> Option<Action<Keycode>>
));
}

if QMKKeycodeRanges::QK_TOGGLE_LAYER as u16 <= keycode
&& keycode <= QMKKeycodeRanges::QK_TOGGLE_LAYER_MAX as u16
{
return Some(Action::DefaultLayer(
(keycode - QMKKeycodeRanges::QK_TOGGLE_LAYER as u16) as usize,
));
}

if QMKKeycodeRanges::QK_LIGHTING as u16 <= keycode
&& keycode <= QMKKeycodeRanges::QK_LIGHTING_MAX as u16
{
Expand Down

0 comments on commit 6038338

Please sign in to comment.