#[cfg(test)]
mod tests;
#[expect(unused_imports)]
use crate::{routine::RoutineBuilder, state_machine::State, xkb::Keymap};
use {
crate::{
GroupType, Keycode, Keysym, ModifierIndex, ModifierMask,
group::GroupIndex,
key_storage::KeyStorage,
lookup::{self, LookupTable},
routine::{Global, Routine},
state_machine::{self, StateMachine},
},
hashbrown::HashMap,
isnt::std_1::primitive::IsntSliceExt,
smallvec::SmallVec,
};
#[derive(Clone, Default, Debug)]
pub struct Builder {
next_global: u32,
ctrl: Option<ModifierMask>,
caps: Option<ModifierMask>,
keys: HashMap<Keycode, BuilderKey>,
}
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
pub enum Redirect {
#[default]
Wrap,
Clamp,
Fixed(usize),
}
impl Redirect {
#[inline]
pub(crate) fn constrain(mut self, len: usize) -> Self {
if let Redirect::Fixed(n) = &mut self {
if *n >= len {
*n = 0;
}
}
self
}
#[inline]
pub(crate) fn apply(self, group: GroupIndex, len: usize) -> usize {
let n = group.0 as usize;
if n >= len {
match self {
Redirect::Wrap => n % len,
Redirect::Clamp => {
if (group.0 as i32) < 0 {
0
} else {
len - 1
}
}
Redirect::Fixed(f) => f,
}
} else {
n
}
}
}
#[derive(Clone, Default, Debug)]
struct BuilderKey {
repeats: bool,
groups: Vec<Option<BuilderGroup>>,
redirect: Redirect,
routine: Option<Routine>,
}
#[derive(Clone, Debug)]
struct BuilderGroup {
ty: GroupType,
levels: Vec<Option<BuilderLevel>>,
}
#[derive(Clone, Default, Debug)]
struct BuilderLevel {
keysyms: SmallVec<[Keysym; 1]>,
routine: Option<Routine>,
}
#[derive(Clone, Debug)]
pub struct KeyBuilder {
code: Keycode,
key: BuilderKey,
}
#[derive(Clone, Debug)]
pub struct GroupBuilder {
idx: usize,
group: BuilderGroup,
}
#[derive(Clone, Debug)]
pub struct LevelBuilder {
idx: usize,
level: BuilderLevel,
}
impl Builder {
pub fn add_global(&mut self) -> Global {
let g = Global(self.next_global);
self.next_global = self.next_global.checked_add(1).unwrap();
g
}
pub fn set_ctrl(&mut self, ctrl: Option<ModifierIndex>) {
self.ctrl = ctrl.map(|c| c.to_mask());
}
pub fn set_caps(&mut self, caps: Option<ModifierIndex>) {
self.caps = caps.map(|c| c.to_mask());
}
pub fn add_key(&mut self, key: KeyBuilder) {
self.keys.insert(key.code, key.key);
}
pub fn build_state_machine(&self) -> StateMachine {
let mut map = HashMap::with_capacity(self.keys.len());
let mut has_layer1 = false;
let mut num_groups = 0;
for (keycode, key) in &self.keys {
let mut groups = Vec::with_capacity(key.groups.len());
num_groups = num_groups.max(key.groups.len());
for group in &key.groups {
match group {
None => groups.push(None),
Some(g) => {
let mut levels = Vec::with_capacity(g.levels.len());
for level in &g.levels {
match level {
None => levels.push(state_machine::KeyLevel::default()),
Some(l) => levels.push(state_machine::KeyLevel {
routine: l.routine.clone(),
}),
}
}
while let Some(level) = levels.last() {
if level.routine.is_none() {
levels.pop();
} else {
break;
}
}
if levels.is_empty() {
groups.push(None);
} else {
groups.push(Some(state_machine::KeyGroup {
ty: g.ty.clone(),
levels: levels.into_boxed_slice(),
}));
}
}
}
}
if groups.iter().all(|g| g.is_none()) {
groups.clear();
}
if groups.is_not_empty() || key.routine.is_some() {
has_layer1 |= key.routine.is_some();
map.insert(
*keycode,
state_machine::KeyGroups {
redirect: key.redirect.constrain(groups.len()),
groups: groups.into_boxed_slice(),
routine: key.routine.clone(),
},
);
}
}
StateMachine {
num_groups: (num_groups as u32).max(1),
num_globals: self.next_global as usize,
keys: KeyStorage::new(map),
has_layer1,
}
}
pub fn build_lookup_table(&self) -> LookupTable {
let mut map = HashMap::with_capacity(self.keys.len());
for (keycode, key) in &self.keys {
let mut any_groups = false;
let mut groups = Vec::with_capacity(key.groups.len());
for group in &key.groups {
match group {
None => groups.push(None),
Some(g) => {
let mut any_levels = false;
let mut levels = Vec::with_capacity(g.levels.len());
for level in &g.levels {
match level {
None => levels.push(lookup::KeyLevel::default()),
Some(l) => {
any_levels |= l.keysyms.is_not_empty();
levels.push(lookup::KeyLevel {
symbols: l.keysyms.clone(),
})
}
}
}
any_groups |= any_levels;
groups.push(Some(lookup::KeyGroup {
ty: g.ty.clone(),
levels: levels.into_boxed_slice(),
}));
}
}
}
if any_groups || !key.repeats {
map.insert(
*keycode,
lookup::KeyGroups {
repeats: key.repeats,
redirect: key.redirect.constrain(groups.len()),
groups: groups.into_boxed_slice(),
},
);
}
}
LookupTable {
ctrl: self.ctrl,
caps: self.caps,
keys: KeyStorage::new(map),
}
}
}
impl KeyBuilder {
pub fn new(key: Keycode) -> Self {
Self {
code: key,
key: Default::default(),
}
}
pub fn repeats(&mut self, repeats: bool) {
self.key.repeats = repeats;
}
pub fn redirect(&mut self, redirect: Redirect) {
self.key.redirect = redirect;
}
pub fn add_group(&mut self, group: GroupBuilder) {
if self.key.groups.len() <= group.idx {
self.key.groups.resize_with(group.idx + 1, Default::default);
}
self.key.groups[group.idx] = Some(group.group);
}
pub fn routine(&mut self, routine: &Routine) -> &mut Self {
self.key.routine = Some(routine.clone());
self
}
}
impl GroupBuilder {
pub fn new(idx: usize, ty: &GroupType) -> Self {
Self {
idx,
group: BuilderGroup {
ty: ty.clone(),
levels: vec![],
},
}
}
pub fn add_level(&mut self, level: LevelBuilder) {
if self.group.levels.len() <= level.idx {
self.group
.levels
.resize_with(level.idx + 1, Default::default);
}
self.group.levels[level.idx] = Some(level.level);
}
}
impl LevelBuilder {
pub fn new(idx: usize) -> Self {
Self {
idx,
level: Default::default(),
}
}
pub fn routine(&mut self, routine: &Routine) -> &mut Self {
self.level.routine = Some(routine.clone());
self
}
pub fn keysyms(&mut self, keysyms: &[Keysym]) -> &mut Self {
self.level.keysyms.clear();
self.level.keysyms.extend_from_slice(keysyms);
self
}
}