use { crate::{ State, config::{Action, InputMode, Shortcut, SimpleCommand}, }, ahash::{AHashMap, AHashSet}, jay_config::keyboard::{ModifiedKeySym, mods::Modifiers}, std::{ cell::{Cell, RefCell}, collections::hash_map::Entry, rc::Rc, }, }; #[derive(Default)] pub struct ModeState { latched: Cell, stack: RefCell>>, slots: RefCell>>, diffs: RefCell>>>, current: RefCell>, } impl ModeState { pub fn clear(&self) { self.slots.borrow_mut().clear(); self.stack.borrow_mut().clear(); self.diffs.borrow_mut().clear(); *self.current.borrow_mut() = Default::default(); } } pub type ConvertedShortcuts = AHashMap; #[derive(Clone)] pub struct ConvertedShortcut { mask: Modifiers, shortcut: Rc, } #[derive(Default)] pub struct ModeSlot { pub mode: RefCell>>, } enum ModeDiff { Bind(ModifiedKeySym, Modifiers, Rc), Unbind(ModifiedKeySym), } impl PartialEq for ConvertedShortcut { fn eq(&self, other: &Self) -> bool { if self.mask != other.mask { return false; } Rc::ptr_eq(&self.shortcut, &other.shortcut) } } impl State { pub fn get_mode_slot(&self, name: &str) -> Rc { let state = &self.persistent.mode_state; state .slots .borrow_mut() .entry(name.to_string()) .or_default() .clone() } pub fn clear_modes_after_reload(&self) { let state = &self.persistent.mode_state; state.slots.borrow_mut().clear(); state.diffs.borrow_mut().clear(); } pub fn init_modes( self: &Rc, shortcuts: &[Shortcut], modes: &AHashMap, ) { let state = &self.persistent.mode_state; let base = self.convert_shortcuts(shortcuts); let stack = &mut *state.stack.borrow_mut(); stack.clear(); stack.push(base.clone()); self.convert_modes(&base, modes); self.apply_shortcuts(&base); state.latched.set(false); } pub fn set_mode(&self, new: &Rc, latch: bool) { let state = &self.persistent.mode_state; self.cancel_mode_latch(); self.apply_shortcuts(new); let stack = &mut *state.stack.borrow_mut(); stack.push(new.clone()); if latch { state.latched.set(true); } } pub fn pop_mode(&self, pop: bool) { let state = &self.persistent.mode_state; let stack = &mut *state.stack.borrow_mut(); if stack.len() < 1 + pop as usize { log::error!("Mode stack is empty"); return; } self.cancel_mode_latch(); if pop { stack.pop(); } else { stack.truncate(1); } let new = stack.last().unwrap(); self.apply_shortcuts(new); } pub fn cancel_mode_latch(&self) { let state = &self.persistent.mode_state; if !state.latched.take() { return; } let stack = &mut *state.stack.borrow_mut(); if stack.len() < 2 { log::error!("Mode is latched but mode stack is empty"); return; } let _ = stack.pop(); let new = stack.last().unwrap(); self.apply_shortcuts(new); } pub fn convert_modes( self: &Rc, base: &ConvertedShortcuts, modes: &AHashMap, ) { let mut pending = AHashSet::new(); let mut out = AHashMap::new(); for (name, mode) in modes { if !out.contains_key(name) { self.convert_mode(&mut out, &mut pending, base, modes, name, mode); } } } fn convert_mode<'a>( self: &Rc, out: &'a mut AHashMap>, pending: &mut AHashSet, base: &ConvertedShortcuts, modes: &AHashMap, mode_name: &String, mode: &InputMode, ) -> Option<&'a ConvertedShortcuts> { if !pending.insert(mode_name.clone()) { log::warn!("Detected loop while converting input mode `{mode_name}`"); return None; } let mut shortcuts = None; if let Some(parent) = &mode.parent { match out.get(parent) { Some(c) => shortcuts = Some((**c).clone()), None => match modes.get(parent) { None => { log::warn!("Input mode `{parent}` does not exist"); } Some(p) => { if let Some(p) = self.convert_mode(out, pending, base, modes, parent, p) { shortcuts = Some(p.clone()); } } }, } } let mut shortcuts = shortcuts.unwrap_or_else(|| base.clone()); self.convert_shortcuts_(&mode.shortcuts, &mut shortcuts); let shortcuts = Rc::new(shortcuts); *self.get_mode_slot(mode_name).mode.borrow_mut() = Some(shortcuts.clone()); let res = out.entry(mode_name.clone()).insert_entry(shortcuts); Some(res.into_mut()) } pub fn convert_shortcuts<'a>( self: &Rc, shortcuts: impl IntoIterator, ) -> Rc { let mut dst = ConvertedShortcuts::new(); self.convert_shortcuts_(shortcuts, &mut dst); Rc::new(dst) } fn convert_shortcuts_<'a>( self: &Rc, shortcuts: impl IntoIterator, dst: &mut ConvertedShortcuts, ) { for sc in shortcuts { match self.convert_shortcut(sc.clone()) { None => dst.remove(&sc.keysym), Some(cs) => dst.insert(sc.keysym, cs), }; } } fn convert_shortcut(self: &Rc, shortcut: Shortcut) -> Option { if let Action::SimpleCommand { cmd: SimpleCommand::None, } = shortcut.action && shortcut.latch.is_none() { return None; } let mut f = shortcut.action.into_shortcut_fn(self); if let Some(l) = shortcut.latch { let l = l.into_rc_fn(self); let s = self.persistent.seat; f = Rc::new(move || { f(); let l = l.clone(); s.latch(move || l()); }); } Some(ConvertedShortcut { mask: shortcut.mask, shortcut: f, }) } pub fn apply_shortcuts(&self, new: &Rc) { let state = &self.persistent.mode_state; let current = &mut *state.current.borrow_mut(); let diffs = self.get_or_create_mode_diffs(current, new); let seat = &self.persistent.seat; for diff in &*diffs { match diff { ModeDiff::Bind(key, mask, f) => { let f = f.clone(); seat.bind_masked(*mask, *key, move || f()); } ModeDiff::Unbind(key) => { seat.unbind(*key); } } } *current = new.clone(); } fn get_or_create_mode_diffs( &self, old: &Rc, new: &Rc, ) -> Rc> { let state = &self.persistent.mode_state; let diffs = &mut *state.diffs.borrow_mut(); match diffs.entry([Rc::as_ptr(old), Rc::as_ptr(new)]) { Entry::Occupied(o) => o.get().clone(), Entry::Vacant(v) => { let mut diffs = vec![]; for (key, sc) in new.iter() { if old.get(key) != Some(sc) { diffs.push(ModeDiff::Bind(*key, sc.mask, sc.shortcut.clone())); } } for key in old.keys() { if !new.contains_key(key) { diffs.push(ModeDiff::Unbind(*key)); } } v.insert(Rc::new(diffs)).clone() } } } }