use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::{Rc, Weak};
use incular_core::{Code, KeyboardEvent, KeyboardKey, Modifiers};
use crate::details::GestureCallbacks;
impl GestureCallbacks {
#[must_use]
pub fn has_keyboard_listener(&self) -> bool {
self.on_key.is_some()
|| self.on_key_down.is_some()
|| self.on_key_repeat.is_some()
|| self.on_key_up.is_some()
|| self.on_shortcut.is_some()
|| self.focus_node.is_some()
}
#[must_use]
pub fn services_semantic_keys(&self) -> bool {
self.has_keyboard_listener() && self.include_semantics
}
#[must_use]
pub fn handle_keyboard(&self, event: KeyboardEvent) -> bool {
if self
.on_shortcut
.as_ref()
.is_some_and(|callback| callback(event.clone()))
{
return true;
}
if self
.on_key
.as_ref()
.is_some_and(|callback| callback(event.clone()))
{
return true;
}
if event.state.is_down() {
if let Some(callback) = &self.on_key_down {
callback(event.clone());
}
if event.repeat
&& let Some(callback) = &self.on_key_repeat
{
callback(event);
}
} else if let Some(callback) = &self.on_key_up {
callback(event);
}
false
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ShortcutKey {
pub code: Code,
pub modifiers: Modifiers,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Command(String);
impl Command {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
#[must_use]
pub fn name(&self) -> &str {
&self.0
}
}
impl From<&str> for Command {
fn from(value: &str) -> Self {
Self::new(value)
}
}
pub trait CommandId: Clone + Eq + std::hash::Hash + 'static {}
impl<T> CommandId for T where T: Clone + Eq + std::hash::Hash + 'static {}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Intent<C: CommandId = Command> {
command: C,
}
impl<C: CommandId> Intent<C> {
#[must_use]
pub fn new(command: impl Into<C>) -> Self {
Self {
command: command.into(),
}
}
#[must_use]
pub fn command(&self) -> &C {
&self.command
}
}
impl Intent<Command> {
#[must_use]
pub fn name(&self) -> &str {
self.command.name()
}
}
impl<C: CommandId> From<C> for Intent<C> {
fn from(value: C) -> Self {
Self::new(value)
}
}
impl From<&str> for Intent {
fn from(value: &str) -> Self {
Self::new(value)
}
}
#[derive(Clone)]
pub struct ErasedIntent {
command: Rc<dyn Any>,
}
impl ErasedIntent {
#[must_use]
pub fn from_intent<C: CommandId>(intent: &Intent<C>) -> Self {
Self {
command: Rc::new(intent.command().clone()),
}
}
#[must_use]
pub fn downcast_command<C: CommandId>(&self) -> Option<&C> {
self.command.as_ref().downcast_ref::<C>()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ActionResult {
Handled,
Ignored,
}
impl From<bool> for ActionResult {
fn from(handled: bool) -> Self {
if handled {
Self::Handled
} else {
Self::Ignored
}
}
}
struct ActionListenerState {
listeners: RefCell<Vec<Weak<ActionListenerEntry>>>,
}
struct ActionListenerEntry {
active: Cell<bool>,
callback: Rc<dyn Fn()>,
}
pub struct ActionListenerSubscription {
state: Weak<ActionListenerState>,
entry: Rc<ActionListenerEntry>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ActionInvocationPhase {
Start,
End,
}
struct ActionInvocationListenerState {
listeners: RefCell<Vec<Weak<ActionInvocationListenerEntry>>>,
}
struct ActionInvocationListenerEntry {
active: Cell<bool>,
callback: Rc<dyn Fn(ActionInvocationPhase)>,
}
pub struct ActionInvocationSubscription {
state: Weak<ActionInvocationListenerState>,
entry: Rc<ActionInvocationListenerEntry>,
}
impl ActionInvocationSubscription {
fn remove(&self) -> bool {
let was_active = self.entry.active.replace(false);
if let Some(state) = self.state.upgrade() {
state.listeners.borrow_mut().retain(|listener| {
listener
.upgrade()
.is_some_and(|listener| !Rc::ptr_eq(&listener, &self.entry))
});
}
was_active
}
}
impl Drop for ActionInvocationSubscription {
fn drop(&mut self) {
let _ = self.remove();
}
}
impl ActionListenerSubscription {
fn remove(&self) -> bool {
let was_active = self.entry.active.replace(false);
if let Some(state) = self.state.upgrade() {
state.listeners.borrow_mut().retain(|listener| {
listener
.upgrade()
.is_some_and(|listener| !Rc::ptr_eq(&listener, &self.entry))
});
}
was_active
}
}
impl Drop for ActionListenerSubscription {
fn drop(&mut self) {
let _ = self.remove();
}
}
type ActionCallback<C> = Rc<dyn Fn(&Intent<C>) -> ActionResult>;
#[derive(Clone)]
pub struct Action<C: CommandId = Command> {
intent: Intent<C>,
enabled: Rc<Cell<bool>>,
callback: ActionCallback<C>,
listeners: Rc<ActionListenerState>,
invocation_listeners: Rc<ActionInvocationListenerState>,
}
impl<C: CommandId + std::fmt::Debug> std::fmt::Debug for Action<C> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Action")
.field("intent", &self.intent)
.field("enabled", &self.is_enabled())
.finish_non_exhaustive()
}
}
impl<C: CommandId> Action<C> {
#[must_use]
pub fn new(command: impl Into<Intent<C>>, callback: impl Fn() + 'static) -> Self {
Self::with_handler(command, move |_| {
callback();
ActionResult::Handled
})
}
#[must_use]
pub fn with_handler<R>(
command: impl Into<Intent<C>>,
callback: impl Fn(&Intent<C>) -> R + 'static,
) -> Self
where
R: Into<ActionResult> + 'static,
{
Self {
intent: command.into(),
enabled: Rc::new(Cell::new(true)),
callback: Rc::new(move |intent| callback(intent).into()),
listeners: Rc::new(ActionListenerState {
listeners: RefCell::new(Vec::new()),
}),
invocation_listeners: Rc::new(ActionInvocationListenerState {
listeners: RefCell::new(Vec::new()),
}),
}
}
#[must_use]
pub fn intent(&self) -> &Intent<C> {
&self.intent
}
#[must_use]
pub fn command(&self) -> &C {
self.intent.command()
}
pub fn set_enabled(&self, enabled: bool) {
if self.enabled.replace(enabled) != enabled {
self.notify_action_listeners();
}
}
#[must_use]
pub fn is_enabled(&self) -> bool {
self.enabled.get()
}
#[must_use]
pub fn is_action_enabled(&self, intent: &Intent<C>) -> bool {
self.is_enabled() && self.intent.command() == intent.command()
}
#[must_use]
pub fn add_action_listener(&self, listener: impl Fn() + 'static) -> ActionListenerSubscription {
let entry = Rc::new(ActionListenerEntry {
active: Cell::new(true),
callback: Rc::new(listener),
});
self.listeners
.listeners
.borrow_mut()
.push(Rc::downgrade(&entry));
ActionListenerSubscription {
state: Rc::downgrade(&self.listeners),
entry,
}
}
#[must_use]
pub fn add_listener(&self, listener: impl Fn() + 'static) -> ActionListenerSubscription {
self.add_action_listener(listener)
}
pub fn remove_action_listener(&self, subscription: &ActionListenerSubscription) -> bool {
subscription.remove()
}
pub fn notify_action_listeners(&self) {
let listeners = {
let mut listeners = self.listeners.listeners.borrow_mut();
listeners.retain(|listener| listener.strong_count() != 0);
listeners
.iter()
.filter_map(Weak::upgrade)
.collect::<Vec<_>>()
};
for listener in listeners {
if listener.active.get() {
(listener.callback)();
}
}
}
#[must_use]
pub fn add_invocation_listener(
&self,
listener: impl Fn(ActionInvocationPhase) + 'static,
) -> ActionInvocationSubscription {
let entry = Rc::new(ActionInvocationListenerEntry {
active: Cell::new(true),
callback: Rc::new(listener),
});
self.invocation_listeners
.listeners
.borrow_mut()
.push(Rc::downgrade(&entry));
ActionInvocationSubscription {
state: Rc::downgrade(&self.invocation_listeners),
entry,
}
}
fn notify_invocation(&self, phase: ActionInvocationPhase) {
let listeners = {
let mut listeners = self.invocation_listeners.listeners.borrow_mut();
listeners.retain(|listener| listener.strong_count() != 0);
listeners
.iter()
.filter_map(Weak::upgrade)
.collect::<Vec<_>>()
};
for listener in listeners {
if listener.active.get() {
(listener.callback)(phase);
}
}
}
#[must_use]
pub fn listener_count(&self) -> usize {
let mut listeners = self.listeners.listeners.borrow_mut();
listeners.retain(|listener| listener.strong_count() != 0);
listeners
.iter()
.filter_map(Weak::upgrade)
.filter(|listener| listener.active.get())
.count()
}
#[must_use]
pub fn invoke(&self, intent: &Intent<C>) -> ActionResult {
if !self.is_action_enabled(intent) {
return ActionResult::Ignored;
}
self.notify_invocation(ActionInvocationPhase::Start);
let result = (self.callback)(intent);
self.notify_invocation(ActionInvocationPhase::End);
result
}
}
#[derive(Clone)]
pub enum ShortcutMatch {
Callback(Rc<dyn Fn()>),
Intent(ErasedIntent),
}
pub struct ErasedShortcutScope {
finder: Rc<dyn Fn(KeyboardEvent) -> Option<ShortcutMatch>>,
}
impl ErasedShortcutScope {
#[must_use]
pub fn from_shortcuts<C: CommandId>(shortcuts: Rc<Shortcuts<C>>) -> Rc<Self> {
Rc::new(Self {
finder: Rc::new(move |event| shortcuts.find_match(&event)),
})
}
#[must_use]
pub fn find(&self, event: KeyboardEvent) -> Option<ShortcutMatch> {
(self.finder)(event)
}
}
type ErasedActionInvoker = dyn Fn(&ErasedIntent) -> Option<ActionResult>;
pub struct ErasedActionScope {
invoker: Rc<ErasedActionInvoker>,
}
impl ErasedActionScope {
#[must_use]
pub fn from_actions<C: CommandId>(actions: Rc<Actions<C>>) -> Rc<Self> {
Rc::new(Self {
invoker: Rc::new(move |intent| {
let command = intent.downcast_command::<C>()?;
let intent = Intent::new(command.clone());
actions.invoke_intent_first(&intent)
}),
})
}
#[must_use]
pub fn invoke(&self, intent: &ErasedIntent) -> Option<ActionResult> {
(self.invoker)(intent)
}
}
struct ActionScope<C: CommandId> {
actions: HashMap<C, Action<C>>,
}
pub struct Actions<C: CommandId = Command> {
scopes: Vec<ActionScope<C>>,
}
impl<C: CommandId> Default for Actions<C> {
fn default() -> Self {
Self {
scopes: vec![ActionScope::<C> {
actions: HashMap::new(),
}],
}
}
}
impl<C: CommandId> Actions<C> {
#[must_use]
pub fn typed() -> Self {
Self::default()
}
pub fn register(&mut self, command: impl Into<C>, action: impl Fn() + 'static) {
let command = command.into();
self.register_action(Action::new(Intent::new(command), action));
}
pub fn register_action(&mut self, action: Action<C>) {
self.current_scope_mut()
.actions
.insert(action.command().clone(), action);
}
pub fn register_handler<R>(
&mut self,
command: impl Into<Intent<C>>,
action: impl Fn(&Intent<C>) -> R + 'static,
) where
R: Into<ActionResult> + 'static,
{
self.register_action(Action::with_handler(command, action));
}
pub fn push_scope(&mut self) {
self.scopes.push(ActionScope::<C> {
actions: HashMap::new(),
});
}
#[must_use]
pub fn pop_scope(&mut self) -> bool {
(self.scopes.len() > 1).then(|| self.scopes.pop()).is_some()
}
pub fn with_scope<R>(&mut self, callback: impl FnOnce(&mut Self) -> R) -> R {
self.push_scope();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(self)));
let _ = self.pop_scope();
match result {
Ok(result) => result,
Err(panic) => std::panic::resume_unwind(panic),
}
}
#[must_use]
pub fn scope_depth(&self) -> usize {
self.scopes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.scopes.iter().all(|scope| scope.actions.is_empty())
}
pub fn unregister(&mut self, command: &C) -> bool {
self.current_scope_mut().actions.remove(command).is_some()
}
#[must_use]
pub fn invoke(&self, command: &C) -> bool {
self.invoke_intent(&Intent::new(command.clone()))
}
#[must_use]
pub fn invoke_intent(&self, intent: &Intent<C>) -> bool {
self.scopes.iter().rev().any(|scope| {
scope
.actions
.get(intent.command())
.is_some_and(|action| action.invoke(intent) == ActionResult::Handled)
})
}
#[must_use]
pub fn invoke_intent_first(&self, intent: &Intent<C>) -> Option<ActionResult> {
self.scopes.iter().rev().find_map(|scope| {
scope
.actions
.get(intent.command())
.map(|action| action.invoke(intent))
})
}
#[must_use]
pub fn action(&self, command: &C) -> Option<Action<C>> {
self.scopes
.iter()
.rev()
.find_map(|scope| scope.actions.get(command).cloned())
}
fn current_scope_mut(&mut self) -> &mut ActionScope<C> {
if self.scopes.is_empty() {
self.scopes.push(ActionScope::<C> {
actions: HashMap::new(),
});
}
self.scopes
.last_mut()
.expect("action registry has a root scope")
}
}
impl Actions<Command> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl ShortcutKey {
#[must_use]
pub const fn new(code: Code, modifiers: Modifiers) -> Self {
Self { code, modifiers }
}
#[must_use]
pub fn logical(key: KeyboardKey, modifiers: Modifiers) -> LogicalShortcutKey {
LogicalShortcutKey { key, modifiers }
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SingleActivator {
pub trigger: KeyboardKey,
pub modifiers: Modifiers,
pub include_repeats: bool,
}
impl SingleActivator {
#[must_use]
pub fn new(trigger: KeyboardKey, modifiers: Modifiers) -> Self {
Self {
trigger,
modifiers: shortcut_modifiers(modifiers),
include_repeats: true,
}
}
#[must_use]
pub fn with_repeats(mut self, include_repeats: bool) -> Self {
self.include_repeats = include_repeats;
self
}
#[must_use]
pub const fn trigger(&self) -> &KeyboardKey {
&self.trigger
}
#[must_use]
pub const fn modifiers(&self) -> Modifiers {
self.modifiers
}
#[must_use]
pub const fn includes_repeats(&self) -> bool {
self.include_repeats
}
#[must_use]
pub fn accepts(&self, event: &KeyboardEvent) -> bool {
event.state.is_down()
&& (self.include_repeats || !event.repeat)
&& event.key == self.trigger
&& shortcut_modifiers(event.modifiers) == self.modifiers
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ShortcutActivator {
Physical {
key: ShortcutKey,
trigger: ShortcutTrigger,
},
Logical {
key: LogicalShortcutKey,
trigger: ShortcutTrigger,
},
Single(SingleActivator),
}
impl ShortcutActivator {
#[must_use]
pub const fn physical(key: ShortcutKey) -> Self {
Self::Physical {
key,
trigger: ShortcutTrigger::Down,
}
}
#[must_use]
pub const fn physical_on(key: ShortcutKey, trigger: ShortcutTrigger) -> Self {
Self::Physical { key, trigger }
}
#[must_use]
pub fn logical(key: LogicalShortcutKey) -> Self {
Self::Logical {
key,
trigger: ShortcutTrigger::Down,
}
}
#[must_use]
pub fn logical_on(key: LogicalShortcutKey, trigger: ShortcutTrigger) -> Self {
Self::Logical { key, trigger }
}
#[must_use]
pub fn single(trigger: KeyboardKey, modifiers: Modifiers) -> Self {
Self::Single(SingleActivator::new(trigger, modifiers))
}
#[must_use]
pub fn accepts(&self, event: &KeyboardEvent) -> bool {
match self {
Self::Physical { key, trigger } => {
key.code == event.code && key.modifiers == event.modifiers && trigger.matches(event)
}
Self::Logical { key, trigger } => {
key.key == event.key && key.modifiers == event.modifiers && trigger.matches(event)
}
Self::Single(activator) => activator.accepts(event),
}
}
}
impl From<ShortcutKey> for ShortcutActivator {
fn from(value: ShortcutKey) -> Self {
Self::physical(value)
}
}
impl From<LogicalShortcutKey> for ShortcutActivator {
fn from(value: LogicalShortcutKey) -> Self {
Self::logical(value)
}
}
impl From<SingleActivator> for ShortcutActivator {
fn from(value: SingleActivator) -> Self {
Self::Single(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LogicalKeySet {
keys: Vec<KeyboardKey>,
}
impl LogicalKeySet {
#[must_use]
pub fn new(keys: impl IntoIterator<Item = KeyboardKey>) -> Self {
let mut unique = Vec::new();
for key in keys {
if !unique.contains(&key) {
unique.push(key);
}
}
Self { keys: unique }
}
#[must_use]
pub fn keys(&self) -> &[KeyboardKey] {
&self.keys
}
fn modifier_of(key: &KeyboardKey) -> Option<Modifiers> {
use incular_core::NamedKey;
match key {
KeyboardKey::Named(NamedKey::Alt) => Some(Modifiers::ALT),
KeyboardKey::Named(NamedKey::Control) => Some(Modifiers::CONTROL),
KeyboardKey::Named(NamedKey::Meta | NamedKey::Super) => Some(Modifiers::META),
KeyboardKey::Named(NamedKey::Shift) => Some(Modifiers::SHIFT),
_ => None,
}
}
#[must_use]
pub fn activator(&self) -> Option<SingleActivator> {
let mut modifiers = Modifiers::empty();
let mut trigger = None;
for key in &self.keys {
match Self::modifier_of(key) {
Some(modifier) => modifiers |= modifier,
None if trigger.is_some() => return None,
None => trigger = Some(key.clone()),
}
}
match trigger {
Some(trigger) => Some(SingleActivator::new(trigger, modifiers)),
None => {
let last = self.keys.last()?.clone();
Some(SingleActivator::new(last, modifiers))
}
}
}
#[must_use]
pub fn accepts(&self, event: &KeyboardEvent) -> bool {
self.activator().is_some_and(|activator| {
if Self::modifier_of(&activator.trigger).is_some() {
event.state.is_down()
&& self.keys.contains(&event.key)
&& shortcut_modifiers(event.modifiers)
| Self::modifier_of(&event.key).unwrap_or_default()
== activator.modifiers
} else {
activator.accepts(event)
}
})
}
}
impl From<LogicalKeySet> for ShortcutActivator {
fn from(value: LogicalKeySet) -> Self {
match value.activator() {
Some(activator) => Self::Single(activator),
None => Self::logical(LogicalShortcutKey {
key: KeyboardKey::Named(incular_core::NamedKey::Unidentified),
modifiers: Modifiers::all(),
}),
}
}
}
fn shortcut_modifiers(modifiers: Modifiers) -> Modifiers {
modifiers & (Modifiers::ALT | Modifiers::CONTROL | Modifiers::META | Modifiers::SHIFT)
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LogicalShortcutKey {
pub key: KeyboardKey,
pub modifiers: Modifiers,
}
impl LogicalShortcutKey {
#[must_use]
pub fn new(key: KeyboardKey, modifiers: Modifiers) -> Self {
Self { key, modifiers }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum ShortcutTrigger {
#[default]
Down,
Press,
Repeat,
Up,
Any,
}
impl ShortcutTrigger {
#[must_use]
pub const fn matches(self, event: &KeyboardEvent) -> bool {
match self {
Self::Down => event.state.is_down(),
Self::Press => event.state.is_down() && !event.repeat,
Self::Repeat => event.state.is_down() && event.repeat,
Self::Up => event.state.is_up(),
Self::Any => true,
}
}
}
enum ShortcutBinding<C: CommandId> {
Callback(Rc<dyn Fn()>),
Intent(Intent<C>),
}
struct ShortcutRegistration<C: CommandId> {
trigger: ShortcutTrigger,
binding: ShortcutBinding<C>,
}
struct ShortcutScope<C: CommandId> {
physical: HashMap<ShortcutKey, Vec<ShortcutRegistration<C>>>,
logical: HashMap<LogicalShortcutKey, Vec<ShortcutRegistration<C>>>,
}
pub struct Shortcuts<C: CommandId = Command> {
scopes: Vec<ShortcutScope<C>>,
}
impl<C: CommandId> Default for Shortcuts<C> {
fn default() -> Self {
Self {
scopes: vec![ShortcutScope {
physical: HashMap::new(),
logical: HashMap::new(),
}],
}
}
}
impl<C: CommandId> Shortcuts<C> {
#[must_use]
pub fn typed() -> Self {
Self::default()
}
pub fn register(&mut self, key: ShortcutKey, action: impl Fn() + 'static) {
self.register_on(key, ShortcutTrigger::Down, action);
}
pub fn register_on(
&mut self,
key: ShortcutKey,
trigger: ShortcutTrigger,
action: impl Fn() + 'static,
) {
Self::replace_binding(
&mut self.current_scope_mut().physical,
key,
trigger,
ShortcutBinding::Callback(Rc::new(action)),
);
}
pub fn register_logical(&mut self, key: LogicalShortcutKey, action: impl Fn() + 'static) {
self.register_logical_on(key, ShortcutTrigger::Down, action);
}
pub fn register_logical_on(
&mut self,
key: LogicalShortcutKey,
trigger: ShortcutTrigger,
action: impl Fn() + 'static,
) {
Self::replace_binding(
&mut self.current_scope_mut().logical,
key,
trigger,
ShortcutBinding::Callback(Rc::new(action)),
);
}
pub fn bind(&mut self, key: ShortcutKey, command: impl Into<C>) {
self.bind_on(key, ShortcutTrigger::Down, Intent::new(command.into()));
}
pub fn bind_on(
&mut self,
key: ShortcutKey,
trigger: ShortcutTrigger,
command: impl Into<Intent<C>>,
) {
Self::replace_binding(
&mut self.current_scope_mut().physical,
key,
trigger,
ShortcutBinding::Intent(command.into()),
);
}
pub fn bind_logical(&mut self, key: LogicalShortcutKey, command: impl Into<C>) {
self.bind_logical_on(key, ShortcutTrigger::Down, Intent::new(command.into()));
}
pub fn bind_logical_on(
&mut self,
key: LogicalShortcutKey,
trigger: ShortcutTrigger,
command: impl Into<Intent<C>>,
) {
Self::replace_binding(
&mut self.current_scope_mut().logical,
key,
trigger,
ShortcutBinding::Intent(command.into()),
);
}
pub fn clear(&mut self, key: &ShortcutKey) -> bool {
self.current_scope_mut().physical.remove(key).is_some()
}
pub fn clear_logical(&mut self, key: &LogicalShortcutKey) -> bool {
self.current_scope_mut().logical.remove(key).is_some()
}
pub fn push_scope(&mut self) {
self.scopes.push(ShortcutScope {
physical: HashMap::new(),
logical: HashMap::new(),
});
}
#[must_use]
pub fn pop_scope(&mut self) -> bool {
(self.scopes.len() > 1).then(|| self.scopes.pop()).is_some()
}
pub fn with_scope<R>(&mut self, callback: impl FnOnce(&mut Self) -> R) -> R {
self.push_scope();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(self)));
let _ = self.pop_scope();
match result {
Ok(result) => result,
Err(panic) => std::panic::resume_unwind(panic),
}
}
#[must_use]
pub fn scope_depth(&self) -> usize {
self.scopes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.scopes.iter().all(|scope| {
scope.physical.values().all(Vec::is_empty) && scope.logical.values().all(Vec::is_empty)
})
}
pub fn handle(&self, event: KeyboardEvent) -> bool {
self.dispatch(event, None)
}
#[must_use]
pub fn handle_actions(&self, event: KeyboardEvent, actions: &Actions<C>) -> bool {
self.dispatch(event, Some(actions))
}
#[must_use]
pub fn handle_event(&self, event: KeyboardEvent, actions: &Actions<C>) -> bool {
self.handle_actions(event, actions)
}
#[must_use]
pub fn erased_scope(self: &Rc<Self>) -> Rc<ErasedShortcutScope> {
ErasedShortcutScope::from_shortcuts(self.clone())
}
fn replace_binding<K>(
bindings: &mut HashMap<K, Vec<ShortcutRegistration<C>>>,
key: K,
trigger: ShortcutTrigger,
binding: ShortcutBinding<C>,
) where
K: Eq + std::hash::Hash,
{
let registrations = bindings.entry(key).or_default();
registrations.retain(|registration| registration.trigger != trigger);
registrations.push(ShortcutRegistration { trigger, binding });
}
fn dispatch(&self, event: KeyboardEvent, actions: Option<&Actions<C>>) -> bool {
let physical = ShortcutKey::new(event.code, event.modifiers);
let logical = LogicalShortcutKey::new(event.key.clone(), event.modifiers);
for scope in self.scopes.iter().rev() {
if scope.physical.get(&physical).is_some_and(|registrations| {
Self::dispatch_registrations(registrations, &event, actions)
}) || scope.logical.get(&logical).is_some_and(|registrations| {
Self::dispatch_registrations(registrations, &event, actions)
}) {
return true;
}
}
false
}
fn find_match(&self, event: &KeyboardEvent) -> Option<ShortcutMatch> {
let physical = ShortcutKey::new(event.code, event.modifiers);
let logical = LogicalShortcutKey::new(event.key.clone(), event.modifiers);
for scope in self.scopes.iter().rev() {
if let Some(registration) = scope.physical.get(&physical).and_then(|registrations| {
registrations
.iter()
.find(|registration| registration.trigger.matches(event))
}) {
return Some(Self::erase_binding(®istration.binding));
}
if let Some(registration) = scope.logical.get(&logical).and_then(|registrations| {
registrations
.iter()
.find(|registration| registration.trigger.matches(event))
}) {
return Some(Self::erase_binding(®istration.binding));
}
}
None
}
fn erase_binding(binding: &ShortcutBinding<C>) -> ShortcutMatch {
match binding {
ShortcutBinding::Callback(callback) => ShortcutMatch::Callback(callback.clone()),
ShortcutBinding::Intent(intent) => {
ShortcutMatch::Intent(ErasedIntent::from_intent(intent))
}
}
}
fn dispatch_registrations(
registrations: &[ShortcutRegistration<C>],
event: &KeyboardEvent,
actions: Option<&Actions<C>>,
) -> bool {
registrations
.iter()
.filter(|registration| registration.trigger.matches(event))
.any(|registration| match ®istration.binding {
ShortcutBinding::Callback(callback) => {
callback();
true
}
ShortcutBinding::Intent(intent) => {
actions.is_some_and(|actions| actions.invoke_intent(intent))
}
})
}
fn current_scope_mut(&mut self) -> &mut ShortcutScope<C> {
if self.scopes.is_empty() {
self.scopes.push(ShortcutScope {
physical: HashMap::new(),
logical: HashMap::new(),
});
}
self.scopes
.last_mut()
.expect("shortcut registry has a root scope")
}
}
impl Shortcuts<Command> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}