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.
96#[derive(Clone)]
97pub struct CommandLink<Msg: Send + 'static> {
98    scope: ScopeId,
99    tx: CommandTx,
100    cancellation_token: CancellationToken,
101    _marker: PhantomData<fn(Msg)>,
102}
103
104impl<Msg: Send + 'static> CommandLink<Msg> {
105    pub(crate) fn new(
106        scope: ScopeId,
107        tx: CommandTx,
108        cancellation_token: CancellationToken,
109    ) -> Self {
110        Self {
111            scope,
112            tx,
113            cancellation_token,
114            _marker: PhantomData,
115        }
116    }
117
118    /// Return the cooperative cancellation token for this command.
119    pub fn cancellation_token(&self) -> CancellationToken {
120        self.cancellation_token.clone()
121    }
122
123    /// Returns `true` when this command has been cancelled by the runtime.
124    pub fn is_cancelled(&self) -> bool {
125        self.cancellation_token.is_cancelled()
126    }
127
128    /// Send a message back to this component instance.
129    pub fn send(&self, msg: Msg) {
130        let _ = self.tx.send((self.scope, Box::new(msg)));
131    }
132
133    /// Send a message unless this command has already been cancelled.
134    pub fn send_if_not_cancelled(&self, msg: Msg) -> bool {
135        if self.is_cancelled() {
136            return false;
137        }
138        self.tx.send((self.scope, Box::new(msg))).is_ok()
139    }
140}
141
142/// Type-safe handle used to send messages to a specific component instance.
143pub struct Link<Msg: 'static> {
144    scope: ScopeId,
145    dispatcher: Dispatcher,
146    _marker: PhantomData<fn(Msg)>,
147}
148
149impl<Msg: 'static> Clone for Link<Msg> {
150    fn clone(&self) -> Self {
151        Self {
152            scope: self.scope,
153            dispatcher: self.dispatcher.clone(),
154            _marker: PhantomData,
155        }
156    }
157}
158
159impl<Msg: 'static> Link<Msg> {
160    pub(crate) fn new(scope: ScopeId, dispatcher: Dispatcher) -> Self {
161        Self {
162            scope,
163            dispatcher,
164            _marker: PhantomData,
165        }
166    }
167
168    /// Send a message to the component.
169    pub fn send(&self, msg: Msg) {
170        self.dispatcher.dispatch(self.scope, Box::new(msg));
171    }
172
173    /// Convert an event into a message.
174    pub fn callback<E: 'static>(&self, f: impl Fn(E) -> Msg + 'static) -> Callback<E> {
175        let link = (*self).clone();
176        Callback::new(move |e| link.send(f(e)))
177    }
178
179    /// Convert an event into an optional message.
180    ///
181    /// If the closure returns `None`, no message is sent.
182    pub fn callback_opt<E: 'static>(&self, f: impl Fn(E) -> Option<Msg> + 'static) -> Callback<E> {
183        let link = (*self).clone();
184        Callback::new(move |e| {
185            if let Some(msg) = f(e) {
186                link.send(msg);
187            }
188        })
189    }
190
191    /// Convert a key event into an optional message.
192    ///
193    /// Returns `true` when a message is produced.
194    pub fn key_handler(&self, f: impl Fn(KeyEvent) -> Option<Msg> + 'static) -> KeyHandler {
195        let link = (*self).clone();
196        KeyHandler::new(move |e| {
197            if let Some(msg) = f(e) {
198                link.send(msg);
199                true
200            } else {
201                false
202            }
203        })
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use std::any::Any;
210    use std::cell::RefCell;
211    use std::rc::Rc;
212
213    use super::{Dispatcher, KeyHandler, Link, ScopeId};
214    use crate::core::event::{KeyCode, KeyEvent, KeyMods};
215
216    type TestQueue = Rc<RefCell<Vec<(ScopeId, Box<dyn Any>)>>>;
217
218    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
219    enum Msg {
220        Ping,
221    }
222
223    fn key(code: KeyCode) -> KeyEvent {
224        KeyEvent {
225            code,
226            mods: KeyMods::default(),
227        }
228    }
229
230    #[test]
231    fn key_handler_respects_explicit_handled_flag() {
232        let queue: TestQueue = Rc::new(RefCell::new(Vec::new()));
233        let dispatcher = {
234            let queue = queue.clone();
235            Dispatcher::new(move |scope, msg| queue.borrow_mut().push((scope, msg)))
236        };
237        let link: Link<Msg> = Link::new(ScopeId(1), dispatcher);
238        let handler = KeyHandler::new({
239            let link = link.clone();
240            move |_key| {
241                link.send(Msg::Ping);
242                false
243            }
244        });
245
246        let handled = handler.handle(key(KeyCode::Enter));
247
248        assert!(!handled);
249        assert_eq!(queue.borrow().len(), 1);
250    }
251
252    #[test]
253    fn key_handler_returns_true_when_message_emitted() {
254        let queue: TestQueue = Rc::new(RefCell::new(Vec::new()));
255        let dispatcher = {
256            let queue = queue.clone();
257            Dispatcher::new(move |scope, msg| queue.borrow_mut().push((scope, msg)))
258        };
259        let link: Link<Msg> = Link::new(ScopeId(1), dispatcher);
260        let handler = link.key_handler(|key| match key.code {
261            KeyCode::Enter => Some(Msg::Ping),
262            _ => None,
263        });
264
265        assert!(handler.handle(key(KeyCode::Enter)));
266        assert_eq!(queue.borrow().len(), 1);
267        assert!(!handler.handle(key(KeyCode::Tab)));
268        assert_eq!(queue.borrow().len(), 1);
269    }
270}