use ahash::AHashMap;
use kludgine::app::winit::keyboard::{
Key, KeyCode, ModifiersState, NamedKey, NativeKey, NativeKeyCode, PhysicalKey, SmolStr,
};
use crate::widget::{
EventHandling, MakeWidget, SharedCallback, WidgetRef, WrapperWidget, HANDLED, IGNORED,
};
use crate::window::KeyEvent;
use crate::{ModifiersExt, ModifiersStateExt};
#[derive(Default, Debug, Clone)]
pub struct ShortcutMap(AHashMap<Shortcut, ShortcutConfig>);
impl ShortcutMap {
#[must_use]
pub fn with_shortcut<F>(
mut self,
key: impl Into<ShortcutKey>,
modifiers: ModifiersState,
callback: F,
) -> Self
where
F: FnMut(KeyEvent) -> EventHandling + Send + 'static,
{
self.insert(key.into(), modifiers, callback);
self
}
pub fn insert<F>(&mut self, key: impl Into<ShortcutKey>, modifiers: ModifiersState, callback: F)
where
F: FnMut(KeyEvent) -> EventHandling + Send + 'static,
{
self.insert_shortcut_inner(key.into(), modifiers, false, SharedCallback::new(callback));
}
#[must_use]
pub fn with_repeating_shortcut<F>(
mut self,
key: impl Into<ShortcutKey>,
modifiers: ModifiersState,
callback: F,
) -> Self
where
F: FnMut(KeyEvent) -> EventHandling + Send + 'static,
{
self.insert_repeating(key.into(), modifiers, callback);
self
}
pub fn insert_repeating<F>(
&mut self,
key: impl Into<ShortcutKey>,
modifiers: ModifiersState,
callback: F,
) where
F: FnMut(KeyEvent) -> EventHandling + Send + 'static,
{
self.insert_shortcut_inner(key.into(), modifiers, true, SharedCallback::new(callback));
}
fn insert_shortcut_inner(
&mut self,
key: ShortcutKey,
modifiers: ModifiersState,
repeat: bool,
callback: SharedCallback<KeyEvent, EventHandling>,
) {
let (first, second) = Shortcut { key, modifiers }.into_variations();
let config = ShortcutConfig { repeat, callback };
if let Some(second) = second {
self.0.insert(second, config.clone());
}
self.0.insert(first, config);
}
#[must_use]
pub fn input(&self, input: KeyEvent) -> EventHandling {
for modifiers in FuzzyModifiers(input.modifiers.state()) {
let physical_match = self.0.get(&Shortcut {
key: ShortcutKey::Physical(input.physical_key),
modifiers,
});
let logical_match = self.0.get(&Shortcut {
key: ShortcutKey::Logical(input.logical_key.clone()),
modifiers,
});
match (physical_match, logical_match) {
(Some(physical), Some(logical)) if physical.callback != logical.callback => {
if input.state.is_pressed()
&& (!input.repeat || physical.repeat)
&& physical.callback.invoke(input.clone()).is_break()
{
return HANDLED;
}
return if input.state.is_pressed() && (!input.repeat || logical.repeat) {
logical.callback.invoke(input)
} else {
IGNORED
};
}
(Some(callback), _) | (_, Some(callback)) => {
return if input.state.is_pressed() && (!input.repeat || callback.repeat) {
callback.callback.invoke(input)
} else {
IGNORED
};
}
_ => {}
}
}
IGNORED
}
}
struct FuzzyModifiers(ModifiersState);
impl Iterator for FuzzyModifiers {
type Item = ModifiersState;
fn next(&mut self) -> Option<Self::Item> {
let modifiers = self.0;
if modifiers.is_empty() {
return None;
} else if modifiers.primary() && !modifiers.only_primary() {
self.0 = ModifiersState::PRIMARY;
} else if modifiers.control_key() && !modifiers.only_control() {
self.0 = ModifiersState::CONTROL;
} else if modifiers.super_key() && !modifiers.only_super() {
self.0 = ModifiersState::SUPER;
} else {
self.0 = ModifiersState::empty();
}
Some(modifiers)
}
}
#[derive(Debug)]
pub struct Shortcuts {
shortcuts: ShortcutMap,
child: WidgetRef,
}
impl Shortcuts {
#[must_use]
pub fn new(child: impl MakeWidget) -> Self {
Self {
shortcuts: ShortcutMap::default(),
child: WidgetRef::new(child),
}
}
#[must_use]
pub fn with_shortcut<F>(
mut self,
key: impl Into<ShortcutKey>,
modifiers: ModifiersState,
callback: F,
) -> Self
where
F: FnMut(KeyEvent) -> EventHandling + Send + 'static,
{
self.shortcuts.insert(key, modifiers, callback);
self
}
#[must_use]
pub fn with_repeating_shortcut<F>(
mut self,
key: impl Into<ShortcutKey>,
modifiers: ModifiersState,
callback: F,
) -> Self
where
F: FnMut(KeyEvent) -> EventHandling + Send + 'static,
{
self.shortcuts.insert_repeating(key, modifiers, callback);
self
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
struct Shortcut {
pub key: ShortcutKey,
pub modifiers: ModifiersState,
}
impl Shortcut {
fn into_variations(self) -> (Shortcut, Option<Shortcut>) {
let modifiers = self.modifiers;
let extra = match &self.key {
ShortcutKey::Logical(Key::Character(c)) => {
let lowercase = SmolStr::new(c.to_lowercase());
let uppercase = SmolStr::new(c.to_uppercase());
if c == &lowercase {
Some(Shortcut {
key: uppercase.into(),
modifiers,
})
} else {
Some(Shortcut {
key: lowercase.into(),
modifiers,
})
}
}
_ => None,
};
(self, extra)
}
}
impl From<PhysicalKey> for ShortcutKey {
fn from(key: PhysicalKey) -> Self {
ShortcutKey::Physical(key)
}
}
impl From<Key> for ShortcutKey {
fn from(key: Key) -> Self {
ShortcutKey::Logical(key)
}
}
impl From<NamedKey> for ShortcutKey {
fn from(key: NamedKey) -> Self {
Self::from(Key::from(key))
}
}
impl From<NativeKey> for ShortcutKey {
fn from(key: NativeKey) -> Self {
Self::from(Key::from(key))
}
}
impl From<SmolStr> for ShortcutKey {
fn from(key: SmolStr) -> Self {
Self::from(Key::Character(key))
}
}
impl From<&'_ str> for ShortcutKey {
fn from(key: &'_ str) -> Self {
Self::from(SmolStr::new(key))
}
}
impl From<KeyCode> for ShortcutKey {
fn from(key: KeyCode) -> Self {
Self::from(PhysicalKey::from(key))
}
}
impl From<NativeKeyCode> for ShortcutKey {
fn from(key: NativeKeyCode) -> Self {
Self::from(PhysicalKey::from(key))
}
}
#[derive(Debug, Clone)]
struct ShortcutConfig {
repeat: bool,
callback: SharedCallback<KeyEvent, EventHandling>,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum ShortcutKey {
Logical(Key),
Physical(PhysicalKey),
}
impl WrapperWidget for Shortcuts {
fn child_mut(&mut self) -> &mut crate::widget::WidgetRef {
&mut self.child
}
fn keyboard_input(
&mut self,
_device_id: crate::window::DeviceId,
input: KeyEvent,
_is_synthetic: bool,
_context: &mut crate::context::EventContext<'_>,
) -> EventHandling {
self.shortcuts.input(input)
}
}