makepad_platform/
ui_runner.rs

1use crate::*;
2use std::marker::PhantomData;
3use std::sync::Mutex;
4
5/// Run code on the UI thread from another thread.
6///
7/// Allows you to mix non-blocking threaded code, with code that reads and updates
8/// your widget in the UI thread.
9///
10/// This can be copied and passed around.
11pub struct UiRunner<T> {
12    /// Trick to later distinguish actions sent globally thru `Cx::post_action`.
13    key: usize,
14    /// Enforce a consistent `target` type across `handle` and `defer`.
15    ///
16    /// `fn() -> W` is used instead of `W` because, in summary, these:
17    /// - https://stackoverflow.com/a/50201389
18    /// - https://doc.rust-lang.org/nomicon/phantom-data.html#table-of-phantomdata-patterns
19    /// - https://doc.rust-lang.org/std/marker/struct.PhantomData.html
20    target: PhantomData<fn() -> T>,
21}
22
23impl<T> Copy for UiRunner<T> {}
24
25impl<T> Clone for UiRunner<T> {
26    fn clone(&self) -> Self {
27        Self {
28            key: self.key,
29            target: PhantomData,
30        }
31    }
32}
33
34impl<T> std::fmt::Debug for UiRunner<T> {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        f.debug_struct("UiRunner").field("key", &self.key).finish()
37    }
38}
39impl<T> PartialEq for UiRunner<T> {
40    fn eq(&self, other: &Self) -> bool {
41        self.key == other.key
42    }
43}
44
45impl<T: 'static> UiRunner<T> {
46    /// Create a new `UiRunner` that dispatches functions as global actions but
47    /// differentiates them by the provided `key`.
48    ///
49    /// If used in a widget, prefer using `your_widget.ui_runner()`.
50    /// If used in your app main, prefer using `your_app_main.ui_runner()`.
51    pub fn new(key: usize) -> Self {
52        Self {
53            key,
54            target: PhantomData,
55        }
56    }
57
58    /// Handle all functions scheduled with the `key` of this `UiRunner`.
59    ///
60    /// You should call this once from your `handle_event` method, like:
61    ///
62    /// ```rust
63    /// fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
64    ///    // ... handle other stuff ...
65    ///    self.ui_runner().handle(cx, event, scope, self);
66    /// }
67    /// ```
68    ///
69    /// Once a function has been handled, it will never run again.
70    pub fn handle(self, cx: &mut Cx, event: &Event, scope: &mut Scope, target: &mut T) {
71        if let Event::Actions(actions) = event {
72            for action in actions {
73                if let Some(action) = action.downcast_ref::<UiRunnerAction<T>>() {
74                    if action.key != self.key {
75                        continue;
76                    }
77
78                    if let Some(f) = action.f.lock().unwrap().take() {
79                        (f)(target, cx, scope);
80                    }
81                }
82            }
83        }
84    }
85
86    /// Schedule the provided closure to run on the UI thread.
87    pub fn defer(self, f: impl DeferCallback<T>) {
88        let action = UiRunnerAction {
89            f: Mutex::new(Some(Box::new(f))),
90            key: self.key,
91        };
92
93        Cx::post_action(action);
94    }
95
96    /// Like `defer`, but blocks the current thread until the UI awakes, processes
97    /// the closure, and returns the result.
98    ///
99    /// Generally, you should prefer to use `defer` if you don't need to communicate
100    /// a value back. This method may wait a long time if the UI thread is busy so you
101    /// should not use it in tight loops.
102    pub fn block_on<R: Send + 'static>(
103        self,
104        f: impl FnOnce(&mut T, &mut Cx, &mut Scope) -> R + Send + 'static,
105    ) -> R {
106        let (tx, rx) = std::sync::mpsc::channel();
107        self.defer(move |target, cx, scope| {
108            tx.send(f(target, cx, scope)).unwrap();
109        });
110        rx.recv().unwrap()
111    }
112}
113
114/// Private message that is sent to the ui thread with the closure to run.
115struct UiRunnerAction<T> {
116    f: Mutex<Option<Box<dyn DeferCallback<T>>>>,
117    key: usize,
118}
119
120impl<T> std::fmt::Debug for UiRunnerAction<T> {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        f.debug_struct("UiRunnerAction")
123            .field("key", &self.key)
124            .field("f", &"...")
125            .finish()
126    }
127}
128
129pub trait DeferCallback<T>: FnOnce(&mut T, &mut Cx, &mut Scope) + Send + 'static {}
130impl<T, F: FnOnce(&mut T, &mut Cx, &mut Scope) + Send + 'static> DeferCallback<T> for F {}