use std::{
sync::{
Arc, Weak,
atomic::{AtomicU32, AtomicUsize, Ordering},
},
time::Duration,
};
use crate::{
AnyVar, VARS, Var,
animation::{
AnimationHandle, Transitionable,
easing::{EasingStep, EasingTime},
},
contextual_var::{ContextInitFnImpl, any_contextual_var_impl},
shared_var::MutexHooks,
};
use super::*;
#[macro_export]
macro_rules! when_var {
($($tt:tt)*) => {
$crate::__when_var! {
$crate
$($tt)*
}
}
}
use zng_clone_move::clmv;
#[doc(hidden)]
pub use zng_var_proc_macros::when_var as __when_var;
#[derive(Clone)]
pub struct AnyWhenVarBuilder {
conditions: Vec<(Var<bool>, AnyVar)>,
default: AnyVar,
}
impl AnyWhenVarBuilder {
pub fn new(default: AnyVar) -> Self {
AnyWhenVarBuilder {
conditions: Vec::with_capacity(2),
default,
}
}
pub fn push(&mut self, condition: Var<bool>, value: AnyVar) -> &mut Self {
self.conditions.push((condition, value));
self
}
pub fn replace_extend(&mut self, other: &Self) {
self.default = other.default.clone();
self.extend(other);
}
pub fn extend(&mut self, other: &Self) {
for (c, v) in other.conditions.iter() {
self.conditions.push((c.clone(), v.clone()));
}
}
pub fn build(self, value_type: TypeId) -> AnyVar {
when_var(self, value_type)
}
pub fn into_typed<O: VarValue>(self) -> WhenVarBuilder<O> {
WhenVarBuilder {
builder: self,
_t: PhantomData,
}
}
pub fn try_from_built(var: &AnyVar) -> Option<Self> {
match &var.0 {
DynAnyVar::When(built) => Some(Self {
conditions: built.0.conditions.to_vec(),
default: built.0.default.clone(),
}),
DynAnyVar::Contextual(built) => {
let init = built.0.init.lock();
let init: &dyn Any = &**init;
init.downcast_ref::<Self>().cloned()
}
_ => None,
}
}
fn is_contextual(&self) -> bool {
self.default.capabilities().is_contextual()
|| self
.conditions
.iter()
.any(|(c, v)| c.capabilities().is_contextual() || v.capabilities().is_contextual())
}
fn current_context(&self) -> Self {
AnyWhenVarBuilder {
conditions: self
.conditions
.iter()
.map(|(c, v)| (c.current_context(), v.current_context()))
.collect(),
default: self.default.current_context(),
}
}
}
impl AnyWhenVarBuilder {
pub fn condition_count(&self) -> usize {
self.conditions.len()
}
}
#[derive(Clone)]
pub struct WhenVarBuilder<O: VarValue> {
builder: AnyWhenVarBuilder,
_t: PhantomData<O>,
}
impl<O: VarValue> WhenVarBuilder<O> {
pub fn new(default: impl IntoVar<O>) -> Self {
Self {
builder: AnyWhenVarBuilder::new(default.into_var().into()),
_t: PhantomData,
}
}
pub fn push(&mut self, condition: impl IntoVar<bool>, value: impl IntoVar<O>) -> &mut Self {
self.builder.conditions.push((condition.into_var(), value.into_var().into()));
self
}
pub fn build(self) -> Var<O> {
Var::new_any(self.builder.build(TypeId::of::<O>()))
}
pub fn as_any(&mut self) -> &mut AnyWhenVarBuilder {
&mut self.builder
}
pub fn try_from_built(var: &Var<O>) -> Option<Self> {
let builder = AnyWhenVarBuilder::try_from_built(var)?;
Some(Self { builder, _t: PhantomData })
}
}
fn when_var(builder: AnyWhenVarBuilder, value_type: TypeId) -> AnyVar {
if builder.is_contextual() {
return any_contextual_var_impl(smallbox!(builder), value_type);
}
when_var_tail(builder)
}
impl ContextInitFnImpl for AnyWhenVarBuilder {
fn init(&mut self) -> AnyVar {
let builder = self.current_context();
when_var_tail(builder)
}
}
fn when_var_tail(mut builder: AnyWhenVarBuilder) -> AnyVar {
builder.conditions.retain(|(c, _)| !c.capabilities().is_const() || c.get());
if builder.conditions.is_empty() {
return builder.default;
}
let all_equal = builder.default.capabilities().is_const()
&& builder.default.with(|default| {
builder
.conditions
.iter()
.all(|(_, v)| v.capabilities().is_const() && v.with(|v| v == default))
});
if all_equal {
return builder.default;
}
AnyVar(DynAnyVar::When(when_var_tail_impl(builder)))
}
fn when_var_tail_impl(builder: AnyWhenVarBuilder) -> WhenVar {
let data = Arc::new(WhenVarData {
active_condition: AtomicUsize::new(builder.conditions.iter().position(|(c, _)| c.get()).unwrap_or(usize::MAX)),
conditions: builder.conditions.into_boxed_slice(),
default: builder.default,
hooks: MutexHooks::default(),
last_active_change: AtomicU32::new(VarUpdateId::never().0),
});
for (i, (c, v)) in data.conditions.iter().enumerate() {
let weak = Arc::downgrade(&data);
c.hook(clmv!(weak, |args| {
if let Some(data) = weak.upgrade() {
let mut changed = false;
let mut active = data.active_condition.load(Ordering::Relaxed);
if active == i {
if !*args.value() {
active = data.conditions.iter().position(|(c, _)| c.get()).unwrap_or(usize::MAX);
changed = true;
}
} else if active > i && *args.value() {
changed = true;
active = i;
}
if changed {
data.active_condition.store(active, Ordering::Relaxed);
data.last_active_change.store(VARS.update_id().0, Ordering::Relaxed);
let active = if active < data.conditions.len() {
&data.conditions[active].1
} else {
&data.default
};
active.0.with(&mut |v| {
data.hooks.notify(&AnyVarHookArgs {
var_instance_tag: VarInstanceTag(Arc::as_ptr(&data) as _),
value: v,
update: args.update,
tags: args.tags,
});
});
}
true
} else {
false
}
}))
.perm();
v.hook(move |args| {
if let Some(data) = weak.upgrade() {
if data.active_condition.load(Ordering::Relaxed) == i {
data.hooks.notify(args);
}
true
} else {
false
}
})
.perm();
}
let weak = Arc::downgrade(&data);
data.default
.hook(move |args| {
if let Some(data) = weak.upgrade() {
if data.active_condition.load(Ordering::Relaxed) >= data.conditions.len() {
data.hooks.notify(args);
}
true
} else {
false
}
})
.perm();
WhenVar(data)
}
struct WhenVarData {
conditions: Box<[(Var<bool>, AnyVar)]>,
default: AnyVar,
active_condition: AtomicUsize,
hooks: MutexHooks,
last_active_change: AtomicU32,
}
pub(crate) struct WhenVar(Arc<WhenVarData>);
impl WhenVar {
fn active(&self) -> &AnyVar {
let i = self.0.active_condition.load(Ordering::Relaxed);
if i < self.0.conditions.len() {
&self.0.conditions[i].1
} else {
&self.0.default
}
}
}
impl fmt::Debug for WhenVar {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut b = f.debug_struct("MergeVar");
b.field("var_instance_tag()", &self.var_instance_tag());
b.field("inputs", &self.0.conditions);
b.field("default", &self.0.default);
let n = self.0.active_condition.load(Ordering::Relaxed);
b.field("active_condition", if n < self.0.conditions.len() { &n } else { &None::<usize> });
b.field(
"last_active_change",
&VarUpdateId(self.0.last_active_change.load(Ordering::Relaxed)),
);
b.field("hooks", &self.0.hooks);
b.finish()
}
}
impl VarImpl for WhenVar {
fn clone_dyn(&self) -> DynAnyVar {
DynAnyVar::When(Self(self.0.clone()))
}
fn value_type(&self) -> TypeId {
self.0.default.0.value_type()
}
#[cfg(feature = "type_names")]
fn value_type_name(&self) -> &'static str {
self.0.default.0.value_type_name()
}
fn strong_count(&self) -> usize {
Arc::strong_count(&self.0)
}
fn var_eq(&self, other: &DynAnyVar) -> bool {
match other {
DynAnyVar::When(o) => Arc::ptr_eq(&self.0, &o.0),
_ => false,
}
}
fn var_instance_tag(&self) -> VarInstanceTag {
VarInstanceTag(Arc::as_ptr(&self.0) as _)
}
fn downgrade(&self) -> DynWeakAnyVar {
DynWeakAnyVar::When(WeakWhenVar(Arc::downgrade(&self.0)))
}
fn capabilities(&self) -> VarCapability {
fn cap_changes(caps: VarCapability) -> VarCapability {
let mut out = VarCapability::NEW;
if caps.contains(VarCapability::MODIFY) || caps.contains(VarCapability::MODIFY_CHANGES) {
out |= VarCapability::MODIFY_CHANGES;
}
out
}
self.active().0.capabilities()
| cap_changes(self.0.default.capabilities())
| self
.0
.conditions
.iter()
.map(|(_, v)| cap_changes(v.capabilities()))
.fold(VarCapability::empty(), |a, b| a | b)
}
fn with(&self, visitor: &mut dyn FnMut(&dyn AnyVarValue)) {
self.active().0.with(visitor)
}
fn get(&self) -> BoxAnyVarValue {
self.active().0.get()
}
fn set(&self, new_value: BoxAnyVarValue) -> bool {
self.active().0.set(new_value)
}
fn update(&self) -> bool {
self.active().0.update()
}
fn modify(&self, modify: SmallBox<dyn FnMut(&mut AnyVarModify) + Send + 'static, smallbox::space::S4>) -> bool {
self.active().0.modify(modify)
}
fn hook(&self, on_new: SmallBox<dyn FnMut(&AnyVarHookArgs) -> bool + Send + 'static, smallbox::space::S4>) -> VarHandle {
self.0.hooks.push(on_new)
}
fn last_update(&self) -> VarUpdateId {
VarUpdateId(self.0.last_active_change.load(Ordering::Relaxed)).max(self.active().0.last_update())
}
fn modify_info(&self) -> ModifyInfo {
self.active().0.modify_info()
}
fn modify_importance(&self) -> usize {
self.active().0.modify_importance()
}
fn is_animating(&self) -> bool {
self.active().0.is_animating()
}
fn hook_animation_stop(&self, handler: AnimationStopFn) -> VarHandle {
self.active().0.hook_animation_stop(handler)
}
fn current_context(&self) -> DynAnyVar {
self.clone_dyn()
}
}
pub(crate) struct WeakWhenVar(Weak<WhenVarData>);
impl fmt::Debug for WeakWhenVar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("WeakWhenVar").field(&self.0.as_ptr()).finish()
}
}
impl WeakVarImpl for WeakWhenVar {
fn clone_dyn(&self) -> DynWeakAnyVar {
DynWeakAnyVar::When(Self(self.0.clone()))
}
fn strong_count(&self) -> usize {
self.0.strong_count()
}
fn upgrade(&self) -> Option<DynAnyVar> {
Some(DynAnyVar::When(WhenVar(self.0.upgrade()?)))
}
fn var_eq(&self, other: &DynWeakAnyVar) -> bool {
match other {
DynWeakAnyVar::When(o) => self.0.ptr_eq(&o.0),
_ => false,
}
}
}
fn when_var_easing<O: VarValue + Transitionable>(builder: AnimatingWhenVarBuilder<O>) -> Var<O> {
if builder.builder.is_contextual() {
let any = any_contextual_var_impl(smallbox!(builder), TypeId::of::<O>());
return Var::new_any(any);
}
when_var_easing_tail(builder)
}
struct AnimatingWhenVarBuilder<O: VarValue + Transitionable> {
builder: AnyWhenVarBuilder,
condition_easing: Vec<Option<EasingData>>,
default_easing: EasingData,
_t: PhantomData<fn() -> O>,
}
impl<O: VarValue + Transitionable> ContextInitFnImpl for AnimatingWhenVarBuilder<O> {
fn init(&mut self) -> AnyVar {
let builder = AnimatingWhenVarBuilder {
builder: self.builder.current_context(),
condition_easing: self.condition_easing.clone(),
default_easing: self.default_easing.clone(),
_t: self._t,
};
when_var_easing_tail(builder).any
}
}
fn when_var_easing_tail<O: VarValue + Transitionable>(builder: AnimatingWhenVarBuilder<O>) -> Var<O> {
let source = when_var_tail_impl(builder.builder);
let weak_source = Arc::downgrade(&source.0);
let output = var(source.get().downcast::<O>().unwrap());
let weak_output = output.downgrade();
let condition_easing = builder.condition_easing.into_boxed_slice();
let default_easing = builder.default_easing;
let mut _animation_handle = AnimationHandle::dummy();
source
.hook(smallbox!(move |args: &AnyVarHookArgs| {
if let Some(output) = weak_output.upgrade() {
let source = weak_source.upgrade().unwrap();
for ((c, _), easing) in source.conditions.iter().zip(&condition_easing) {
if let Some((duration, func)) = easing
&& c.get()
{
_animation_handle = output.ease(args.downcast_value::<O>().unwrap().clone(), *duration, clmv!(func, |t| func(t)));
return true;
}
}
let (duration, func) = &default_easing;
_animation_handle = output.ease(args.downcast_value::<O>().unwrap().clone(), *duration, clmv!(func, |t| func(t)));
true
} else {
false
}
}))
.perm();
output.hold(source).perm();
output
}
type EasingData = (Duration, Arc<dyn Fn(EasingTime) -> EasingStep + Send + Sync>);
impl<O: VarValue + Transitionable> WhenVarBuilder<O> {
pub fn build_easing(self, condition_easing: Vec<Option<EasingData>>, default_easing: EasingData) -> Var<O> {
when_var_easing(AnimatingWhenVarBuilder {
builder: self.builder,
condition_easing,
default_easing,
_t: PhantomData,
})
}
}