Skip to main content

tui_lipan/
callback.rs

1use std::any::Any;
2use std::marker::PhantomData;
3use std::rc::Rc;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::{Arc, mpsc};
6
7use crate::core::event::KeyEvent;
8
9/// A cheap-to-clone event handler.
10#[derive(Clone)]
11pub struct Callback<E>(Rc<dyn Fn(E)>);
12
13impl<E> Callback<E> {
14    /// Create a new callback.
15    pub fn new(f: impl Fn(E) + 'static) -> Self {
16        Self(Rc::new(f))
17    }
18
19    /// Invoke the callback.
20    pub fn emit(&self, event: E) {
21        (self.0)(event)
22    }
23}
24
25impl<E> PartialEq for Callback<E> {
26    fn eq(&self, other: &Self) -> bool {
27        Rc::ptr_eq(&self.0, &other.0)
28    }
29}
30
31impl<E> Eq for Callback<E> {}
32
33/// A cheap-to-clone key handler that reports handled status.
34#[derive(Clone)]
35pub struct KeyHandler(Rc<dyn Fn(KeyEvent) -> bool>);
36
37impl KeyHandler {
38    /// Create a new key handler.
39    pub fn new(f: impl Fn(KeyEvent) -> bool + 'static) -> Self {
40        Self(Rc::new(f))
41    }
42
43    /// Invoke the handler and return whether it handled the key.
44    pub fn handle(&self, event: KeyEvent) -> bool {
45        (self.0)(event)
46    }
47}
48
49impl PartialEq for KeyHandler {
50    fn eq(&self, other: &Self) -> bool {
51        Rc::ptr_eq(&self.0, &other.0)
52    }
53}
54
55/// Identifies a mounted component instance.
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57pub struct ScopeId(pub u32);
58
59/// Message dispatcher used by `Link`.
60#[derive(Clone)]
61pub struct Dispatcher(Rc<dyn Fn(ScopeId, Box<dyn Any>)>);
62
63impl Dispatcher {
64    /// Create a new dispatcher.
65    pub fn new(f: impl Fn(ScopeId, Box<dyn Any>) + 'static) -> Self {
66        Self(Rc::new(f))
67    }
68
69    /// Dispatch a boxed message to a component scope.
70    pub fn dispatch(&self, scope: ScopeId, msg: Box<dyn Any>) {
71        (self.0)(scope, msg)
72    }
73}
74
75pub(crate) type CommandTx = mpsc::Sender<(ScopeId, Box<dyn Any + Send>)>;
76pub(crate) type CommandRx = mpsc::Receiver<(ScopeId, Box<dyn Any + Send>)>;
77
78/// Cooperative cancellation state for a background command.
79#[derive(Clone, Debug, Default)]
80pub struct CancellationToken {
81    cancelled: Arc<AtomicBool>,
82}
83
84impl CancellationToken {
85    /// Returns `true` when the owning command has been cancelled by the runtime.
86    pub fn is_cancelled(&self) -> bool {
87        self.cancelled.load(Ordering::Acquire)
88    }
89
90    pub(crate) fn cancel(&self) {
91        self.cancelled.store(true, Ordering::Release);
92    }
93}
94
95/// Type-safe handle used by background tasks to send messages back to the UI thread.
96pub struct CommandLink<Msg: Send + 'static> {
97    scope: ScopeId,
98    tx: CommandTx,
99    cancellation_token: CancellationToken,
100    _marker: PhantomData<fn(Msg)>,
101}
102
103/// Hand-written so cloning does not require `Msg: Clone`. `#[derive(Clone)]` would bound the impl
104/// on the message type, and `link.clone()` would then silently resolve to cloning the `&` instead —
105/// borrowing the link rather than sharing it, which fails to escape into a task.
106impl<Msg: Send + 'static> Clone for CommandLink<Msg> {
107    fn clone(&self) -> Self {
108        Self {
109            scope: self.scope,
110            tx: self.tx.clone(),
111            cancellation_token: self.cancellation_token.clone(),
112            _marker: PhantomData,
113        }
114    }
115}
116
117impl<Msg: Send + 'static> CommandLink<Msg> {
118    pub(crate) fn new(
119        scope: ScopeId,
120        tx: CommandTx,
121        cancellation_token: CancellationToken,
122    ) -> Self {
123        Self {
124            scope,
125            tx,
126            cancellation_token,
127            _marker: PhantomData,
128        }
129    }
130
131    /// Return the cooperative cancellation token for this command.
132    pub fn cancellation_token(&self) -> CancellationToken {
133        self.cancellation_token.clone()
134    }
135
136    /// Returns `true` when this command has been cancelled by the runtime.
137    pub fn is_cancelled(&self) -> bool {
138        self.cancellation_token.is_cancelled()
139    }
140
141    /// Send a message back to this component instance.
142    pub fn send(&self, msg: Msg) {
143        let _ = self.tx.send((self.scope, Box::new(msg)));
144    }
145
146    /// Send a message unless this command has already been cancelled.
147    pub fn send_if_not_cancelled(&self, msg: Msg) -> bool {
148        if self.is_cancelled() {
149            return false;
150        }
151        self.tx.send((self.scope, Box::new(msg))).is_ok()
152    }
153
154    /// Send a message after `delay`, without holding a thread while it waits.
155    ///
156    /// The shared timer thread does the waiting, so this is the right way to arm a debounce or a
157    /// recurring tick from code that already holds a link — spawning a thread per delay, or
158    /// sleeping inside a task, both cost far more than the message being delivered.
159    ///
160    /// The message is dropped if the command is cancelled before the delay elapses.
161    pub fn send_after(&self, delay: std::time::Duration, msg: Msg) {
162        let link = self.clone();
163        crate::core::component::schedule_after(delay, move || {
164            link.send_if_not_cancelled(msg);
165        });
166    }
167}
168
169/// Type-safe handle used to send messages to a specific component instance.
170pub struct Link<Msg: 'static> {
171    scope: ScopeId,
172    dispatcher: Dispatcher,
173    _marker: PhantomData<fn(Msg)>,
174}
175
176impl<Msg: 'static> Clone for Link<Msg> {
177    fn clone(&self) -> Self {
178        Self {
179            scope: self.scope,
180            dispatcher: self.dispatcher.clone(),
181            _marker: PhantomData,
182        }
183    }
184}
185
186impl<Msg: 'static> Link<Msg> {
187    pub(crate) fn new(scope: ScopeId, dispatcher: Dispatcher) -> Self {
188        Self {
189            scope,
190            dispatcher,
191            _marker: PhantomData,
192        }
193    }
194
195    /// Send a message to the component.
196    pub fn send(&self, msg: Msg) {
197        self.dispatcher.dispatch(self.scope, Box::new(msg));
198    }
199
200    /// Convert an event into a message.
201    pub fn callback<E: 'static>(&self, f: impl Fn(E) -> Msg + 'static) -> Callback<E> {
202        let link = (*self).clone();
203        Callback::new(move |e| link.send(f(e)))
204    }
205
206    /// Convert an event into an optional message.
207    ///
208    /// If the closure returns `None`, no message is sent.
209    pub fn callback_opt<E: 'static>(&self, f: impl Fn(E) -> Option<Msg> + 'static) -> Callback<E> {
210        let link = (*self).clone();
211        Callback::new(move |e| {
212            if let Some(msg) = f(e) {
213                link.send(msg);
214            }
215        })
216    }
217
218    /// Convert a key event into an optional message.
219    ///
220    /// Returns `true` when a message is produced.
221    pub fn key_handler(&self, f: impl Fn(KeyEvent) -> Option<Msg> + 'static) -> KeyHandler {
222        let link = (*self).clone();
223        KeyHandler::new(move |e| {
224            if let Some(msg) = f(e) {
225                link.send(msg);
226                true
227            } else {
228                false
229            }
230        })
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use std::any::Any;
237    use std::cell::RefCell;
238    use std::rc::Rc;
239
240    use super::{Dispatcher, KeyHandler, Link, ScopeId};
241    use crate::core::event::{KeyCode, KeyEvent, KeyMods};
242
243    type TestQueue = Rc<RefCell<Vec<(ScopeId, Box<dyn Any>)>>>;
244
245    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
246    enum Msg {
247        Ping,
248    }
249
250    fn key(code: KeyCode) -> KeyEvent {
251        KeyEvent {
252            code,
253            mods: KeyMods::default(),
254        }
255    }
256
257    #[test]
258    fn key_handler_respects_explicit_handled_flag() {
259        let queue: TestQueue = Rc::new(RefCell::new(Vec::new()));
260        let dispatcher = {
261            let queue = queue.clone();
262            Dispatcher::new(move |scope, msg| queue.borrow_mut().push((scope, msg)))
263        };
264        let link: Link<Msg> = Link::new(ScopeId(1), dispatcher);
265        let handler = KeyHandler::new({
266            let link = link.clone();
267            move |_key| {
268                link.send(Msg::Ping);
269                false
270            }
271        });
272
273        let handled = handler.handle(key(KeyCode::Enter));
274
275        assert!(!handled);
276        assert_eq!(queue.borrow().len(), 1);
277    }
278
279    #[test]
280    fn key_handler_returns_true_when_message_emitted() {
281        let queue: TestQueue = Rc::new(RefCell::new(Vec::new()));
282        let dispatcher = {
283            let queue = queue.clone();
284            Dispatcher::new(move |scope, msg| queue.borrow_mut().push((scope, msg)))
285        };
286        let link: Link<Msg> = Link::new(ScopeId(1), dispatcher);
287        let handler = link.key_handler(|key| match key.code {
288            KeyCode::Enter => Some(Msg::Ping),
289            _ => None,
290        });
291
292        assert!(handler.handle(key(KeyCode::Enter)));
293        assert_eq!(queue.borrow().len(), 1);
294        assert!(!handler.handle(key(KeyCode::Tab)));
295        assert_eq!(queue.borrow().len(), 1);
296    }
297}