Skip to main content

bubba_core/events/
mod.rs

1//! # Events
2//!
3//! Bubba's event system. Every interactive element supports a small set of
4//! declarative events that map directly to native Android touch/input callbacks.
5//!
6//! ## Supported Events
7//! | Attribute  | Fires when …                              |
8//! |------------|-------------------------------------------|
9//! | `onclick`  | Element is tapped / clicked               |
10//! | `oninput`  | Text input changes (per character)        |
11//! | `onkeypress` | A key is pressed in a focused input     |
12//! | `onfocus`  | Element receives focus                    |
13//! | `onblur`   | Element loses focus                       |
14//! | `onchange` | Select / checkbox value changes           |
15
16use std::sync::Arc;
17
18/// The raw event payload passed to every handler.
19#[derive(Debug, Clone)]
20pub struct BubbaEvent {
21    /// The event name ("click", "input", "keypress", …).
22    pub kind: &'static str,
23    /// Optional string payload (e.g. the current value of an `<input>`).
24    pub value: Option<String>,
25    /// Optional key name for keyboard events.
26    pub key: Option<String>,
27}
28
29/// A boxed, type-erased event callback.
30pub type Callback = Arc<dyn Fn(BubbaEvent) + Send + Sync + 'static>;
31
32/// An event handler attached to a UI element.
33#[derive(Clone)]
34pub struct EventHandler {
35    /// The event name this handler listens to ("click", "input", …).
36    pub event: &'static str,
37    /// The callback invoked when the event fires.
38    pub callback: Callback,
39}
40
41impl std::fmt::Debug for EventHandler {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        f.debug_struct("EventHandler")
44            .field("event", &self.event)
45            .field("callback", &"<fn>")
46            .finish()
47    }
48}
49
50impl EventHandler {
51    /// Create a new event handler.
52    pub fn new<F>(event: &'static str, f: F) -> Self
53    where
54        F: Fn(BubbaEvent) + Send + Sync + 'static,
55    {
56        Self {
57            event,
58            callback: Arc::new(f),
59        }
60    }
61
62    /// Convenience: create an `onclick` handler.
63    pub fn onclick<F: Fn(BubbaEvent) + Send + Sync + 'static>(f: F) -> Self {
64        Self::new("click", f)
65    }
66
67    /// Convenience: create an `oninput` handler.
68    pub fn oninput<F: Fn(BubbaEvent) + Send + Sync + 'static>(f: F) -> Self {
69        Self::new("input", f)
70    }
71
72    /// Convenience: create an `onkeypress` handler.
73    pub fn onkeypress<F: Fn(BubbaEvent) + Send + Sync + 'static>(f: F) -> Self {
74        Self::new("keypress", f)
75    }
76
77    /// Convenience: create an `onfocus` handler.
78    pub fn onfocus<F: Fn(BubbaEvent) + Send + Sync + 'static>(f: F) -> Self {
79        Self::new("focus", f)
80    }
81
82    /// Convenience: create an `onblur` handler.
83    pub fn onblur<F: Fn(BubbaEvent) + Send + Sync + 'static>(f: F) -> Self {
84        Self::new("blur", f)
85    }
86
87    /// Dispatch this handler with a given event payload.
88    pub fn dispatch(&self, event: BubbaEvent) {
89        (self.callback)(event);
90    }
91}
92
93/// Global event dispatcher — the runtime calls this when Android fires an event.
94pub struct EventDispatcher {
95    /// Pending events waiting to be processed.
96    queue: std::sync::Mutex<Vec<(String, BubbaEvent)>>,
97}
98
99impl EventDispatcher {
100    /// Create a new dispatcher.
101    pub fn new() -> Self {
102        Self {
103            queue: std::sync::Mutex::new(Vec::new()),
104        }
105    }
106
107    /// Push a new event into the queue.
108    pub fn push(&self, element_id: impl Into<String>, event: BubbaEvent) {
109        let mut q = self.queue.lock().unwrap();
110        q.push((element_id.into(), event));
111    }
112
113    /// Drain and process all pending events.
114    pub fn flush(&self, handlers: &[(String, EventHandler)]) {
115        let events: Vec<(String, BubbaEvent)> = {
116            let mut q = self.queue.lock().unwrap();
117            std::mem::take(&mut *q)
118        };
119
120        for (element_id, event) in events {
121            for (handler_id, handler) in handlers {
122                if *handler_id == element_id && handler.event == event.kind {
123                    handler.dispatch(event.clone());
124                }
125            }
126        }
127    }
128}
129
130impl Default for EventDispatcher {
131    fn default() -> Self {
132        Self::new()
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use std::sync::atomic::{AtomicBool, Ordering};
140
141    #[test]
142    fn handler_fires() {
143        let fired = Arc::new(AtomicBool::new(false));
144        let fired_clone = Arc::clone(&fired);
145
146        let h = EventHandler::onclick(move |_e| {
147            fired_clone.store(true, Ordering::SeqCst);
148        });
149
150        h.dispatch(BubbaEvent {
151            kind: "click",
152            value: None,
153            key: None,
154        });
155
156        assert!(fired.load(Ordering::SeqCst));
157    }
158}