use crate::action::{ActionManager, ActionRouter};
use crate::compat::{lock, Arc, Box, Mutex};
use crate::core::ObjectId;
use crate::event::r#loop::{EventDispatchFn, EventShortcutFn};
use crate::event::{Event, EventLoop};
use crate::shortcut::ShortcutManager;
#[derive(Clone)]
pub struct ActionRuntime {
shortcuts: Arc<Mutex<ShortcutManager>>,
actions: Arc<Mutex<ActionManager>>,
}
impl ActionRuntime {
pub fn new(shortcuts: ShortcutManager, actions: ActionManager) -> Self {
Self { shortcuts: Arc::new(Mutex::new(shortcuts)), actions: Arc::new(Mutex::new(actions)) }
}
pub fn from_shared(
shortcuts: Arc<Mutex<ShortcutManager>>,
actions: Arc<Mutex<ActionManager>>,
) -> Self {
Self { shortcuts, actions }
}
pub fn shortcuts(&self) -> &Arc<Mutex<ShortcutManager>> {
&self.shortcuts
}
pub fn actions(&self) -> &Arc<Mutex<ActionManager>> {
&self.actions
}
pub fn with_router<R>(&self, f: impl FnOnce(&mut ActionRouter<'_>) -> R) -> R {
let mut shortcuts = lock(&self.shortcuts);
let mut actions = lock(&self.actions);
let mut router = ActionRouter::new(&mut shortcuts, &mut actions);
f(&mut router)
}
pub fn dispatch_event(&self, event: &Event) -> bool {
if !matches!(event, Event::KeyPress { .. }) {
return false;
}
self.with_router(|router| router.dispatch_event(event))
}
pub fn shortcut_dispatch_fn(&self) -> EventShortcutFn {
let runtime = self.clone();
Arc::new(move |event| runtime.dispatch_event(event))
}
}
pub fn default_parent_lookup(id: ObjectId) -> Option<ObjectId> {
crate::widget::runtime::read_widget(id, |widget| widget.parent()).flatten()
}
pub fn default_dispatch_fn() -> EventDispatchFn {
Arc::new(|id, event| {
let _ = crate::widget::runtime::dispatch_event(id, event);
})
}
pub fn assemble_event_loop(event_loop: &mut EventLoop, runtime: &ActionRuntime) {
event_loop.set_parent_lookup(Box::new(default_parent_lookup));
event_loop.set_dispatch_fn(default_dispatch_fn());
event_loop.set_shortcut_dispatch(runtime.shortcut_dispatch_fn());
}
pub fn assemble_event_loop_with(
event_loop: &mut EventLoop,
runtime: &ActionRuntime,
parent_lookup: Option<Box<dyn Fn(ObjectId) -> Option<ObjectId> + Send + Sync>>,
dispatch_fn: Option<EventDispatchFn>,
) {
event_loop.set_parent_lookup(parent_lookup.unwrap_or_else(|| Box::new(default_parent_lookup)));
event_loop.set_dispatch_fn(dispatch_fn.unwrap_or_else(default_dispatch_fn));
event_loop.set_shortcut_dispatch(runtime.shortcut_dispatch_fn());
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shortcut::{Key, Shortcut};
#[test]
fn the_shortcut_hook_dispatches_into_the_action_registry() {
let mut shortcuts = ShortcutManager::new();
shortcuts.register("save", Shortcut::primary(Key::S), "Save");
let mut actions = ActionManager::new();
assert!(actions.register_action("save", "Save"));
assert!(actions.bind_shortcut_type(&Shortcut::primary(Key::S), "save"));
let fired = Arc::new(core::sync::atomic::AtomicUsize::new(0));
let counter = Arc::clone(&fired);
actions.action("save").expect("registered above").connect_triggered(move || {
counter.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
});
let runtime = ActionRuntime::new(shortcuts, actions);
let hook = runtime.shortcut_dispatch_fn();
let matched = runtime
.with_router(|router| router.dispatch_key(Key::S, crate::shortcut::Modifiers::PRIMARY));
assert_eq!(matched.as_deref(), Some("save"));
assert_eq!(fired.load(core::sync::atomic::Ordering::SeqCst), 1);
assert!(!hook(&Event::Paint));
}
#[test]
fn the_router_reports_the_assembly_lock_pair_without_deadlock() {
let runtime = ActionRuntime::new(ShortcutManager::new(), ActionManager::new());
assert!(runtime
.with_router(|router| router.dispatch_key(Key::A, crate::shortcut::Modifiers::PRIMARY))
.is_none());
assert!(!runtime.dispatch_event(&Event::KeyRelease { key: 65, modifiers: 0 }));
}
}