use std::pin::Pin;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use parking_lot::Mutex;
#[doc(hidden)]
pub use zng_clone_move::*;
use crate::update::UPDATES;
use crate::widget::{UiTaskWidget as _, WIDGET};
use crate::{AppControlFlow, HeadlessApp};
use zng_handle::{Handle, WeakHandle};
use zng_task::{self as task, UiTask};
use crate::INSTANT;
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>;
}
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)
}
}
#[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: 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,
}
impl HeadlessApp {
pub fn block_on<A>(&mut self, handler: &mut Handler<A>, args: &A, timeout: Duration) -> Result<(), String>
where
A: Clone + 'static,
{
self.block_on_multi(vec![handler], args, timeout)
}
pub fn block_on_multi<A>(&mut self, handlers: Vec<&mut Handler<A>>, args: &A, timeout: Duration) -> Result<(), String>
where
A: Clone + 'static,
{
let (pre_len, pos_len) = UPDATES.handler_lens();
let handle = Handle::dummy(()).downgrade();
for handler in handlers {
handler.app_event(handle.clone_boxed(), false, args);
}
let mut pending = UPDATES.new_update_handlers(pre_len, pos_len);
if !pending.is_empty() {
let start_time = INSTANT.now();
while {
pending.retain(|h| h());
!pending.is_empty()
} {
UPDATES.update(None);
let flow = self.update(false);
if INSTANT.now().duration_since(start_time) >= timeout {
return Err(format!(
"block_on reached timeout of {timeout:?} before the handler task could finish",
));
}
match flow {
AppControlFlow::Poll => continue,
AppControlFlow::Wait => {
thread::yield_now();
continue;
}
AppControlFlow::Exit => return Ok(()),
}
}
}
Ok(())
}
pub fn block_on_fut<F: Future>(&mut self, future: F, timeout: Duration) -> Result<F::Output, String> {
let future = task::with_deadline(future, timeout);
let mut future = std::pin::pin!(future);
let waker = UPDATES.waker(None);
let mut cx = std::task::Context::from_waker(&waker);
loop {
let mut fut_poll = future.as_mut().poll(&mut cx);
let flow = self.update_observe(
|| {
if fut_poll.is_pending() {
fut_poll = future.as_mut().poll(&mut cx);
}
},
true,
);
match fut_poll {
std::task::Poll::Ready(r) => match r {
Ok(r) => return Ok(r),
Err(e) => return Err(e.to_string()),
},
std::task::Poll::Pending => {}
}
match flow {
AppControlFlow::Poll => continue,
AppControlFlow::Wait => {
thread::yield_now();
continue;
}
AppControlFlow::Exit => return Err("app exited".to_owned()),
}
}
}
#[track_caller]
#[cfg(any(test, doc, feature = "test_util"))]
pub fn doc_test<A, H>(args: A, mut handler: Handler<A>)
where
A: Clone + 'static,
{
let mut app = crate::APP.minimal().run_headless(false);
app.block_on(&mut handler, &args, DOC_TEST_BLOCK_ON_TIMEOUT).unwrap();
}
#[track_caller]
#[cfg(any(test, doc, feature = "test_util"))]
pub fn doc_test_multi<A>(args: A, mut handlers: Vec<Handler<A>>)
where
A: Clone + 'static,
{
let mut app = crate::APP.minimal().run_headless(false);
app.block_on_multi(handlers.iter_mut().collect(), &args, DOC_TEST_BLOCK_ON_TIMEOUT)
.unwrap()
}
}
#[cfg(any(test, doc, feature = "test_util"))]
const DOC_TEST_BLOCK_ON_TIMEOUT: Duration = Duration::from_secs(60);
#[cfg(test)]
mod tests {
use crate::handler::{Handler, async_hn, async_hn_once, hn, hn_once};
#[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;
}))
}
fn t(_: Handler<TestArgs>) {}
#[derive(Clone, Default)]
struct TestArgs {
pub field: bool,
}
impl TestArgs {
async fn task(&self) {}
}
}