use std::pin::Pin;
use std::sync::Arc;
use parking_lot::Mutex;
#[doc(hidden)]
pub use zng_clone_move::*;
use zng_txt::Txt;
use crate::update::UPDATES;
use crate::widget::{UiTaskWidget as _, WIDGET};
use zng_handle::WeakHandle;
use zng_task::UiTask;
pub enum HandlerResult {
Done,
Continue(Pin<Box<dyn Future<Output = ()> + Send + 'static>>),
}
#[allow(type_alias_bounds)] pub type Handler<A: Clone + 'static> = Box<dyn FnMut(&A) -> HandlerResult + Send + 'static>;
pub trait HandlerExt<A: Clone + 'static> {
fn widget_event(&mut self, args: &A) -> Option<UiTask<()>>;
fn app_event(&mut self, handle: Box<dyn AppWeakHandle>, is_preview: bool, args: &A);
fn filtered(self, filter: impl FnMut(&A) -> bool + Send + 'static) -> Handler<A>;
fn into_once(self) -> Handler<A>;
fn into_arc(self) -> ArcHandler<A>;
fn into_wgt_runner(self) -> WidgetRunner<A>;
fn trace(self, name: impl Into<Txt>) -> Handler<A>;
}
impl<A: Clone + 'static> HandlerExt<A> for Handler<A> {
fn widget_event(&mut self, args: &A) -> Option<UiTask<()>> {
match self(args) {
HandlerResult::Done => None,
HandlerResult::Continue(future) => {
let mut task = UiTask::new_boxed(Some(WIDGET.id()), future);
if task.update().is_none() { Some(task) } else { None }
}
}
}
fn app_event(&mut self, handle: Box<dyn AppWeakHandle>, is_preview: bool, args: &A) {
match APP_HANDLER.with(handle.clone_boxed(), is_preview, || self(args)) {
HandlerResult::Done => {}
HandlerResult::Continue(future) => {
let mut task = UiTask::new_boxed(None, future);
if APP_HANDLER.with(handle.clone_boxed(), is_preview, || task.update().is_none()) {
if is_preview {
UPDATES
.on_pre_update(hn!(|_| {
if APP_HANDLER.with(handle.clone_boxed(), is_preview, || task.update().is_some()) {
APP_HANDLER.unsubscribe();
}
}))
.perm();
} else {
UPDATES
.on_update(hn!(|_| {
if APP_HANDLER.with(handle.clone_boxed(), is_preview, || task.update().is_some()) {
APP_HANDLER.unsubscribe();
}
}))
.perm();
}
}
}
}
}
fn filtered(mut self, mut filter: impl FnMut(&A) -> bool + Send + 'static) -> Self {
Box::new(move |a| if filter(a) { self(a) } else { HandlerResult::Done })
}
fn into_once(self) -> Self {
let mut f = Some(self);
Box::new(move |a| {
if let Some(mut f) = f.take() {
APP_HANDLER.unsubscribe();
f(a)
} else {
HandlerResult::Done
}
})
}
fn into_arc(self) -> ArcHandler<A> {
ArcHandler(Arc::new(Mutex::new(self)))
}
fn into_wgt_runner(self) -> WidgetRunner<A> {
WidgetRunner::new(self)
}
fn trace(mut self, name: impl Into<Txt>) -> Handler<A> {
let name = name.into();
let mut fut_count = 0usize;
tracing::info!("handler {name} created");
Box::new(move |a| {
tracing::debug!("handler {name} called");
match self(a) {
HandlerResult::Done => {
tracing::info!("handler {name} call done");
HandlerResult::Done
}
HandlerResult::Continue(fut) => {
let fut_id = fut_count;
fut_count += 1;
tracing::info!("handler {name} call continues in future #{fut_id}");
struct TraceFut {
fut: Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
name: Txt,
fut_id: usize,
is_pending: bool,
}
impl Future for TraceFut {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
match self.fut.as_mut().poll(cx) {
std::task::Poll::Ready(()) => {
tracing::info!("handler {} future #{} completed", self.name, self.fut_id);
self.is_pending = false;
std::task::Poll::Ready(())
}
std::task::Poll::Pending => {
self.is_pending = true;
std::task::Poll::Pending
}
}
}
}
impl Drop for TraceFut {
fn drop(&mut self) {
if self.is_pending {
tracing::warn!("handle {} future #{} dropped pending", self.name, self.fut_id);
} else {
tracing::debug!("handle {} future #{} dropped completed", self.name, self.fut_id);
}
}
}
HandlerResult::Continue(Box::pin(TraceFut {
fut,
name: name.clone(),
fut_id,
is_pending: true,
}))
}
}
})
}
}
#[derive(Clone)]
pub struct ArcHandler<A: Clone + 'static>(Arc<Mutex<Handler<A>>>);
impl<A: Clone + 'static> ArcHandler<A> {
pub fn widget_event(&self, args: &A) -> Option<UiTask<()>> {
self.0.lock().widget_event(args)
}
pub fn app_event(&self, handle: Box<dyn AppWeakHandle>, is_preview: bool, args: &A) {
self.0.lock().app_event(handle, is_preview, args)
}
pub fn call(&self, args: &A) -> HandlerResult {
self.0.lock()(args)
}
pub fn handler(&self) -> Handler<A> {
self.clone().into()
}
}
impl<A: Clone + 'static> From<ArcHandler<A>> for Handler<A> {
fn from(f: ArcHandler<A>) -> Self {
Box::new(move |a| f.0.lock()(a))
}
}
pub struct WidgetRunner<A: Clone + 'static> {
handler: Handler<A>,
tasks: Vec<UiTask<()>>,
}
impl<A: Clone + 'static> WidgetRunner<A> {
fn new(handler: Handler<A>) -> Self {
Self { handler, tasks: vec![] }
}
pub fn event(&mut self, args: &A) {
if let Some(task) = self.handler.widget_event(args) {
self.tasks.push(task);
}
}
pub fn update(&mut self) {
self.tasks.retain_mut(|t| t.update().is_none());
}
pub fn deinit(&mut self) {
self.tasks.clear();
}
}
#[macro_export]
macro_rules! hn {
($($clmv:ident,)* |_| $body:expr) => {
std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |_| {
#[allow(clippy::redundant_closure_call)] (||{
$body
})();
#[allow(unused)]
{
$crate::handler::HandlerResult::Done
}
}))
};
($($clmv:ident,)* |$args:ident| $body:expr) => {
std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args| {
#[allow(clippy::redundant_closure_call)]
(||{
$body
})();
#[allow(unused)]
{
$crate::handler::HandlerResult::Done
}
}))
};
($($clmv:ident,)* |$args:ident : & $Args:ty| $body:expr) => {
std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &$Args| {
#[allow(clippy::redundant_closure_call)]
(||{
$body
})();
#[allow(unused)]
{
$crate::handler::HandlerResult::Done
}
}))
};
}
#[doc(inline)]
pub use crate::hn;
#[macro_export]
macro_rules! hn_once {
($($clmv:ident,)* |_| $body:expr) => {{
let mut once: Option<std::boxed::Box<dyn FnOnce() + Send + 'static>> =
Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* || { $body })));
$crate::handler::hn!(|_| if let Some(f) = once.take() {
$crate::handler::APP_HANDLER.unsubscribe();
f();
})
}};
($($clmv:ident,)* |$args:ident| $body:expr) => {{
let mut once: Option<std::boxed::Box<dyn FnOnce(&_) + Send + 'static>> =
Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &_| { $body })));
$crate::handler::hn!(|$args: &_| if let Some(f) = once.take() {
$crate::handler::APP_HANDLER.unsubscribe();
f($args);
})
}};
($($clmv:ident,)* |$args:ident : & $Args:ty| $body:expr) => {{
let mut once: Option<std::boxed::Box<dyn FnOnce(&$Args) + Send + 'static>> =
Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &$Args| { $body })));
$crate::handler::hn!(|$args: &$Args| if let Some(f) = once.take() {
$crate::handler::APP_HANDLER.unsubscribe();
f($args);
})
}};
}
#[doc(inline)]
pub use crate::hn_once;
#[macro_export]
macro_rules! async_hn {
($($clmv:ident,)* |_| $body:expr) => {
std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |_| {
$crate::handler::HandlerResult::Continue(std::boxed::Box::pin($crate::handler::async_clmv!($($clmv,)* {$body})))
}))
};
($($clmv:ident,)* |$args:ident| $body:expr) => {
std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args| {
$crate::handler::HandlerResult::Continue(std::boxed::Box::pin($crate::handler::async_clmv!($args, $($clmv,)* {$body})))
}))
};
($($clmv:ident,)* |$args:ident : & $Args:ty| $body:expr) => {
std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &$Args| {
$crate::handler::HandlerResult::Continue(std::boxed::Box::pin($crate::handler::async_clmv!($args, $($clmv,)* {$body})))
}))
};
}
#[doc(inline)]
pub use crate::async_hn;
#[macro_export]
macro_rules! async_hn_once {
($($clmv:ident,)* |_| $body:expr) => {
{
let mut once: Option<std::boxed::Box<dyn FnOnce() -> std::pin::Pin<std::boxed::Box<dyn Future<Output = ()> + Send + 'static>> + Send + 'static>>
= Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* || {
$crate::handler::APP_HANDLER.unsubscribe();
std::boxed::Box::pin($crate::handler::async_clmv!($($clmv,)* { $body }))
})));
std::boxed::Box::new(move |_| if let Some(f) = once.take() {
$crate::handler::HandlerResult::Continue(f())
} else {
$crate::handler::HandlerResult::Done
})
}
};
($($clmv:ident,)* |$args:ident| $body:expr) => {
{
let mut once: Option<std::boxed::Box<dyn FnOnce(&_) -> std::pin::Pin<std::boxed::Box<dyn Future<Output = ()> + Send + 'static>> + Send + 'static>>
= Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &_| {
$crate::handler::APP_HANDLER.unsubscribe();
std::boxed::Box::pin($crate::handler::async_clmv!($args, $($clmv,)* { $body }))
})));
std::boxed::Box::new(move |$args: &_| if let Some(f) = once.take() {
$crate::handler::HandlerResult::Continue(f($args))
} else {
$crate::handler::HandlerResult::Done
})
}
};
($($clmv:ident,)* |$args:ident : & $Args:ty| $body:expr) => {
{
let mut once: Option<std::boxed::Box<dyn FnOnce(&$Args) -> std::pin::Pin<std::boxed::Box<dyn Future<Output = ()> + Send + 'static>> + Send + 'static>>
= Some(std::boxed::Box::new($crate::handler::clmv!($($clmv,)* |$args: &$Args| {
$crate::handler::APP_HANDLER.unsubscribe();
std::boxed::Box::pin($crate::handler::async_clmv!($args, $($clmv,)* { $body }))
})));
std::boxed::Box::new(move |$args: &$Args| if let Some(f) = once.take() {
$crate::handler::HandlerResult::Continue(f($args))
} else {
$crate::handler::HandlerResult::Done
})
}
};
}
#[doc(inline)]
pub use crate::async_hn_once;
pub trait AppWeakHandle: Send + Sync + 'static {
fn clone_boxed(&self) -> Box<dyn AppWeakHandle>;
fn unsubscribe(&self);
}
impl<D: Send + Sync + 'static> AppWeakHandle for WeakHandle<D> {
fn clone_boxed(&self) -> Box<dyn AppWeakHandle> {
Box::new(self.clone())
}
fn unsubscribe(&self) {
if let Some(handle) = self.upgrade() {
handle.force_drop();
}
}
}
#[allow(non_camel_case_types)]
pub struct APP_HANDLER;
impl APP_HANDLER {
pub fn weak_handle(&self) -> Option<Box<dyn AppWeakHandle>> {
if let Some(ctx) = &*APP_HANDLER_CTX.get() {
Some(ctx.handle.clone_boxed())
} else {
None
}
}
pub fn unsubscribe(&self) {
if let Some(h) = self.weak_handle() {
h.unsubscribe();
}
}
pub fn is_preview(&self) -> bool {
if let Some(ctx) = &*APP_HANDLER_CTX.get() {
ctx.is_preview
} else {
false
}
}
pub fn with<R>(&self, handle: Box<dyn AppWeakHandle>, is_preview: bool, f: impl FnOnce() -> R) -> R {
APP_HANDLER_CTX.with_context(&mut Some(Arc::new(Some(AppHandlerCtx { handle, is_preview }))), f)
}
}
zng_app_context::context_local! {
static APP_HANDLER_CTX: Option<AppHandlerCtx> = None;
}
struct AppHandlerCtx {
handle: Box<dyn AppWeakHandle>,
is_preview: bool,
}
#[cfg(test)]
mod tests {
use crate::{APP, handler::Handler};
#[test]
fn hn_return() {
t(hn!(|args| {
if args.field {
return;
}
println!("else");
}))
}
#[test]
fn hn_once_return() {
t(hn_once!(|args: &TestArgs| {
if args.field {
return;
}
println!("else");
}))
}
#[test]
fn async_hn_return() {
t(async_hn!(|args| {
if args.field {
return;
}
args.task().await;
}))
}
#[test]
fn async_hn_once_return() {
t(async_hn_once!(|args: &TestArgs| {
if args.field {
return;
}
args.task().await;
}))
}
#[test]
fn run_test_runs() {
let mut app = APP.minimal().run_headless(false);
app.run_test(async { zng_task::deadline(std::time::Duration::from_millis(30)).await })
.unwrap();
}
fn t(_: Handler<TestArgs>) {}
#[derive(Clone, Default)]
struct TestArgs {
pub field: bool,
}
impl TestArgs {
async fn task(&self) {}
}
}