zng_app/handler.rs
1//! Handler types and macros.
2
3use std::pin::Pin;
4use std::sync::Arc;
5
6use parking_lot::Mutex;
7#[doc(hidden)]
8pub use zng_clone_move::*;
9use zng_txt::Txt;
10
11use crate::update::UPDATES;
12use crate::widget::{UiTaskWidget as _, WIDGET};
13use zng_handle::WeakHandle;
14use zng_task::UiTask;
15
16/// Output of [`Handler<A>`].
17pub enum HandlerResult {
18 /// Handler already finished.
19 Done,
20 /// Handler is async and the future was pending after first poll. The caller must run the future in the same context the handler was called.
21 Continue(Pin<Box<dyn Future<Output = ()> + Send + 'static>>),
22}
23
24/// Represents a handler in a widget context.
25///
26/// There are different flavors of handlers, you can use macros to declare then.
27/// See [`hn!`], [`hn_once!`] or [`async_hn!`], [`async_hn_once!`] to start.
28///
29/// # Type Inference Limitations
30///
31/// This type is not a full struct because the closure args type inference only works with `Box`, if this was
32/// a full `struct` all handler declarations that use the args would have to declare the args type.
33/// Methods for this type are implemented in [`HandlerExt`]. Also note that the `A` type must be `Clone + 'static`,
34/// unfortunately Rust does not enforce bounds in type alias.
35#[allow(type_alias_bounds)] // we need a type alias here
36pub type Handler<A: Clone + 'static> = Box<dyn FnMut(&A) -> HandlerResult + Send + 'static>;
37
38/// Extension methods for [`Handler<A>`].
39pub trait HandlerExt<A: Clone + 'static> {
40 /// Notify the handler in a widget context.
41 ///
42 /// If the handler is async polls once immediately and returns an [`UiTask`] if the future is pending.
43 /// The caller must update the task until completion in the same widget context.
44 fn widget_event(&mut self, args: &A) -> Option<UiTask<()>>;
45
46 /// Notify the handler outside of any widget or window context, inside a [`APP_HANDLER`] context.
47 ///
48 /// If the handler is async polls once and continue execution in [`UPDATES`].
49 fn app_event(&mut self, handle: Box<dyn AppWeakHandle>, is_preview: bool, args: &A);
50
51 /// New handler that only calls for arguments approved by `filter`.
52 fn filtered(self, filter: impl FnMut(&A) -> bool + Send + 'static) -> Handler<A>;
53
54 /// New handler that calls this one only once.
55 fn into_once(self) -> Handler<A>;
56
57 /// Into cloneable handler.
58 ///
59 /// Note that [`hn_once!`] and [`async_hn_once!`] handlers will still only run once.
60 fn into_arc(self) -> ArcHandler<A>;
61
62 /// Wrap the handler into a type that implements the async task management in an widget context.
63 fn into_wgt_runner(self) -> WidgetRunner<A>;
64
65 /// Debug print handler calls and state.
66 fn trace(self, name: impl Into<Txt>) -> Handler<A>;
67}
68impl<A: Clone + 'static> HandlerExt<A> for Handler<A> {
69 fn widget_event(&mut self, args: &A) -> Option<UiTask<()>> {
70 match self(args) {
71 HandlerResult::Done => None,
72 HandlerResult::Continue(future) => {
73 let mut task = UiTask::new_boxed(Some(WIDGET.id()), future);
74 if task.update().is_none() { Some(task) } else { None }
75 }
76 }
77 }
78
79 fn app_event(&mut self, handle: Box<dyn AppWeakHandle>, is_preview: bool, args: &A) {
80 match APP_HANDLER.with(handle.clone_boxed(), is_preview, || self(args)) {
81 HandlerResult::Done => {}
82 HandlerResult::Continue(future) => {
83 let mut task = UiTask::new_boxed(None, future);
84 if APP_HANDLER.with(handle.clone_boxed(), is_preview, || task.update().is_none()) {
85 if is_preview {
86 UPDATES
87 .on_pre_update(hn!(|_| {
88 if APP_HANDLER.with(handle.clone_boxed(), is_preview, || task.update().is_some()) {
89 APP_HANDLER.unsubscribe();
90 }
91 }))
92 .perm();
93 } else {
94 UPDATES
95 .on_update(hn!(|_| {
96 if APP_HANDLER.with(handle.clone_boxed(), is_preview, || task.update().is_some()) {
97 APP_HANDLER.unsubscribe();
98 }
99 }))
100 .perm();
101 }
102 }
103 }
104 }
105 }
106
107 fn filtered(mut self, mut filter: impl FnMut(&A) -> bool + Send + 'static) -> Self {
108 Box::new(move |a| if filter(a) { self(a) } else { HandlerResult::Done })
109 }
110
111 fn into_once(self) -> Self {
112 let mut f = Some(self);
113 Box::new(move |a| {
114 if let Some(mut f) = f.take() {
115 APP_HANDLER.unsubscribe();
116 f(a)
117 } else {
118 HandlerResult::Done
119 }
120 })
121 }
122
123 fn into_arc(self) -> ArcHandler<A> {
124 ArcHandler(Arc::new(Mutex::new(self)))
125 }
126
127 fn into_wgt_runner(self) -> WidgetRunner<A> {
128 WidgetRunner::new(self)
129 }
130
131 fn trace(mut self, name: impl Into<Txt>) -> Handler<A> {
132 let name = name.into();
133 let mut fut_count = 0usize;
134 tracing::info!("handler {name} created");
135 Box::new(move |a| {
136 tracing::debug!("handler {name} called");
137 match self(a) {
138 HandlerResult::Done => {
139 tracing::info!("handler {name} call done");
140 HandlerResult::Done
141 }
142 HandlerResult::Continue(fut) => {
143 let fut_id = fut_count;
144 fut_count += 1;
145 tracing::info!("handler {name} call continues in future #{fut_id}");
146 struct TraceFut {
147 fut: Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
148 name: Txt,
149 fut_id: usize,
150 is_pending: bool,
151 }
152 impl Future for TraceFut {
153 type Output = ();
154
155 fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
156 match self.fut.as_mut().poll(cx) {
157 std::task::Poll::Ready(()) => {
158 tracing::info!("handler {} future #{} completed", self.name, self.fut_id);
159 self.is_pending = false;
160 std::task::Poll::Ready(())
161 }
162 std::task::Poll::Pending => {
163 self.is_pending = true;
164 std::task::Poll::Pending
165 }
166 }
167 }
168 }
169 impl Drop for TraceFut {
170 fn drop(&mut self) {
171 if self.is_pending {
172 tracing::warn!("handle {} future #{} dropped pending", self.name, self.fut_id);
173 } else {
174 tracing::debug!("handle {} future #{} dropped completed", self.name, self.fut_id);
175 }
176 }
177 }
178 HandlerResult::Continue(Box::pin(TraceFut {
179 fut,
180 name: name.clone(),
181 fut_id,
182 is_pending: true,
183 }))
184 }
185 }
186 })
187 }
188}
189
190/// Represents a cloneable handler.
191///
192/// See [`Handler::into_arc`] for more details.
193#[derive(Clone)]
194pub struct ArcHandler<A: Clone + 'static>(Arc<Mutex<Handler<A>>>);
195impl<A: Clone + 'static> ArcHandler<A> {
196 /// Calls [`HandlerExt::widget_event`].
197 pub fn widget_event(&self, args: &A) -> Option<UiTask<()>> {
198 self.0.lock().widget_event(args)
199 }
200
201 /// Calls [`HandlerExt::app_event`].
202 pub fn app_event(&self, handle: Box<dyn AppWeakHandle>, is_preview: bool, args: &A) {
203 self.0.lock().app_event(handle, is_preview, args)
204 }
205
206 /// Calls the handler.
207 pub fn call(&self, args: &A) -> HandlerResult {
208 self.0.lock()(args)
209 }
210
211 /// Make a handler from this arc handler.
212 pub fn handler(&self) -> Handler<A> {
213 self.clone().into()
214 }
215}
216impl<A: Clone + 'static> From<ArcHandler<A>> for Handler<A> {
217 fn from(f: ArcHandler<A>) -> Self {
218 Box::new(move |a| f.0.lock()(a))
219 }
220}
221
222/// Represents an widget [`Handler<A>`] caller that manages the async tasks if needed.
223///
224/// See [`Handler::into_wgt_runner`] for more details.
225pub struct WidgetRunner<A: Clone + 'static> {
226 handler: Handler<A>,
227 tasks: Vec<UiTask<()>>,
228}
229
230impl<A: Clone + 'static> WidgetRunner<A> {
231 fn new(handler: Handler<A>) -> Self {
232 Self { handler, tasks: vec![] }
233 }
234
235 /// Call [`HandlerExt::widget_event`] and start UI task is needed.
236 pub fn event(&mut self, args: &A) {
237 if let Some(task) = self.handler.widget_event(args) {
238 self.tasks.push(task);
239 }
240 }
241
242 /// Update async tasks.
243 ///
244 /// UI node implementers must call this on [`UiNodeOp::Update`].
245 /// For preview events before delegation to child, for other after delegation.
246 ///
247 /// [`UiNodeOp::Update`]: crate::widget::node::UiNodeOp::Update
248 pub fn update(&mut self) {
249 self.tasks.retain_mut(|t| t.update().is_none());
250 }
251
252 /// Drop pending tasks.
253 ///
254 /// Dropped tasks will log a warning.
255 ///
256 /// UI node implementers must call this on [`UiNodeOp::Deinit`], async tasks must not run across widget reinit.
257 ///
258 /// [`UiNodeOp::Deinit`]: crate::widget::node::UiNodeOp::Deinit
259 pub fn deinit(&mut self) {
260 self.tasks.clear();
261 }
262}
263
264///<span data-del-macro-root></span> Declare a mutable *clone-move* event handler.
265///
266/// The macro input is a closure with optional *clone-move* variables, internally it uses [`clmv!`] so
267/// the input is the same syntax.
268///
269/// # Examples
270///
271/// The example declares an event handler for the `on_click` property.
272///
273/// ```
274/// # macro_rules! example { () => {
275/// on_click = hn!(|_| {
276/// println!("Clicked {}!", args.click_count);
277/// });
278/// # }}
279/// ```
280///
281/// Internally the [`clmv!`] macro is used so you can *clone-move* variables into the handler.
282///
283/// ```
284/// # macro_rules! example { () => {
285/// let foo = var(0);
286///
287/// // ..
288///
289/// # let
290/// on_click = hn!(foo, |args| {
291/// foo.set(args.click_count);
292/// });
293///
294/// // can still use after:
295/// let bar = foo.map(|c| formatx!("click_count: {c}"));
296///
297/// # }}
298/// ```
299///
300/// In the example above only a clone of `foo` is moved into the handler. Note that handlers always capture by move, if `foo` was not
301/// listed in the *clone-move* section it would not be available after the handler is created. See [`clmv!`] for details.
302///
303/// # App Scope
304///
305/// When used in app scopes the [`APP_HANDLER`] contextual service can be used to unsubscribe from inside the handler.
306///
307/// The example declares an event handler for the `CLICK_EVENT`. Unlike in an widget this handler will run in the app scope, in this case
308/// the `APP_HANDLER` is available during handler calls, in the example the subscription handle is marked `perm`, but the event still unsubscribes
309/// from the inside.
310///
311/// ```
312/// # zng_app::event::event_args! { pub struct ClickArgs { pub target: zng_txt::Txt, pub click_count: usize, .. fn is_in_target(&self, _id: WidgetId) -> bool { true } } }
313/// # zng_app::event::event! { pub static CLICK_EVENT: ClickArgs; }
314/// # use zng_app::handler::{hn, APP_HANDLER};
315/// # let _scope = zng_app::APP.minimal();
316/// # fn assert_type() {
317/// CLICK_EVENT
318/// .on_event(
319/// false,
320/// hn!(|args| {
321/// println!("Clicked Somewhere!");
322/// if args.target == "something" {
323/// APP_HANDLER.unsubscribe();
324/// }
325/// }),
326/// )
327/// .perm();
328/// # }
329/// ```
330///
331/// [`clmv!`]: zng_clone_move::clmv
332#[macro_export]
333macro_rules! hn {
334 ($($clmv:ident,)* |_| $body:expr) => {
335 std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |_| {
336 #[allow(clippy::redundant_closure_call)] // closure is to support `return;`
337 (||{
338 $body
339 })();
340 #[allow(unused)]
341 {
342 $crate::handler::HandlerResult::Done
343 }
344 }))
345 };
346 ($($clmv:ident,)* |$args:ident| $body:expr) => {
347 std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args| {
348 #[allow(clippy::redundant_closure_call)]
349 (||{
350 $body
351 })();
352 #[allow(unused)]
353 {
354 $crate::handler::HandlerResult::Done
355 }
356 }))
357 };
358 ($($clmv:ident,)* |$args:ident : & $Args:ty| $body:expr) => {
359 std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &$Args| {
360 #[allow(clippy::redundant_closure_call)]
361 (||{
362 $body
363 })();
364 #[allow(unused)]
365 {
366 $crate::handler::HandlerResult::Done
367 }
368 }))
369 };
370}
371#[doc(inline)]
372pub use crate::hn;
373
374///<span data-del-macro-root></span> Declare a *clone-move* event handler that is only called once.
375///
376/// The macro input is a closure with optional *clone-move* variables, internally it uses [`clmv!`] so
377/// the input is the same syntax.
378///
379/// # Examples
380///
381/// The example captures `data` by move and then destroys it in the first call, this cannot be done using [`hn!`] because
382/// the `data` needs to be available for all event calls. In this case the closure is only called once, subsequent events
383/// are ignored by the handler.
384///
385/// ```
386/// # macro_rules! example { () => {
387/// let data = vec![1, 2, 3];
388/// # let
389/// on_click = hn_once!(|_| {
390/// for i in data {
391/// print!("{i}, ");
392/// }
393/// });
394/// # }}
395/// ```
396///
397/// [`clmv!`]: zng_clone_move::clmv
398#[macro_export]
399macro_rules! hn_once {
400 ($($clmv:ident,)* |_| $body:expr) => {{
401 let mut once: Option<std::boxed::Box<dyn FnOnce() + Send + 'static>> =
402 Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* || { $body })));
403 $crate::handler::hn!(|_| if let Some(f) = once.take() {
404 $crate::handler::APP_HANDLER.unsubscribe();
405 f();
406 })
407 }};
408 ($($clmv:ident,)* |$args:ident| $body:expr) => {{
409 // type inference fails here, error message slightly better them not having this pattern
410 let mut once: Option<std::boxed::Box<dyn FnOnce(&_) + Send + 'static>> =
411 Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &_| { $body })));
412 $crate::handler::hn!(|$args: &_| if let Some(f) = once.take() {
413 $crate::handler::APP_HANDLER.unsubscribe();
414 f($args);
415 })
416 }};
417 ($($clmv:ident,)* |$args:ident : & $Args:ty| $body:expr) => {{
418 // type inference fails here, error message slightly better them not having this pattern
419 let mut once: Option<std::boxed::Box<dyn FnOnce(&$Args) + Send + 'static>> =
420 Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &$Args| { $body })));
421 $crate::handler::hn!(|$args: &$Args| if let Some(f) = once.take() {
422 $crate::handler::APP_HANDLER.unsubscribe();
423 f($args);
424 })
425 }};
426}
427#[doc(inline)]
428pub use crate::hn_once;
429
430///<span data-del-macro-root></span> Declare an async *clone-move* event handler.
431///
432/// The macro input is a closure with optional *clone-move* variables, internally it uses [`clmv!`] so
433/// the input is the same syntax, for each call is also uses [`async_clmv!`] to clone the args and other cloning captures.
434///
435/// # Examples
436///
437/// The example declares an async event handler for the `on_click` property.
438///
439/// ```
440/// # macro_rules! example { () => {
441/// on_click = async_hn!(|args| {
442/// println!("Clicked {} {} times!", WIDGET.id(), args.click_count);
443///
444/// task::run(async {
445/// println!("In other thread!");
446/// })
447/// .await;
448///
449/// println!("Back in UI thread, in a widget update.");
450/// });
451/// # }}
452/// ```
453///
454/// Internally the [`clmv!`] macro is used so you can *clone-move* variables into the handler.
455///
456/// ```
457/// # zng_app::event::event_args! { pub struct ClickArgs { pub target: zng_txt::Txt, pub click_count: usize, .. fn is_in_target(&self, _id: WidgetId) -> bool { true } } }
458/// # use zng_app::handler::async_hn;
459/// # use zng_var::{var, Var};
460/// # use zng_task as task;
461/// # use zng_txt::formatx;
462/// # let _scope = zng_app::APP.minimal();
463/// # fn assert_type() -> zng_app::handler::Handler<ClickArgs> {
464/// let enabled = var(true);
465///
466/// // ..
467///
468/// # let
469/// on_click = async_hn!(enabled, |args: &ClickArgs| {
470/// enabled.set(false);
471///
472/// task::run(async move {
473/// println!("do something {}", args.click_count);
474/// })
475/// .await;
476///
477/// enabled.set(true);
478/// });
479///
480/// // can still use after:
481/// # let
482/// text = enabled.map(|&e| if e { "Click Me!" } else { "Busy.." });
483/// enabled;
484///
485/// # on_click }
486/// ```
487///
488/// In the example above only a clone of `enabled` is moved into the handler. Note that handlers always capture by move, if `enabled` was not
489/// listed in the *clone-move* section it would not be available after the handler is created. See [`async_clmv_fn!`] for details.
490///
491/// The example also demonstrates a common pattern with async handlers, most events are only raised when the widget is enabled, so you can
492/// disable the widget while the async task is running. This way you don't block the UI running a task but the user cannot spawn a second
493/// task while the first is still running.
494///
495/// ## Futures and Clone-Move
496///
497/// You want to always *clone-move* captures for async handlers, because they then automatically get cloned again for each event. This
498/// needs to happen because you can have more then one *handler task* running at the same type, and both want access to the captured variables.
499///
500/// This second cloning can be avoided by using the [`async_hn_once!`] macro instead, but only if you expect a single event.
501///
502/// Note that this means you are declaring a normal closure that returns a `'static` future, not an async closure, see [`async_clmv_fn!`].
503///
504/// [`async_clmv_fn!`]: zng_clone_move::async_clmv_fn
505#[macro_export]
506macro_rules! async_hn {
507 ($($clmv:ident,)* |_| $body:expr) => {
508 std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |_| {
509 $crate::handler::HandlerResult::Continue(std::boxed::Box::pin($crate::handler::async_clmv!($($clmv,)* {$body})))
510 }))
511 };
512 ($($clmv:ident,)* |$args:ident| $body:expr) => {
513 std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args| {
514 $crate::handler::HandlerResult::Continue(std::boxed::Box::pin($crate::handler::async_clmv!($args, $($clmv,)* {$body})))
515 }))
516 };
517 ($($clmv:ident,)* |$args:ident : & $Args:ty| $body:expr) => {
518 std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &$Args| {
519 $crate::handler::HandlerResult::Continue(std::boxed::Box::pin($crate::handler::async_clmv!($args, $($clmv,)* {$body})))
520 }))
521 };
522}
523#[doc(inline)]
524pub use crate::async_hn;
525
526///<span data-del-macro-root></span> Declare an async *clone-move* event handler that is only called once.
527///
528/// The macro input is a closure with optional *clone-move* variables, internally it uses [`clmv!`] so
529/// the input is the same syntax.
530///
531/// # Examples
532///
533/// The example captures `data` by move and then moves it again to another thread. This is not something you can do using [`async_hn!`]
534/// because that handler expects to be called many times. We expect `on_open` to only be called once, so we can don't need to capture by
535/// *clone-move* here just to use `data`.
536///
537/// ```
538/// # macro_rules! example { () => {
539/// let data = vec![1, 2, 3];
540/// # let
541/// on_open = async_hn_once!(|_| {
542/// task::run(async move {
543/// for i in data {
544/// print!("{i}, ");
545/// }
546/// })
547/// .await;
548///
549/// println!("Done!");
550/// });
551/// # }}
552/// ```
553///
554/// You can still *clone-move* to have access to the variable after creating the handler, in this case the `data` will be cloned into the handler
555/// but will just be moved to the other thread, avoiding a needless clone.
556///
557/// ```
558/// # macro_rules! example { () => {
559/// let data = vec![1, 2, 3];
560/// # let
561/// on_open = async_hn_once!(data, |_| {
562/// task::run(async move {
563/// for i in data {
564/// print!("{i}, ");
565/// }
566/// })
567/// .await;
568///
569/// println!("Done!");
570/// });
571/// println!("{data:?}");
572/// # }}
573/// ```
574///
575/// [`async_clmv_fn_once!`]: zng_clone_move::async_clmv_fn_once
576#[macro_export]
577macro_rules! async_hn_once {
578 ($($clmv:ident,)* |_| $body:expr) => {
579 {
580 let mut once: Option<std::boxed::Box<dyn FnOnce() -> std::pin::Pin<std::boxed::Box<dyn Future<Output = ()> + Send + 'static>> + Send + 'static>>
581 = Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* || {
582 $crate::handler::APP_HANDLER.unsubscribe();
583 std::boxed::Box::pin($crate::handler::async_clmv!($($clmv,)* { $body }))
584 })));
585
586 std::boxed::Box::new(move |_| if let Some(f) = once.take() {
587 $crate::handler::HandlerResult::Continue(f())
588 } else {
589 $crate::handler::HandlerResult::Done
590 })
591 }
592 };
593 ($($clmv:ident,)* |$args:ident| $body:expr) => {
594 {
595 let mut once: Option<std::boxed::Box<dyn FnOnce(&_) -> std::pin::Pin<std::boxed::Box<dyn Future<Output = ()> + Send + 'static>> + Send + 'static>>
596 = Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &_| {
597 $crate::handler::APP_HANDLER.unsubscribe();
598 std::boxed::Box::pin($crate::handler::async_clmv!($args, $($clmv,)* { $body }))
599 })));
600
601 std::boxed::Box::new(move |$args: &_| if let Some(f) = once.take() {
602 $crate::handler::HandlerResult::Continue(f($args))
603 } else {
604 $crate::handler::HandlerResult::Done
605 })
606 }
607 };
608 ($($clmv:ident,)* |$args:ident : & $Args:ty| $body:expr) => {
609 {
610 let mut once: Option<std::boxed::Box<dyn FnOnce(&$Args) -> std::pin::Pin<std::boxed::Box<dyn Future<Output = ()> + Send + 'static>> + Send + 'static>>
611 = Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &$Args| {
612 $crate::handler::APP_HANDLER.unsubscribe();
613 std::boxed::Box::pin($crate::handler::async_clmv!($args, $($clmv,)* { $body }))
614 })));
615
616 std::boxed::Box::new(move |$args: &$Args| if let Some(f) = once.take() {
617 $crate::handler::HandlerResult::Continue(f($args))
618 } else {
619 $crate::handler::HandlerResult::Done
620 })
621 }
622 };
623}
624#[doc(inline)]
625pub use crate::async_hn_once;
626
627/// Represents a weak handle to a [`Handler`] subscription in the app context.
628///
629/// Inside the handler use [`APP_HANDLER`] to access this handle.
630pub trait AppWeakHandle: Send + Sync + 'static {
631 /// Dynamic clone.
632 fn clone_boxed(&self) -> Box<dyn AppWeakHandle>;
633
634 /// Unsubscribes the [`Handler`].
635 ///
636 /// This stops the handler from being called again and causes it to be dropped in a future app update.
637 fn unsubscribe(&self);
638}
639impl<D: Send + Sync + 'static> AppWeakHandle for WeakHandle<D> {
640 fn clone_boxed(&self) -> Box<dyn AppWeakHandle> {
641 Box::new(self.clone())
642 }
643
644 fn unsubscribe(&self) {
645 if let Some(handle) = self.upgrade() {
646 handle.force_drop();
647 }
648 }
649}
650
651/// Service available in app scoped [`Handler<A>`] calls.
652#[allow(non_camel_case_types)]
653pub struct APP_HANDLER;
654
655impl APP_HANDLER {
656 /// Acquire a weak reference to the event subscription handle if the handler is being called in the app scope.
657 pub fn weak_handle(&self) -> Option<Box<dyn AppWeakHandle>> {
658 if let Some(ctx) = &*APP_HANDLER_CTX.get() {
659 Some(ctx.handle.clone_boxed())
660 } else {
661 None
662 }
663 }
664
665 /// Unsubscribe, if the handler is being called in the app scope.
666 pub fn unsubscribe(&self) {
667 if let Some(h) = self.weak_handle() {
668 h.unsubscribe();
669 }
670 }
671
672 /// If the handler is being called in the *preview* track.
673 pub fn is_preview(&self) -> bool {
674 if let Some(ctx) = &*APP_HANDLER_CTX.get() {
675 ctx.is_preview
676 } else {
677 false
678 }
679 }
680
681 /// Calls `f` with the `handle` and `is_preview` values in context.
682 pub fn with<R>(&self, handle: Box<dyn AppWeakHandle>, is_preview: bool, f: impl FnOnce() -> R) -> R {
683 APP_HANDLER_CTX.with_context(&mut Some(Arc::new(Some(AppHandlerCtx { handle, is_preview }))), f)
684 }
685}
686zng_app_context::context_local! {
687 static APP_HANDLER_CTX: Option<AppHandlerCtx> = None;
688}
689
690struct AppHandlerCtx {
691 handle: Box<dyn AppWeakHandle>,
692 is_preview: bool,
693}
694
695#[cfg(test)]
696mod tests {
697 use crate::{APP, handler::Handler};
698
699 #[test]
700 fn hn_return() {
701 t(hn!(|args| {
702 if args.field {
703 return;
704 }
705 println!("else");
706 }))
707 }
708
709 #[test]
710 fn hn_once_return() {
711 t(hn_once!(|args: &TestArgs| {
712 if args.field {
713 return;
714 }
715 println!("else");
716 }))
717 }
718
719 #[test]
720 fn async_hn_return() {
721 t(async_hn!(|args| {
722 if args.field {
723 return;
724 }
725 args.task().await;
726 }))
727 }
728
729 #[test]
730 fn async_hn_once_return() {
731 t(async_hn_once!(|args: &TestArgs| {
732 if args.field {
733 return;
734 }
735 args.task().await;
736 }))
737 }
738
739 #[test]
740 fn run_test_runs() {
741 let mut app = APP.minimal().run_headless(false);
742 app.run_test(async { zng_task::deadline(std::time::Duration::from_millis(30)).await })
743 .unwrap();
744 }
745
746 fn t(_: Handler<TestArgs>) {}
747
748 #[derive(Clone, Default)]
749 struct TestArgs {
750 pub field: bool,
751 }
752
753 impl TestArgs {
754 async fn task(&self) {}
755 }
756}