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 {}