use crate::compat::HashMap;
use core::cell::RefCell;
use crate::WidgetTriggerEvent;
pub struct EventHandlerContext {
pub trigger: WidgetTriggerEvent,
pub payload: crate::widget::capability::CapabilityValue,
pub user_data: Option<*mut std::ffi::c_void>,
}
unsafe impl Send for EventHandlerContext {}
impl EventHandlerContext {
pub fn new(trigger: WidgetTriggerEvent) -> Self {
Self { trigger, payload: crate::widget::capability::CapabilityValue::Null, user_data: None }
}
pub fn with_payload(mut self, payload: crate::widget::capability::CapabilityValue) -> Self {
self.payload = payload;
self
}
pub fn with_user_data(mut self, data: *mut std::ffi::c_void) -> Self {
self.user_data = Some(data);
self
}
pub fn user_data<T>(&self) -> Option<&T> {
let ptr = self.user_data?;
unsafe { Some(&*(ptr as *const T)) }
}
pub fn user_data_mut<T>(&mut self) -> Option<&mut T> {
let ptr = self.user_data?;
unsafe { Some(&mut *(ptr as *mut T)) }
}
}
pub type EventHandler = Box<dyn Fn(&EventHandlerContext)>;
pub struct EventHandlerMap {
handlers: HashMap<String, EventHandler>,
}
impl EventHandlerMap {
pub fn new() -> Self {
Self { handlers: HashMap::new() }
}
pub fn register<F>(&mut self, name: impl Into<String>, f: F)
where
F: Fn(&EventHandlerContext) + 'static,
{
self.handlers.insert(name.into(), Box::new(f));
}
pub fn invoke(&self, name: &str, ctx: &EventHandlerContext) -> bool {
if let Some(handler) = self.handlers.get(name) {
handler(ctx);
true
} else {
false
}
}
pub fn take(&mut self, name: &str) -> Option<EventHandler> {
self.handlers.remove(name)
}
pub fn restore(&mut self, name: impl Into<String>, handler: EventHandler) {
self.handlers.entry(name.into()).or_insert(handler);
}
pub fn has_handler(&self, name: &str) -> bool {
self.handlers.contains_key(name)
}
pub fn unregister(&mut self, name: &str) -> bool {
self.handlers.remove(name).is_some()
}
pub fn len(&self) -> usize {
self.handlers.len()
}
pub fn is_empty(&self) -> bool {
self.handlers.is_empty()
}
pub fn clear(&mut self) {
self.handlers.clear();
}
}
crate::impl_default_via_new!(EventHandlerMap);
thread_local! {
#[allow(clippy::missing_const_for_thread_local)]
static GLOBAL_EVENT_HANDLERS: RefCell<EventHandlerMap> = RefCell::new(EventHandlerMap::new());
#[allow(clippy::missing_const_for_thread_local)]
static HANDLER_GENERATION: core::cell::Cell<u64> = const { core::cell::Cell::new(0) };
}
fn handler_generation() -> u64 {
HANDLER_GENERATION.with(|generation| generation.get())
}
pub fn register_global_handler<F>(name: impl Into<String>, f: F)
where
F: Fn(&EventHandlerContext) + 'static,
{
GLOBAL_EVENT_HANDLERS.with(|handlers| {
handlers.borrow_mut().register(name, f);
});
}
pub fn invoke_global_handler(name: &str, ctx: &EventHandlerContext) -> bool {
let taken = GLOBAL_EVENT_HANDLERS.with(|handlers| handlers.borrow_mut().take(name));
let Some(handler) = taken else {
return false;
};
let generation_at_invoke = handler_generation();
struct RestoreGuard<'a> {
name: &'a str,
handler: Option<EventHandler>,
generation_at_invoke: u64,
}
impl Drop for RestoreGuard<'_> {
fn drop(&mut self) {
let handler = match self.handler.take() {
Some(handler) => handler,
None => return,
};
if handler_generation() != self.generation_at_invoke {
return;
}
GLOBAL_EVENT_HANDLERS
.with(|handlers| handlers.borrow_mut().restore(self.name, handler));
}
}
let guard = RestoreGuard { name, handler: Some(handler), generation_at_invoke };
if let Some(handler) = guard.handler.as_ref() {
handler(ctx);
}
true
}
pub fn clear_global_handlers() {
HANDLER_GENERATION.with(|generation| generation.set(generation.get().wrapping_add(1)));
GLOBAL_EVENT_HANDLERS.with(|handlers| {
handlers.borrow_mut().clear();
});
}
static CROSS_THREAD_SKIPS: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
pub fn record_cross_thread_skip(handler: &str, bound_thread: &str, fired_thread: &str) -> usize {
log::warn!(
"JSON handler `{handler}` was bound on thread {bound_thread} but fired on thread \
{fired_thread}; the handler registry is thread-local, so the binding was skipped rather \
than run cross-thread. Emit the control's signal on its owning thread, or move the \
binding to the emitting thread."
);
CROSS_THREAD_SKIPS.fetch_add(1, core::sync::atomic::Ordering::SeqCst) + 1
}
pub fn cross_thread_skips() -> usize {
CROSS_THREAD_SKIPS.load(core::sync::atomic::Ordering::SeqCst)
}
pub fn reset_cross_thread_skips() {
CROSS_THREAD_SKIPS.store(0, core::sync::atomic::Ordering::SeqCst);
}
pub fn current_thread_name() -> alloc::string::String {
let current = std::thread::current();
match current.name() {
Some(name) => alloc::string::String::from(name),
None => alloc::format!("{:?}", current.id()),
}
}
pub fn current_thread_id() -> std::thread::ThreadId {
std::thread::current().id()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn handler_map_new_is_empty() {
let map = EventHandlerMap::new();
assert!(map.is_empty());
assert_eq!(map.len(), 0);
}
#[test]
fn register_and_invoke_handler() {
let mut map = EventHandlerMap::new();
let invoked = std::rc::Rc::new(std::cell::Cell::new(false));
let invoked_clone = invoked.clone();
map.register("test_handler", move |_ctx| {
invoked_clone.set(true);
});
assert!(map.has_handler("test_handler"));
assert_eq!(map.len(), 1);
let ctx = EventHandlerContext::new(crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
});
let result = map.invoke("test_handler", &ctx);
assert!(result);
assert!(invoked.get());
}
#[test]
fn invoke_missing_handler_returns_false() {
let map = EventHandlerMap::new();
let ctx = EventHandlerContext::new(crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
});
assert!(!map.invoke("nonexistent", &ctx));
}
#[test]
fn unregister_handler() {
let mut map = EventHandlerMap::new();
map.register("to_remove", |_ctx| {});
assert!(map.has_handler("to_remove"));
assert!(map.unregister("to_remove"));
assert!(!map.has_handler("to_remove"));
assert_eq!(map.len(), 0);
}
#[test]
fn unregister_missing_returns_false() {
let mut map = EventHandlerMap::new();
assert!(!map.unregister("never_existed"));
}
#[test]
fn clear_removes_all_handlers() {
let mut map = EventHandlerMap::new();
map.register("a", |_ctx| {});
map.register("b", |_ctx| {});
map.register("c", |_ctx| {});
assert_eq!(map.len(), 3);
map.clear();
assert!(map.is_empty());
assert_eq!(map.len(), 0);
}
#[test]
fn handler_context_trigger_fields() {
let id = 42;
let trigger = crate::WidgetTriggerEvent {
widget_id: id,
kind: crate::platform::WidgetTriggerKind::ValueChanged,
};
let ctx = EventHandlerContext::new(trigger);
assert_eq!(ctx.trigger.widget_id, id);
}
#[test]
fn handler_context_with_user_data() {
let trigger = crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
};
let mut data: i32 = 123;
let ctx = EventHandlerContext::new(trigger)
.with_user_data(&mut data as *mut i32 as *mut std::ffi::c_void);
let retrieved: &i32 = ctx.user_data().unwrap();
assert_eq!(*retrieved, 123);
}
#[test]
fn handler_context_user_data_none_when_not_set() {
let trigger = crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
};
let ctx = EventHandlerContext::new(trigger);
assert!(ctx.user_data::<i32>().is_none());
}
#[test]
fn handler_context_user_data_mut() {
let trigger = crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
};
let mut data: i32 = 456;
let mut ctx = EventHandlerContext::new(trigger)
.with_user_data(&mut data as *mut i32 as *mut std::ffi::c_void);
{
let val: &mut i32 = ctx.user_data_mut().unwrap();
*val = 789;
}
assert_eq!(data, 789);
}
#[test]
fn default_is_empty() {
let map = EventHandlerMap::default();
assert!(map.is_empty());
assert_eq!(map.len(), 0);
}
#[test]
fn handler_passed_correct_trigger() {
let mut map = EventHandlerMap::new();
let captured_trigger =
std::rc::Rc::new(std::cell::RefCell::new(None::<crate::platform::WidgetTriggerKind>));
let captured_clone = captured_trigger.clone();
map.register("capture", move |ctx| {
*captured_clone.borrow_mut() = Some(ctx.trigger.kind);
});
let trigger = crate::WidgetTriggerEvent {
widget_id: 7,
kind: crate::platform::WidgetTriggerKind::SelectionChanged,
};
let ctx = EventHandlerContext::new(trigger);
map.invoke("capture", &ctx);
assert_eq!(
captured_trigger.borrow().unwrap(),
crate::platform::WidgetTriggerKind::SelectionChanged
);
}
#[test]
fn global_register_and_invoke() {
clear_global_handlers();
let invoked = std::rc::Rc::new(std::cell::Cell::new(false));
let invoked_clone = invoked.clone();
register_global_handler("global_test", move |_ctx| {
invoked_clone.set(true);
});
let ctx = EventHandlerContext::new(crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
});
assert!(invoke_global_handler("global_test", &ctx));
assert!(invoked.get());
}
#[test]
fn global_missing_handler_returns_false() {
clear_global_handlers();
let ctx = EventHandlerContext::new(crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
});
assert!(!invoke_global_handler("not_registered", &ctx));
}
#[test]
fn global_clear_removes_all() {
clear_global_handlers();
register_global_handler("g1", |_ctx| {});
register_global_handler("g2", |_ctx| {});
clear_global_handlers();
let ctx = EventHandlerContext::new(crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
});
assert!(!invoke_global_handler("g1", &ctx));
assert!(!invoke_global_handler("g2", &ctx));
}
#[test]
fn a_global_handler_may_register_another_handler_while_running() {
clear_global_handlers();
let inner_ran = std::rc::Rc::new(std::cell::Cell::new(false));
let inner_flag = inner_ran.clone();
register_global_handler("outer", move |_ctx| {
let inner_flag = inner_flag.clone();
register_global_handler("inner", move |_ctx| {
inner_flag.set(true);
});
});
let ctx = EventHandlerContext::new(crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
});
assert!(invoke_global_handler("outer", &ctx), "the outer handler must run");
assert!(
invoke_global_handler("inner", &ctx),
"the handler registered by the outer must exist"
);
assert!(inner_ran.get(), "and must be callable");
clear_global_handlers();
}
#[test]
fn a_global_handler_may_clear_the_registry_while_running() {
clear_global_handlers();
let ran = std::rc::Rc::new(std::cell::Cell::new(false));
let ran_flag = ran.clone();
register_global_handler("clears", move |_ctx| {
clear_global_handlers();
ran_flag.set(true);
});
let ctx = EventHandlerContext::new(crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
});
assert!(invoke_global_handler("clears", &ctx), "the handler must run despite clearing");
assert!(ran.get());
assert!(
!invoke_global_handler("clears", &ctx),
"a handler that cleared the registry must not be restored by the clear's own frame"
);
clear_global_handlers();
}
#[test]
fn a_handler_reregistered_after_a_clear_survives() {
clear_global_handlers();
let calls = std::rc::Rc::new(std::cell::Cell::new(0u32));
let calls_slot = calls.clone();
register_global_handler("cycle", move |_ctx| {
clear_global_handlers();
let inner = calls_slot.clone();
register_global_handler("cycle", move |_ctx| {
inner.set(inner.get() + 1);
});
clear_global_handlers();
let inner = calls_slot.clone();
register_global_handler("cycle", move |_ctx| {
inner.set(inner.get() + 1);
});
});
let ctx = EventHandlerContext::new(crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
});
assert!(invoke_global_handler("cycle", &ctx));
assert!(invoke_global_handler("cycle", &ctx), "the re-registered handler must survive");
clear_global_handlers();
}
#[test]
fn a_same_name_reregistration_without_a_clear_is_not_clobbered() {
clear_global_handlers();
let which = std::rc::Rc::new(std::cell::Cell::new(0u8));
let which_first = which.clone();
register_global_handler("slot", move |_ctx| {
which_first.set(1);
let which_second = which_first.clone();
register_global_handler("slot", move |_ctx| {
which_second.set(2);
});
});
let ctx = EventHandlerContext::new(crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
});
assert!(invoke_global_handler("slot", &ctx), "the first handler runs");
assert_eq!(which.get(), 1, "the first handler recorded itself");
assert!(invoke_global_handler("slot", &ctx));
assert_eq!(
which.get(),
2,
"the same-name re-registration must win over the restored older handler"
);
clear_global_handlers();
}
#[test]
fn a_panicking_handler_is_restored_on_the_unwind_path() {
clear_global_handlers();
let calls = std::rc::Rc::new(std::cell::Cell::new(0u32));
let calls_slot = calls.clone();
let should_panic = std::rc::Rc::new(std::cell::Cell::new(true));
let panic_slot = should_panic.clone();
register_global_handler("boom", move |_ctx| {
calls_slot.set(calls_slot.get() + 1);
if panic_slot.get() {
panic!("deliberate unwind from a global handler");
}
});
let ctx = EventHandlerContext::new(crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
});
let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
invoke_global_handler("boom", &ctx)
}));
assert!(caught.is_err(), "the handler must have unwound");
assert_eq!(calls.get(), 1, "the handler ran once before unwinding");
should_panic.set(false);
assert!(
invoke_global_handler("boom", &ctx),
"a panicking handler must be restored so later emits still reach it"
);
assert_eq!(calls.get(), 2, "the restored handler ran again");
clear_global_handlers();
}
#[test]
fn a_handler_may_invoke_a_nested_handler_while_running() {
clear_global_handlers();
let inner_calls = std::rc::Rc::new(std::cell::Cell::new(0u32));
let inner_slot = inner_calls.clone();
register_global_handler("inner", move |_ctx| {
inner_slot.set(inner_slot.get() + 1);
});
let outer_calls = std::rc::Rc::new(std::cell::Cell::new(0u32));
let outer_slot = outer_calls.clone();
register_global_handler("outer", move |ctx| {
outer_slot.set(outer_slot.get() + 1);
assert!(invoke_global_handler("inner", ctx), "the nested handler must run");
});
let ctx = EventHandlerContext::new(crate::WidgetTriggerEvent {
widget_id: 1,
kind: crate::platform::WidgetTriggerKind::Clicked,
});
assert!(invoke_global_handler("outer", &ctx));
assert_eq!(outer_calls.get(), 1, "the outer handler ran once");
assert_eq!(inner_calls.get(), 1, "the nested inner handler ran once");
assert!(invoke_global_handler("outer", &ctx));
assert!(invoke_global_handler("inner", &ctx));
assert_eq!(outer_calls.get(), 2);
assert_eq!(inner_calls.get(), 3, "inner: 1 nested + 1 nested again + 1 explicit");
clear_global_handlers();
}
}