use std::{
any::{Any, TypeId},
sync::Arc,
};
use zng_app::{
event::{Command, CommandArgs, CommandHandle, CommandScope, Event, EventArgs},
handler::WidgetHandler,
render::{FrameBuilder, FrameValueKey},
update::WidgetUpdates,
widget::{
border::{BORDER, BORDER_ALIGN_VAR, BORDER_OVER_VAR},
info::Interactivity,
node::*,
VarLayout, WidgetUpdateMode, WIDGET,
},
window::WINDOW,
};
use zng_app_context::{ContextLocal, LocalContext};
use zng_layout::{
context::LAYOUT,
unit::{PxConstraints2d, PxCornerRadius, PxPoint, PxRect, PxSideOffsets, PxSize, PxVector, SideOffsets},
};
use zng_state_map::{StateId, StateMapRef, StateValue};
use zng_var::{types::VecChange, *};
#[doc(hidden)]
pub use paste::paste;
#[doc(hidden)]
pub use zng_app;
pub fn with_context_var<T: VarValue>(child: impl UiNode, context_var: ContextVar<T>, value: impl IntoVar<T>) -> impl UiNode {
let value = value.into_var();
let mut actual_value = None;
let mut id = None;
match_node(child, move |child, op| {
let mut is_deinit = false;
match &op {
UiNodeOp::Init => {
id = Some(ContextInitHandle::new());
actual_value = Some(Arc::new(value.clone().actual_var().boxed()));
}
UiNodeOp::Deinit => {
is_deinit = true;
}
_ => {}
}
context_var.with_context(id.clone().expect("node not inited"), &mut actual_value, || child.op(op));
if is_deinit {
id = None;
actual_value = None;
}
})
}
pub fn with_context_var_init<T: VarValue>(
child: impl UiNode,
var: ContextVar<T>,
mut init_value: impl FnMut() -> BoxedVar<T> + Send + 'static,
) -> impl UiNode {
let mut id = None;
let mut value = None;
match_node(child, move |child, op| {
let mut is_deinit = false;
match &op {
UiNodeOp::Init => {
id = Some(ContextInitHandle::new());
value = Some(Arc::new(init_value().actual_var()));
}
UiNodeOp::Deinit => {
is_deinit = true;
}
_ => {}
}
var.with_context(id.clone().expect("node not inited"), &mut value, || child.op(op));
if is_deinit {
id = None;
value = None;
}
})
}
#[macro_export]
macro_rules! event_property {
($(
$(#[$on_event_attrs:meta])*
$vis:vis fn $event:ident {
event: $EVENT:path,
args: $Args:path,
$(filter: $filter:expr,)?
$(widget_impl: $Wgt:ty,)?
$(with: $with:expr,)?
}
)+) => {$(
$crate::__event_property! {
done {
sig { $(#[$on_event_attrs])* $vis fn $event { event: $EVENT, args: $Args, } }
}
$(filter: $filter,)?
$(widget_impl: $Wgt,)?
$(with: $with,)?
}
)+};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __event_property {
(
done {
$($done:tt)+
}
filter: $filter:expr,
$($rest:tt)*
) => {
$crate::__event_property! {
done {
$($done)+
filter { $filter }
}
$($rest)*
}
};
(
done {
$($done:tt)+
}
widget_impl: $Wgt:ty,
$($rest:tt)*
) => {
$crate::__event_property! {
done {
$($done)+
widget_impl { , widget_impl($Wgt) }
}
$($rest)*
}
};
(
done {
$($done:tt)+
}
with: $with:expr,
) => {
$crate::__event_property! {
done {
$($done)+
with { $with }
}
}
};
(
done {
sig { $($sig:tt)+ }
}
) => {
$crate::__event_property! {
done {
sig { $($sig)+ }
filter { |_args| true }
widget_impl { }
with { }
}
}
};
(
done {
sig { $($sig:tt)+ }
filter { $($filter:tt)+ }
}
) => {
$crate::__event_property! {
done {
sig { $($sig)+ }
filter { $($filter)+ }
widget_impl { }
with { }
}
}
};
(
done {
sig { $($sig:tt)+ }
widget_impl { $($widget_impl:tt)+ }
}
) => {
$crate::__event_property! {
done {
sig { $($sig)+ }
filter { |_args| true }
widget_impl { $($widget_impl)+ }
with { }
}
}
};
(
done {
sig { $($sig:tt)+ }
with { $($with:tt)+ }
}
) => {
$crate::__event_property! {
done {
sig { $($sig)+ }
filter { |_args| true }
widget_impl { }
with { $($with)+ }
}
}
};
(
done {
sig { $($sig:tt)+ }
filter { $($filter:tt)+ }
widget_impl { $($widget_impl:tt)+ }
}
) => {
$crate::__event_property! {
done {
sig { $($sig)+ }
filter { $($filter)+ }
widget_impl { $($widget_impl)+ }
with { }
}
}
};
(
done {
sig { $($sig:tt)+ }
filter { $($filter:tt)+ }
with { $($with:tt)+ }
}
) => {
$crate::__event_property! {
done {
sig { $($sig)+ }
filter { $($filter)+ }
widget_impl { }
with { $($with)+ }
}
}
};
(
done {
sig { $(#[$on_event_attrs:meta])* $vis:vis fn $event:ident { event: $EVENT:path, args: $Args:path, } }
filter { $filter:expr }
widget_impl { $($widget_impl:tt)* }
with { $($with:expr)? }
}
) => {
$crate::node::paste! {
$(#[$on_event_attrs])*
#[doc = "You can preview this event using [`on_pre_"$event "`](fn.on_pre_"$event ".html)."]
#[$crate::node::zng_app::widget::property(
EVENT,
default( $crate::node::zng_app::handler::hn!(|_|{}) )
$($widget_impl)*
)]
$vis fn [<on_ $event>](
child: impl $crate::node::zng_app::widget::node::UiNode,
handler: impl $crate::node::zng_app::handler::WidgetHandler<$Args>,
) -> impl $crate::node::zng_app::widget::node::UiNode {
$crate::__event_property!(=> with($crate::node::on_event(child, $EVENT, $filter, handler), false, $($with)?))
}
#[doc = "Preview [`on_"$event "`](fn.on_"$event ".html) event."]
#[$crate::node::zng_app::widget::property(
EVENT,
default( $crate::node::zng_app::handler::hn!(|_|{}) )
$($widget_impl)*
)]
$vis fn [<on_pre_ $event>](
child: impl $crate::node::zng_app::widget::node::UiNode,
handler: impl $crate::node::zng_app::handler::WidgetHandler<$Args>,
) -> impl $crate::node::zng_app::widget::node::UiNode {
$crate::__event_property!(=> with($crate::node::on_pre_event(child, $EVENT, $filter, handler), true, $($with)?))
}
}
};
(=> with($child:expr, $preview:expr,)) => { $child };
(=> with($child:expr, $preview:expr, $with:expr)) => { ($with)($child, $preview) };
}
pub fn on_event<C, A, F, H>(child: C, event: Event<A>, filter: F, handler: H) -> impl UiNode
where
C: UiNode,
A: EventArgs,
F: FnMut(&A) -> bool + Send + 'static,
H: WidgetHandler<A>,
{
#[cfg(feature = "dyn_closure")]
let filter: Box<dyn FnMut(&A) -> bool + Send> = Box::new(filter);
on_event_impl(child.cfg_boxed(), event, filter, handler.cfg_boxed()).cfg_boxed()
}
fn on_event_impl<C, A, F, H>(child: C, event: Event<A>, mut filter: F, mut handler: H) -> impl UiNode
where
C: UiNode,
A: EventArgs,
F: FnMut(&A) -> bool + Send + 'static,
H: WidgetHandler<A>,
{
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
WIDGET.sub_event(&event);
}
UiNodeOp::Event { update } => {
child.event(update);
if let Some(args) = event.on(update) {
if !args.propagation().is_stopped() && filter(args) {
handler.event(args);
}
}
}
UiNodeOp::Update { updates } => {
child.update(updates);
handler.update();
}
_ => {}
})
}
pub fn on_pre_event<C, A, F, H>(child: C, event: Event<A>, filter: F, handler: H) -> impl UiNode
where
C: UiNode,
A: EventArgs,
F: FnMut(&A) -> bool + Send + 'static,
H: WidgetHandler<A>,
{
#[cfg(feature = "dyn_closure")]
let filter: Box<dyn FnMut(&A) -> bool + Send> = Box::new(filter);
on_pre_event_impl(child.cfg_boxed(), event, filter, handler.cfg_boxed()).cfg_boxed()
}
fn on_pre_event_impl<C, A, F, H>(child: C, event: Event<A>, mut filter: F, mut handler: H) -> impl UiNode
where
C: UiNode,
A: EventArgs,
F: FnMut(&A) -> bool + Send + 'static,
H: WidgetHandler<A>,
{
match_node(child, move |_, op| match op {
UiNodeOp::Init => {
WIDGET.sub_event(&event);
}
UiNodeOp::Event { update } => {
if let Some(args) = event.on(update) {
if !args.propagation().is_stopped() && filter(args) {
handler.event(args);
}
}
}
UiNodeOp::Update { .. } => {
handler.update();
}
_ => {}
})
}
#[doc(hidden)]
#[macro_export]
macro_rules! __command_property {
(
$(#[$on_cmd_attrs:meta])*
$vis:vis fn $command:ident {
cmd: $cmd_init:expr,
enabled: $enabled_var:expr,
}
) => { $crate::node::paste! {
$(#[$on_cmd_attrs])*
#[doc = "You can preview this command event using [`on_pre_"$command "`](fn.on_pre_"$command ".html)."]
#[$crate::node::zng_app::widget::property(EVENT, default( $crate::node::zng_app::handler::hn!(|_|{}) ))]
$vis fn [<on_ $command>](
child: impl $crate::node::zng_app::widget::node::UiNode,
handler: impl $crate::node::zng_app::handler::WidgetHandler<$crate::node::zng_app::event::CommandArgs>,
) -> impl $crate::node::zng_app::widget::node::UiNode {
$crate::node::on_command(child, || $cmd_init, || $enabled_var, handler)
}
#[doc = "Preview [`on_"$command "`](fn.on_"$command ".html) command."]
#[$crate::node::zng_app::widget::property(EVENT, default( $crate::node::zng_app::handler::hn!(|_|{}) ))]
$vis fn [<on_pre_ $command>](
child: impl $crate::node::zng_app::widget::node::UiNode,
handler: impl $crate::node::zng_app::handler::WidgetHandler<$crate::node::zng_app::event::CommandArgs>,
) -> impl $crate::node::zng_app::widget::node::UiNode {
$crate::node::on_pre_command(child, || $cmd_init, || $enabled_var, handler)
}
} };
(
$(#[$on_cmd_attrs:meta])*
$vis:vis fn $command:ident {
cmd: $cmd_init:expr,
}
) => {
$crate::__command_property! {
$(#[$on_cmd_attrs])*
$vis fn $command {
cmd: $cmd_init,
enabled: $crate::node::zng_app::var::LocalVar(true),
}
}
};
}
#[macro_export]
macro_rules! command_property {
($(
$(#[$on_cmd_attrs:meta])*
$vis:vis fn $command:ident {
cmd: $cmd_init:expr$(,
enabled: $enabled_var:expr)? $(,)?
}
)+) => {$(
$crate::__command_property! {
$(#[$on_cmd_attrs])*
$vis fn $command {
cmd: $cmd_init,
$(enabled: $enabled_var,)?
}
}
)+};
}
pub fn on_command<U, CB, E, EB, H>(child: U, command_builder: CB, enabled_builder: EB, handler: H) -> impl UiNode
where
U: UiNode,
CB: FnMut() -> Command + Send + 'static,
E: Var<bool>,
EB: FnMut() -> E + Send + 'static,
H: WidgetHandler<CommandArgs>,
{
#[cfg(feature = "dyn_closure")]
let command_builder: Box<dyn FnMut() -> Command + Send> = Box::new(command_builder);
#[cfg(feature = "dyn_closure")]
let enabled_builder: Box<dyn FnMut() -> E + Send> = Box::new(enabled_builder);
on_command_impl(child.boxed(), command_builder, enabled_builder, handler.cfg_boxed()).cfg_boxed()
}
fn on_command_impl<U, CB, E, EB, H>(child: U, mut command_builder: CB, mut enabled_builder: EB, handler: H) -> impl UiNode
where
U: UiNode,
CB: FnMut() -> Command + Send + 'static,
E: Var<bool>,
EB: FnMut() -> E + Send + 'static,
H: WidgetHandler<CommandArgs>,
{
let mut enabled = None;
let mut handle = CommandHandle::dummy();
let mut win_handle = CommandHandle::dummy();
let mut command = NIL_CMD;
let mut handler = handler.cfg_boxed();
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
child.init();
let e = enabled_builder();
WIDGET.sub_var(&e);
let is_enabled = e.get();
enabled = Some(e);
command = command_builder();
let id = WIDGET.id();
handle = command.subscribe_wgt(is_enabled, id);
if CommandScope::Widget(id) == command.scope() && WIDGET.parent_id().is_none() {
win_handle = command.scoped(WINDOW.id()).subscribe_wgt(is_enabled, id);
}
}
UiNodeOp::Deinit => {
child.deinit();
enabled = None;
handle = CommandHandle::dummy();
win_handle = CommandHandle::dummy();
command = NIL_CMD;
}
UiNodeOp::Event { update } => {
child.event(update);
if let Some(args) = command.on_unhandled(update) {
handler.event(args);
} else if !win_handle.is_dummy() {
if let Some(args) = command.scoped(WINDOW.id()).on_unhandled(update) {
handler.event(args);
}
}
}
UiNodeOp::Update { updates } => {
child.update(updates);
handler.update();
if let Some(enabled) = enabled.as_ref().expect("node not inited").get_new() {
handle.set_enabled(enabled);
win_handle.set_enabled(enabled);
}
}
_ => {}
})
}
zng_app::event::command! {
static NIL_CMD;
}
pub fn on_pre_command<U, CB, E, EB, H>(child: U, command_builder: CB, enabled_builder: EB, handler: H) -> impl UiNode
where
U: UiNode,
CB: FnMut() -> Command + Send + 'static,
E: Var<bool>,
EB: FnMut() -> E + Send + 'static,
H: WidgetHandler<CommandArgs>,
{
#[cfg(feature = "dyn_closure")]
let command_builder: Box<dyn FnMut() -> Command + Send> = Box::new(command_builder);
#[cfg(feature = "dyn_closure")]
let enabled_builder: Box<dyn FnMut() -> E + Send> = Box::new(enabled_builder);
on_pre_command_impl(child.cfg_boxed(), command_builder, enabled_builder, handler.cfg_boxed()).cfg_boxed()
}
fn on_pre_command_impl<U, CB, E, EB, H>(child: U, mut command_builder: CB, mut enabled_builder: EB, handler: H) -> impl UiNode
where
U: UiNode,
CB: FnMut() -> Command + Send + 'static,
E: Var<bool>,
EB: FnMut() -> E + Send + 'static,
H: WidgetHandler<CommandArgs>,
{
let mut handler = handler.cfg_boxed();
let mut enabled = None;
let mut handle = CommandHandle::dummy();
let mut win_handle = CommandHandle::dummy();
let mut command = NIL_CMD;
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
child.init();
let e = enabled_builder();
WIDGET.sub_var(&e);
let is_enabled = e.get();
enabled = Some(e);
command = command_builder();
let id = WIDGET.id();
handle = command.subscribe_wgt(is_enabled, id);
if CommandScope::Widget(id) == command.scope() && WIDGET.parent_id().is_none() {
win_handle = command.scoped(WINDOW.id()).subscribe_wgt(is_enabled, id);
}
}
UiNodeOp::Deinit => {
child.deinit();
enabled = None;
handle = CommandHandle::dummy();
win_handle = CommandHandle::dummy();
command = NIL_CMD;
}
UiNodeOp::Event { update } => {
if let Some(args) = command.on_unhandled(update) {
handler.event(args);
} else if !win_handle.is_dummy() {
if let Some(args) = command.scoped(WINDOW.id()).on_unhandled(update) {
handler.event(args);
}
}
}
UiNodeOp::Update { .. } => {
handler.update();
if let Some(enabled) = enabled.as_ref().expect("on_pre_command not initialized").get_new() {
handle.set_enabled(enabled);
win_handle.set_enabled(enabled);
}
}
_ => {}
})
}
pub fn validate_getter_var<T: VarValue>(_var: &impl Var<T>) {
#[cfg(debug_assertions)]
if _var.capabilities().is_always_read_only() {
tracing::error!(
"`is_`, `has_` or `get_` property inited with read-only var in `{}`",
WIDGET.trace_id()
);
}
}
pub fn event_state<A: EventArgs, S: VarValue>(
child: impl UiNode,
state: impl IntoVar<S>,
default: S,
event: Event<A>,
mut on_event: impl FnMut(&A) -> Option<S> + Send + 'static,
) -> impl UiNode {
let state = state.into_var();
match_node(child, move |_, op| match op {
UiNodeOp::Init => {
validate_getter_var(&state);
WIDGET.sub_event(&event);
let _ = state.set(default.clone());
}
UiNodeOp::Deinit => {
let _ = state.set(default.clone());
}
UiNodeOp::Event { update } => {
if let Some(args) = event.on(update) {
if let Some(s) = on_event(args) {
let _ = state.set(s);
}
}
}
_ => {}
})
}
#[allow(clippy::too_many_arguments)]
pub fn event_state2<A0, A1, S0, S1, S>(
child: impl UiNode,
state: impl IntoVar<S>,
default: S,
event0: Event<A0>,
default0: S0,
mut on_event0: impl FnMut(&A0) -> Option<S0> + Send + 'static,
event1: Event<A1>,
default1: S1,
mut on_event1: impl FnMut(&A1) -> Option<S1> + Send + 'static,
mut merge: impl FnMut(S0, S1) -> Option<S> + Send + 'static,
) -> impl UiNode
where
A0: EventArgs,
A1: EventArgs,
S0: VarValue,
S1: VarValue,
S: VarValue,
{
let state = state.into_var();
let partial_default = (default0, default1);
let mut partial = partial_default.clone();
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
validate_getter_var(&state);
WIDGET.sub_event(&event0).sub_event(&event1);
partial = partial_default.clone();
let _ = state.set(default.clone());
}
UiNodeOp::Deinit => {
let _ = state.set(default.clone());
}
UiNodeOp::Event { update } => {
let mut updated = false;
if let Some(args) = event0.on(update) {
if let Some(state) = on_event0(args) {
if partial.0 != state {
partial.0 = state;
updated = true;
}
}
} else if let Some(args) = event1.on(update) {
if let Some(state) = on_event1(args) {
if partial.1 != state {
partial.1 = state;
updated = true;
}
}
}
child.event(update);
if updated {
if let Some(value) = merge(partial.0.clone(), partial.1.clone()) {
let _ = state.set(value);
}
}
}
_ => {}
})
}
#[allow(clippy::too_many_arguments)]
pub fn event_state3<A0, A1, A2, S0, S1, S2, S>(
child: impl UiNode,
state: impl IntoVar<S>,
default: S,
event0: Event<A0>,
default0: S0,
mut on_event0: impl FnMut(&A0) -> Option<S0> + Send + 'static,
event1: Event<A1>,
default1: S1,
mut on_event1: impl FnMut(&A1) -> Option<S1> + Send + 'static,
event2: Event<A2>,
default2: S2,
mut on_event2: impl FnMut(&A2) -> Option<S2> + Send + 'static,
mut merge: impl FnMut(S0, S1, S2) -> Option<S> + Send + 'static,
) -> impl UiNode
where
A0: EventArgs,
A1: EventArgs,
A2: EventArgs,
S0: VarValue,
S1: VarValue,
S2: VarValue,
S: VarValue,
{
let state = state.into_var();
let partial_default = (default0, default1, default2);
let mut partial = partial_default.clone();
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
validate_getter_var(&state);
WIDGET.sub_event(&event0).sub_event(&event1).sub_event(&event2);
partial = partial_default.clone();
let _ = state.set(default.clone());
}
UiNodeOp::Deinit => {
let _ = state.set(default.clone());
}
UiNodeOp::Event { update } => {
let mut updated = false;
if let Some(args) = event0.on(update) {
if let Some(state) = on_event0(args) {
if partial.0 != state {
partial.0 = state;
updated = true;
}
}
} else if let Some(args) = event1.on(update) {
if let Some(state) = on_event1(args) {
if partial.1 != state {
partial.1 = state;
updated = true;
}
}
} else if let Some(args) = event2.on(update) {
if let Some(state) = on_event2(args) {
if partial.2 != state {
partial.2 = state;
updated = true;
}
}
}
child.event(update);
if updated {
if let Some(value) = merge(partial.0.clone(), partial.1.clone(), partial.2.clone()) {
let _ = state.set(value);
}
}
}
_ => {}
})
}
#[allow(clippy::too_many_arguments)]
pub fn event_state4<A0, A1, A2, A3, S0, S1, S2, S3, S>(
child: impl UiNode,
state: impl IntoVar<S>,
default: S,
event0: Event<A0>,
default0: S0,
mut on_event0: impl FnMut(&A0) -> Option<S0> + Send + 'static,
event1: Event<A1>,
default1: S1,
mut on_event1: impl FnMut(&A1) -> Option<S1> + Send + 'static,
event2: Event<A2>,
default2: S2,
mut on_event2: impl FnMut(&A2) -> Option<S2> + Send + 'static,
event3: Event<A3>,
default3: S3,
mut on_event3: impl FnMut(&A3) -> Option<S3> + Send + 'static,
mut merge: impl FnMut(S0, S1, S2, S3) -> Option<S> + Send + 'static,
) -> impl UiNode
where
A0: EventArgs,
A1: EventArgs,
A2: EventArgs,
A3: EventArgs,
S0: VarValue,
S1: VarValue,
S2: VarValue,
S3: VarValue,
S: VarValue,
{
let state = state.into_var();
let partial_default = (default0, default1, default2, default3);
let mut partial = partial_default.clone();
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
validate_getter_var(&state);
WIDGET.sub_event(&event0).sub_event(&event1).sub_event(&event2).sub_event(&event3);
partial = partial_default.clone();
let _ = state.set(default.clone());
}
UiNodeOp::Deinit => {
let _ = state.set(default.clone());
}
UiNodeOp::Event { update } => {
let mut updated = false;
if let Some(args) = event0.on(update) {
if let Some(state) = on_event0(args) {
if partial.0 != state {
partial.0 = state;
updated = true;
}
}
} else if let Some(args) = event1.on(update) {
if let Some(state) = on_event1(args) {
if partial.1 != state {
partial.1 = state;
updated = true;
}
}
} else if let Some(args) = event2.on(update) {
if let Some(state) = on_event2(args) {
if partial.2 != state {
partial.2 = state;
updated = true;
}
}
} else if let Some(args) = event3.on(update) {
if let Some(state) = on_event3(args) {
if partial.3 != state {
partial.3 = state;
updated = true;
}
}
}
child.event(update);
if updated {
if let Some(value) = merge(partial.0.clone(), partial.1.clone(), partial.2.clone(), partial.3.clone()) {
let _ = state.set(value);
}
}
}
_ => {}
})
}
pub fn bind_state<T: VarValue>(child: impl UiNode, source: impl IntoVar<T>, state: impl IntoVar<T>) -> impl UiNode {
let source = source.into_var();
let state = state.into_var();
let mut _binding = VarHandle::dummy();
match_node(child, move |_, op| match op {
UiNodeOp::Init => {
validate_getter_var(&state);
let _ = state.set_from(&source);
_binding = source.bind(&state);
}
UiNodeOp::Deinit => {
_binding = VarHandle::dummy();
}
_ => {}
})
}
pub fn widget_state_is_state(
child: impl UiNode,
predicate: impl Fn(StateMapRef<WIDGET>) -> bool + Send + 'static,
deinit: impl Fn(StateMapRef<WIDGET>) -> bool + Send + 'static,
state: impl IntoVar<bool>,
) -> impl UiNode {
let state = state.into_var();
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
validate_getter_var(&state);
child.init();
let s = WIDGET.with_state(&predicate);
if s != state.get() {
let _ = state.set(s);
}
}
UiNodeOp::Deinit => {
child.deinit();
let s = WIDGET.with_state(&deinit);
if s != state.get() {
let _ = state.set(s);
}
}
UiNodeOp::Update { updates } => {
child.update(updates);
let s = WIDGET.with_state(&predicate);
if s != state.get() {
let _ = state.set(s);
}
}
_ => {}
})
}
pub fn widget_state_get_state<T: VarValue>(
child: impl UiNode,
get_new: impl Fn(StateMapRef<WIDGET>, &T) -> Option<T> + Send + 'static,
get_deinit: impl Fn(StateMapRef<WIDGET>, &T) -> Option<T> + Send + 'static,
state: impl IntoVar<T>,
) -> impl UiNode {
let state = state.into_var();
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
validate_getter_var(&state);
child.init();
let new = state.with(|s| WIDGET.with_state(|w| get_new(w, s)));
if let Some(new) = new {
let _ = state.set(new);
}
}
UiNodeOp::Deinit => {
child.deinit();
let new = state.with(|s| WIDGET.with_state(|w| get_deinit(w, s)));
if let Some(new) = new {
let _ = state.set(new);
}
}
UiNodeOp::Update { updates } => {
child.update(updates);
let new = state.with(|s| WIDGET.with_state(|w| get_new(w, s)));
if let Some(new) = new {
let _ = state.set(new);
}
}
_ => {}
})
}
pub fn fill_node(content: impl UiNode) -> impl UiNode {
let mut clip_bounds = PxSize::zero();
let mut clip_corners = PxCornerRadius::zero();
let mut offset = PxVector::zero();
let offset_key = FrameValueKey::new_unique();
let mut define_frame = false;
match_node(content, move |child, op| match op {
UiNodeOp::Init => {
WIDGET.sub_var_layout(&BORDER_ALIGN_VAR);
define_frame = false;
offset = PxVector::zero();
}
UiNodeOp::Measure { desired_size, .. } => {
let offsets = BORDER.inner_offsets();
let align = BORDER_ALIGN_VAR.get();
let our_offsets = offsets * align;
let size_offset = offsets - our_offsets;
let size_increase = PxSize::new(size_offset.horizontal(), size_offset.vertical());
*desired_size = LAYOUT.constraints().fill_size() + size_increase;
}
UiNodeOp::Layout { wl, final_size } => {
let (bounds, corners) = BORDER.fill_bounds();
let mut new_offset = bounds.origin.to_vector();
if clip_bounds != bounds.size || clip_corners != corners {
clip_bounds = bounds.size;
clip_corners = corners;
WIDGET.render();
}
let (_, branch_offset) = LAYOUT.with_constraints(PxConstraints2d::new_exact_size(bounds.size), || {
wl.with_branch_child(|wl| child.layout(wl))
});
new_offset += branch_offset;
if offset != new_offset {
offset = new_offset;
if define_frame {
WIDGET.render_update();
} else {
define_frame = true;
WIDGET.render();
}
}
*final_size = bounds.size;
}
UiNodeOp::Render { frame } => {
let mut render = |frame: &mut FrameBuilder| {
let bounds = PxRect::from_size(clip_bounds);
frame.push_clips(
|c| {
if clip_corners != PxCornerRadius::zero() {
c.push_clip_rounded_rect(bounds, clip_corners, false, false);
} else {
c.push_clip_rect(bounds, false, false);
}
if let Some(inline) = WIDGET.bounds().inline() {
for r in inline.negative_space().iter() {
c.push_clip_rect(*r, true, false);
}
}
},
|f| child.render(f),
);
};
if define_frame {
frame.push_reference_frame(offset_key.into(), offset_key.bind(offset.into(), false), true, false, |frame| {
render(frame);
});
} else {
render(frame);
}
}
UiNodeOp::RenderUpdate { update } => {
if define_frame {
update.with_transform(offset_key.update(offset.into(), false), false, |update| {
child.render_update(update);
});
} else {
child.render_update(update);
}
}
_ => {}
})
}
pub fn border_node(child: impl UiNode, border_offsets: impl IntoVar<SideOffsets>, border_visual: impl UiNode) -> impl UiNode {
let offsets = border_offsets.into_var();
let mut render_offsets = PxSideOffsets::zero();
let mut border_rect = PxRect::zero();
match_node_list(ui_vec![child, border_visual], move |children, op| match op {
UiNodeOp::Init => {
WIDGET.sub_var_layout(&offsets).sub_var_render(&BORDER_OVER_VAR);
}
UiNodeOp::Measure { wm, desired_size } => {
let offsets = offsets.layout();
*desired_size = BORDER.measure_border(offsets, || {
LAYOUT.with_sub_size(PxSize::new(offsets.horizontal(), offsets.vertical()), || {
children.with_node(0, |n| wm.measure_block(n))
})
});
}
UiNodeOp::Layout { wl, final_size } => {
let offsets = offsets.layout();
if render_offsets != offsets {
render_offsets = offsets;
WIDGET.render();
}
let parent_offsets = BORDER.inner_offsets();
let origin = PxPoint::new(parent_offsets.left, parent_offsets.top);
if border_rect.origin != origin {
border_rect.origin = origin;
WIDGET.render();
}
BORDER.layout_border(offsets, || {
wl.translate(PxVector::new(offsets.left, offsets.top));
let taken_size = PxSize::new(offsets.horizontal(), offsets.vertical());
border_rect.size = LAYOUT.with_sub_size(taken_size, || children.with_node(0, |n| n.layout(wl)));
LAYOUT.with_constraints(PxConstraints2d::new_exact_size(border_rect.size), || {
BORDER.with_border_layout(border_rect, offsets, || {
children.with_node(1, |n| n.layout(wl));
});
});
});
*final_size = border_rect.size;
}
UiNodeOp::Render { frame } => {
if BORDER_OVER_VAR.get() {
children.with_node(0, |c| c.render(frame));
BORDER.with_border_layout(border_rect, render_offsets, || {
children.with_node(1, |c| c.render(frame));
});
} else {
BORDER.with_border_layout(border_rect, render_offsets, || {
children.with_node(1, |c| c.render(frame));
});
children.with_node(0, |c| c.render(frame));
}
}
UiNodeOp::RenderUpdate { update } => {
children.with_node(0, |c| c.render_update(update));
BORDER.with_border_layout(border_rect, render_offsets, || {
children.with_node(1, |c| c.render_update(update));
})
}
_ => {}
})
}
pub fn with_context_local<T: Any + Send + Sync + 'static>(
child: impl UiNode,
context: &'static ContextLocal<T>,
value: impl Into<T>,
) -> impl UiNode {
let mut value = Some(Arc::new(value.into()));
match_node(child, move |child, op| {
context.with_context(&mut value, || child.op(op));
})
}
pub fn with_context_local_init<T: Any + Send + Sync + 'static>(
child: impl UiNode,
context: &'static ContextLocal<T>,
init_value: impl FnMut() -> T + Send + 'static,
) -> impl UiNode {
#[cfg(feature = "dyn_closure")]
let init_value: Box<dyn FnMut() -> T + Send> = Box::new(init_value);
with_context_local_init_impl(child.cfg_boxed(), context, init_value).cfg_boxed()
}
fn with_context_local_init_impl<T: Any + Send + Sync + 'static>(
child: impl UiNode,
context: &'static ContextLocal<T>,
mut init_value: impl FnMut() -> T + Send + 'static,
) -> impl UiNode {
let mut value = None;
match_node(child, move |child, op| {
let mut is_deinit = false;
match &op {
UiNodeOp::Init => {
value = Some(Arc::new(init_value()));
}
UiNodeOp::Deinit => {
is_deinit = true;
}
_ => {}
}
context.with_context(&mut value, || child.op(op));
if is_deinit {
value = None;
}
})
}
pub fn with_context_blend(mut ctx: LocalContext, over: bool, child: impl UiNode) -> impl UiNode {
match_widget(child, move |c, op| {
if let UiNodeOp::Init = op {
let init_app = LocalContext::current_app();
ctx.with_context_blend(over, || {
let ctx_app = LocalContext::current_app();
assert_eq!(init_app, ctx_app);
c.op(op)
});
} else {
ctx.with_context_blend(over, || c.op(op));
}
})
}
pub fn with_widget_state<U, I, T>(child: U, id: impl Into<StateId<T>>, default: I, value: impl IntoVar<T>) -> impl UiNode
where
U: UiNode,
I: Fn() -> T + Send + 'static,
T: StateValue + VarValue,
{
#[cfg(feature = "dyn_closure")]
let default: Box<dyn Fn() -> T + Send> = Box::new(default);
with_widget_state_impl(child.cfg_boxed(), id.into(), default, value.into_var()).cfg_boxed()
}
fn with_widget_state_impl<U, I, T>(child: U, id: impl Into<StateId<T>>, default: I, value: impl IntoVar<T>) -> impl UiNode
where
U: UiNode,
I: Fn() -> T + Send + 'static,
T: StateValue + VarValue,
{
let id = id.into();
let value = value.into_var();
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
child.init();
WIDGET.sub_var(&value);
WIDGET.set_state(id, value.get());
}
UiNodeOp::Deinit => {
child.deinit();
WIDGET.set_state(id, default());
}
UiNodeOp::Update { updates } => {
child.update(updates);
if let Some(v) = value.get_new() {
WIDGET.set_state(id, v);
}
}
_ => {}
})
}
pub fn with_widget_state_modify<U, S, V, I, M>(
child: U,
id: impl Into<StateId<S>>,
value: impl IntoVar<V>,
default: I,
modify: M,
) -> impl UiNode
where
U: UiNode,
S: StateValue,
V: VarValue,
I: Fn() -> S + Send + 'static,
M: FnMut(&mut S, &V) + Send + 'static,
{
#[cfg(feature = "dyn_closure")]
let default: Box<dyn Fn() -> S + Send> = Box::new(default);
#[cfg(feature = "dyn_closure")]
let modify: Box<dyn FnMut(&mut S, &V) + Send> = Box::new(modify);
with_widget_state_modify_impl(child.cfg_boxed(), id.into(), value.into_var(), default, modify)
}
fn with_widget_state_modify_impl<U, S, V, I, M>(
child: U,
id: impl Into<StateId<S>>,
value: impl IntoVar<V>,
default: I,
mut modify: M,
) -> impl UiNode
where
U: UiNode,
S: StateValue,
V: VarValue,
I: Fn() -> S + Send + 'static,
M: FnMut(&mut S, &V) + Send + 'static,
{
let id = id.into();
let value = value.into_var();
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
child.init();
WIDGET.sub_var(&value);
value.with(|v| {
WIDGET.with_state_mut(|mut s| {
modify(s.entry(id).or_insert_with(&default), v);
})
})
}
UiNodeOp::Deinit => {
child.deinit();
WIDGET.set_state(id, default());
}
UiNodeOp::Update { updates } => {
child.update(updates);
value.with_new(|v| {
WIDGET.with_state_mut(|mut s| {
modify(s.req_mut(id), v);
})
});
}
_ => {}
})
}
pub fn interactive_node(child: impl UiNode, interactive: impl IntoVar<bool>) -> impl UiNode {
let interactive = interactive.into_var();
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
WIDGET.sub_var_info(&interactive);
}
UiNodeOp::Info { info } => {
if interactive.get() {
child.info(info);
} else if let Some(id) = child.with_context(WidgetUpdateMode::Ignore, || WIDGET.id()) {
info.push_interactivity_filter(move |args| {
if args.info.id() == id {
Interactivity::BLOCKED
} else {
Interactivity::ENABLED
}
});
child.info(info);
} else {
let block_range = info.with_children_range(|info| child.info(info));
if !block_range.is_empty() {
let id = WIDGET.id();
info.push_interactivity_filter(move |args| {
if let Some(parent) = args.info.parent() {
if parent.id() == id {
for (i, item) in parent.children().enumerate() {
if item == args.info {
return if !block_range.contains(&i) {
Interactivity::ENABLED
} else {
Interactivity::BLOCKED
};
} else if i >= block_range.end {
break;
}
}
}
}
Interactivity::ENABLED
});
}
}
}
_ => {}
})
}
pub fn with_index_node(
child: impl UiNode,
panel_list_id: impl Into<StateId<PanelListRange>>,
mut update: impl FnMut(Option<usize>) + Send + 'static,
) -> impl UiNode {
let panel_list_id = panel_list_id.into();
let mut version = None;
match_node(child, move |_, op| match op {
UiNodeOp::Deinit => {
update(None);
version = None;
}
UiNodeOp::Update { .. } => {
let info = WIDGET.info();
if let Some(parent) = info.parent() {
if let Some(mut c) = PanelListRange::update(&parent, panel_list_id, &mut version) {
let id = info.id();
let p = c.position(|w| w.id() == id);
update(p);
}
}
}
_ => {}
})
}
pub fn with_rev_index_node(
child: impl UiNode,
panel_list_id: impl Into<StateId<PanelListRange>>,
mut update: impl FnMut(Option<usize>) + Send + 'static,
) -> impl UiNode {
let panel_list_id = panel_list_id.into();
let mut version = None;
match_node(child, move |_, op| match op {
UiNodeOp::Deinit => {
update(None);
version = None;
}
UiNodeOp::Update { .. } => {
let info = WIDGET.info();
if let Some(parent) = info.parent() {
if let Some(c) = PanelListRange::update(&parent, panel_list_id, &mut version) {
let id = info.id();
let p = c.rev().position(|w| w.id() == id);
update(p);
}
}
}
_ => {}
})
}
pub fn with_index_len_node(
child: impl UiNode,
panel_list_id: impl Into<StateId<PanelListRange>>,
mut update: impl FnMut(Option<(usize, usize)>) + Send + 'static,
) -> impl UiNode {
let panel_list_id = panel_list_id.into();
let mut version = None;
match_node(child, move |_, op| match op {
UiNodeOp::Deinit => {
update(None);
version = None;
}
UiNodeOp::Update { .. } => {
let info = WIDGET.info();
if let Some(parent) = info.parent() {
if let Some(mut iter) = PanelListRange::update(&parent, panel_list_id, &mut version) {
let id = info.id();
let mut p = 0;
let mut count = 0;
for c in &mut iter {
if c.id() == id {
p = count;
count += 1 + iter.count();
break;
} else {
count += 1;
}
}
update(Some((p, count)));
}
}
}
_ => {}
})
}
pub fn presenter<D: VarValue>(data: impl IntoVar<D>, wgt_fn: impl IntoVar<WidgetFn<D>>) -> impl UiNode {
let data = data.into_var();
let wgt_fn = wgt_fn.into_var();
match_node(NilUiNode.boxed(), move |c, op| match op {
UiNodeOp::Init => {
WIDGET.sub_var(&data).sub_var(&wgt_fn);
*c.child() = wgt_fn.get()(data.get());
}
UiNodeOp::Deinit => {
c.deinit();
*c.child() = NilUiNode.boxed();
}
UiNodeOp::Update { .. } => {
if data.is_new() || wgt_fn.is_new() {
c.child().deinit();
*c.child() = wgt_fn.get()(data.get());
c.child().init();
c.delegated();
WIDGET.update_info().layout().render();
}
}
_ => {}
})
}
pub fn presenter_opt<D: VarValue>(data: impl IntoVar<Option<D>>, wgt_fn: impl IntoVar<WidgetFn<D>>) -> impl UiNode {
let data = data.into_var();
let wgt_fn = wgt_fn.into_var();
match_node(NilUiNode.boxed(), move |c, op| match op {
UiNodeOp::Init => {
WIDGET.sub_var(&data).sub_var(&wgt_fn);
if let Some(data) = data.get() {
*c.child() = wgt_fn.get()(data);
}
}
UiNodeOp::Deinit => {
c.deinit();
*c.child() = NilUiNode.boxed();
}
UiNodeOp::Update { .. } => {
if data.is_new() || wgt_fn.is_new() {
if let Some(data) = data.get() {
c.child().deinit();
*c.child() = wgt_fn.get()(data);
c.child().init();
c.delegated();
WIDGET.update_info().layout().render();
} else if c.child().actual_type_id() != TypeId::of::<NilUiNode>() {
c.child().deinit();
*c.child() = NilUiNode.boxed();
c.delegated();
WIDGET.update_info().layout().render();
}
}
}
_ => {}
})
}
pub fn list_presenter<D: VarValue>(list: impl IntoVar<ObservableVec<D>>, item_fn: impl IntoVar<WidgetFn<D>>) -> impl UiNodeList {
ListPresenter {
list: list.into_var(),
item_fn: item_fn.into_var(),
view: vec![],
_e: std::marker::PhantomData,
}
}
struct ListPresenter<D: VarValue, L: Var<ObservableVec<D>>, E: Var<WidgetFn<D>>> {
list: L,
item_fn: E,
view: Vec<BoxedUiNode>,
_e: std::marker::PhantomData<D>,
}
impl<D, L, E> UiNodeList for ListPresenter<D, L, E>
where
D: VarValue,
L: Var<ObservableVec<D>>,
E: Var<WidgetFn<D>>,
{
fn with_node<R, F>(&mut self, index: usize, f: F) -> R
where
F: FnOnce(&mut BoxedUiNode) -> R,
{
self.view.with_node(index, f)
}
fn for_each<F>(&mut self, f: F)
where
F: FnMut(usize, &mut BoxedUiNode),
{
self.view.for_each(f)
}
fn par_each<F>(&mut self, f: F)
where
F: Fn(usize, &mut BoxedUiNode) + Send + Sync,
{
self.view.par_each(f)
}
fn par_fold_reduce<T, I, F, R>(&mut self, identity: I, fold: F, reduce: R) -> T
where
T: Send + 'static,
I: Fn() -> T + Send + Sync,
F: Fn(T, usize, &mut BoxedUiNode) -> T + Send + Sync,
R: Fn(T, T) -> T + Send + Sync,
{
self.view.par_fold_reduce(identity, fold, reduce)
}
fn len(&self) -> usize {
self.view.len()
}
fn boxed(self) -> BoxedUiNodeList {
Box::new(self)
}
fn drain_into(&mut self, vec: &mut Vec<BoxedUiNode>) {
self.view.drain_into(vec);
tracing::warn!("drained `list_presenter`, now out of sync with data");
}
fn init_all(&mut self) {
debug_assert!(self.view.is_empty());
self.view.clear();
WIDGET.sub_var(&self.list).sub_var(&self.item_fn);
let e_fn = self.item_fn.get();
self.list.with(|l| {
for el in l.iter() {
let child = e_fn(el.clone());
self.view.push(child);
}
});
self.view.init_all();
}
fn deinit_all(&mut self) {
self.view.deinit_all();
self.view.clear();
}
fn update_all(&mut self, updates: &WidgetUpdates, observer: &mut dyn UiNodeListObserver) {
let mut need_reset = self.item_fn.is_new();
let is_new = self
.list
.with_new(|l| {
need_reset |= l.changes().is_empty() || l.changes() == [VecChange::Clear];
if need_reset {
return;
}
self.view.update_all(updates, observer);
let e_fn = self.item_fn.get();
for change in l.changes() {
match change {
VecChange::Insert { index, count } => {
for i in *index..(*index + count) {
let mut el = e_fn(l[i].clone());
el.init();
self.view.insert(i, el);
observer.inserted(i);
}
}
VecChange::Remove { index, count } => {
let mut count = *count;
let index = *index;
while count > 0 {
count -= 1;
let mut el = self.view.remove(index);
el.deinit();
observer.removed(index);
}
}
VecChange::Move { from_index, to_index } => {
let el = self.view.remove(*from_index);
self.view.insert(*to_index, el);
observer.moved(*from_index, *to_index);
}
VecChange::Clear => unreachable!(),
}
}
})
.is_some();
if !need_reset && !is_new && self.list.with(|l| l.len() != self.view.len()) {
need_reset = true;
}
if need_reset {
self.view.deinit_all();
self.view.clear();
let e_fn = self.item_fn.get();
self.list.with(|l| {
for el in l.iter() {
let child = e_fn(el.clone());
self.view.push(child);
}
});
self.view.init_all();
} else if !is_new {
self.view.update_all(updates, observer);
}
}
}
#[doc(inline)]
pub use crate::command_property;
#[doc(inline)]
pub use crate::event_property;
use crate::WidgetFn;