use std::{
collections::{HashMap, HashSet},
ops::{BitOr, BitOrAssign},
};
use keycode::KeyMappingCode;
use tinyset::SetU32;
struct Trigger {
trigger: SetU32,
on_pressed: Box<dyn FnMut() -> ListeningCmd>,
on_released: Option<Box<dyn FnMut() -> ListeningCmd>>,
}
#[derive(Default, Clone, Copy)]
#[must_use]
pub enum ListeningCmd {
#[default]
Continue,
Stop,
}
impl From<()> for ListeningCmd {
fn from(_: ()) -> Self {
Default::default()
}
}
impl BitOr for ListeningCmd {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(ListeningCmd::Continue, ListeningCmd::Continue) => ListeningCmd::Continue,
_ => ListeningCmd::Stop,
}
}
}
impl BitOrAssign for ListeningCmd {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
}
pub struct KeyboardTriggers {
keyboard_name: String,
triggers: Vec<Trigger>,
current_pressed: Option<usize>,
take_ownership: bool,
}
impl KeyboardTriggers {
pub fn new(keyboard_name: impl ToString) -> Self {
Self {
keyboard_name: keyboard_name.to_string(),
triggers: Vec::new(),
current_pressed: None,
take_ownership: true,
}
}
pub fn new_keep_keyboard(name: impl ToString) -> Self {
Self {
keyboard_name: name.to_string(),
triggers: Vec::new(),
current_pressed: None,
take_ownership: false,
}
}
pub fn insert<P, C>(&mut self, keys: &[KeyMappingCode], mut on_pressed: P)
where
C: Into<ListeningCmd>,
P: FnMut() -> C + 'static,
{
self.insert_boxed(keys, Box::new(move || on_pressed().into()), None)
}
pub fn insert_with_release<P, R, C1, C2>(
&mut self,
keys: &[KeyMappingCode],
mut on_pressed: P,
mut on_released: R,
) where
C1: Into<ListeningCmd>,
C2: Into<ListeningCmd>,
P: FnMut() -> C1 + 'static,
R: FnMut() -> C2 + 'static,
{
self.insert_boxed(
keys,
Box::new(move || on_pressed().into()),
Some(Box::new(move || on_released().into())),
)
}
fn insert_boxed(
&mut self,
keys: &[KeyMappingCode],
on_pressed: Box<dyn FnMut() -> ListeningCmd>,
on_released: Option<Box<dyn FnMut() -> ListeningCmd>>,
) {
self.triggers.push(Trigger {
trigger: keys.iter().copied().map(crate::platform::keymap).collect(),
on_pressed,
on_released,
});
}
}
#[derive(Default)]
pub struct Triggers {
candidates: HashMap<String, KeyboardTriggers>,
}
impl Triggers {
pub fn insert(&mut self, triggers: KeyboardTriggers) {
self.candidates
.insert(triggers.keyboard_name.clone(), triggers);
}
pub fn listen(self) {
crate::platform::run_input_handler(self);
}
pub(crate) fn devices_to_own(&self) -> HashSet<String> {
self.candidates
.iter()
.filter(|(_, v)| v.take_ownership)
.map(|(k, _)| k.clone())
.collect()
}
pub(crate) fn try_run(&mut self, keyboard: &str, keys: &SetU32) -> (bool, ListeningCmd) {
let mut triggered = false;
let mut cmd = ListeningCmd::default();
if let Some(candidates) = self.candidates.get_mut(keyboard) {
let mut to_release = None;
for (i, trigger) in candidates.triggers.iter_mut().enumerate() {
if *keys == trigger.trigger {
triggered = true;
to_release = candidates.current_pressed;
candidates.current_pressed = Some(i);
cmd |= (trigger.on_pressed)();
break;
}
}
if let Some(release) = to_release {
if let Some(release) = &mut candidates
.triggers
.get_mut(release)
.expect("to release trigger not found")
.on_released
{
cmd |= release();
}
}
}
(triggered, cmd)
}
pub(crate) fn release(&mut self, keyboard: &str) -> ListeningCmd {
let mut cmd = ListeningCmd::default();
if let Some(candidates) = self.candidates.get_mut(keyboard) {
if let Some(release) = candidates.current_pressed {
if let Some(release) = &mut candidates
.triggers
.get_mut(release)
.expect("to release trigger not found")
.on_released
{
cmd |= release();
}
candidates.current_pressed = None;
}
}
cmd
}
}