zng_var/var_impl/
when_var.rs

1//! Conditional var proxy.
2
3use std::{
4    sync::{
5        Arc, Weak,
6        atomic::{AtomicU32, AtomicUsize, Ordering},
7    },
8    time::Duration,
9};
10
11use crate::{
12    AnyVar, VARS, Var,
13    animation::{
14        AnimationHandle, Transitionable,
15        easing::{EasingStep, EasingTime},
16    },
17    contextual_var::{ContextInitFnImpl, any_contextual_var_impl},
18    shared_var::MutexHooks,
19};
20
21use super::*;
22
23///<span data-del-macro-root></span> Initializes a new conditional var.
24///
25/// A condition var updates when the first `true` condition changes or the mapped var for the current condition changes.
26///
27/// # Syntax
28///
29/// The macro expects a list of `condition-var => condition-value-var`, the list is separated by comma.
30/// The last condition must be the `_` token that maps to the value for when none of the conditions are `true`.
31///
32/// The `condition-var` must be an expression that evaluates to a `Var<bool>` type. The `condition-value-var` must
33/// by any type that implements `IntoVar`. All condition values must be of the same [`VarValue`] type.
34///
35/// # Examples
36///
37/// ```
38/// # use zng_var::*;
39/// # use zng_txt::ToTxt;
40/// # macro_rules! Text { ($($tt:tt)*) => { () } }
41/// let condition = var(true);
42/// let when_false = var("condition: false".to_txt());
43///
44/// let t = Text!(when_var! {
45///     condition.clone() => "condition: true".to_txt(),
46///     _ => when_false.clone(),
47/// });
48/// ```
49///
50/// In the example if `condition` or `when_false` are modified the text updates.
51///
52/// # `cfg`
53///
54/// Every condition can be annotated with attributes, including `#[cfg(..)]`.
55///
56/// ```
57/// # use zng_var::*;
58/// # use zng_txt::*;
59/// # macro_rules! Text { ($($tt:tt)*) => { () } }
60/// # let condition0 = var(true);
61/// # let condition1 = var(true);
62/// let t = Text!(when_var! {
63///     #[cfg(some_flag)]
64///     condition0 => "is condition 0".to_txt(),
65///     #[cfg(not(some_flag))]
66///     condition1 => "is condition 1".to_txt(),
67///     _ => "is default".to_txt(),
68/// });
69/// ```
70///
71/// In the example above only one of the conditions will be compiled, the generated variable is the same
72/// type as if you had written a single condition.
73///
74/// # Capabilities
75///
76/// The when var is contextualized when needed, meaning if any input is [`CONTEXT`] at the moment the var is created it
77/// is also contextual. The full output type of this macro is a `Var<T>`.
78///
79/// [`CONTEXT`]: crate::VarCapability::CONTEXT
80#[macro_export]
81macro_rules! when_var {
82    ($($tt:tt)*) => {
83        $crate::__when_var! {
84            $crate
85            $($tt)*
86        }
87    }
88}
89
90use zng_clone_move::clmv;
91#[doc(hidden)]
92pub use zng_var_proc_macros::when_var as __when_var;
93
94/// Type erased [`when_var!`] manual builder.
95///
96/// See [`WhenVarBuilder`] for more details.
97#[derive(Clone)]
98pub struct AnyWhenVarBuilder {
99    conditions: Vec<(Var<bool>, AnyVar)>,
100    default: AnyVar,
101}
102impl AnyWhenVarBuilder {
103    /// New with value variable used when no other conditions are `true`.
104    pub fn new(default: AnyVar) -> Self {
105        AnyWhenVarBuilder {
106            conditions: Vec::with_capacity(2),
107            default,
108        }
109    }
110
111    /// Push a conditional value.
112    ///
113    /// When the `condition` is `true` and all previous pushed conditions
114    /// are `false` the when variable represents the `value` variable.
115    pub fn push(&mut self, condition: Var<bool>, value: AnyVar) -> &mut Self {
116        self.conditions.push((condition, value));
117        self
118    }
119
120    /// Replace the default value if `other` has default and extend the conditions with clones of `other`.
121    pub fn replace_extend(&mut self, other: &Self) {
122        self.default = other.default.clone();
123        self.extend(other);
124    }
125
126    /// Extend the conditions with clones of `other`.
127    pub fn extend(&mut self, other: &Self) {
128        for (c, v) in other.conditions.iter() {
129            self.conditions.push((c.clone(), v.clone()));
130        }
131    }
132
133    /// Build the when var.
134    ///
135    /// The `value_type` is the when var output value type.
136    pub fn build(self, value_type: TypeId) -> AnyVar {
137        when_var(self, value_type)
138    }
139
140    /// Convert to typed builder.
141    ///
142    /// Note that the type is not checked.
143    pub fn into_typed<O: VarValue>(self) -> WhenVarBuilder<O> {
144        WhenVarBuilder {
145            builder: self,
146            _t: PhantomData,
147        }
148    }
149
150    /// If the `var` was built by [`build`] clones the internal conditions, values and default variables into a new builder.
151    ///
152    /// [`build`]: Self::build
153    pub fn try_from_built(var: &AnyVar) -> Option<Self> {
154        match &var.0 {
155            DynAnyVar::When(built) => Some(Self {
156                conditions: built.0.conditions.to_vec(),
157                default: built.0.default.clone(),
158            }),
159            DynAnyVar::Contextual(built) => {
160                let init = built.0.init.lock();
161                let init: &dyn Any = &**init;
162                init.downcast_ref::<Self>().cloned()
163            }
164            _ => None,
165        }
166    }
167
168    fn is_contextual(&self) -> bool {
169        self.default.capabilities().is_contextual()
170            || self
171                .conditions
172                .iter()
173                .any(|(c, v)| c.capabilities().is_contextual() || v.capabilities().is_contextual())
174    }
175
176    fn current_context(&self) -> Self {
177        AnyWhenVarBuilder {
178            conditions: self
179                .conditions
180                .iter()
181                .map(|(c, v)| (c.current_context(), v.current_context()))
182                .collect(),
183            default: self.default.current_context(),
184        }
185    }
186}
187impl AnyWhenVarBuilder {
188    /// Returns the number of conditions set.
189    pub fn condition_count(&self) -> usize {
190        self.conditions.len()
191    }
192}
193
194/// Manual [`when_var!`] builder.
195#[derive(Clone)]
196pub struct WhenVarBuilder<O: VarValue> {
197    builder: AnyWhenVarBuilder,
198    _t: PhantomData<O>,
199}
200impl<O: VarValue> WhenVarBuilder<O> {
201    /// New with value variable used when no other conditions are `true`.
202    pub fn new(default: impl IntoVar<O>) -> Self {
203        Self {
204            builder: AnyWhenVarBuilder::new(default.into_var().into()),
205            _t: PhantomData,
206        }
207    }
208
209    /// Push a conditional value.
210    ///
211    /// When the `condition` is `true` and all previous pushed conditions
212    /// are `false` the when variable represents the `value` variable.
213    pub fn push(&mut self, condition: impl IntoVar<bool>, value: impl IntoVar<O>) -> &mut Self {
214        self.builder.conditions.push((condition.into_var(), value.into_var().into()));
215        self
216    }
217
218    /// Build the when var.
219    pub fn build(self) -> Var<O> {
220        Var::new_any(self.builder.build(TypeId::of::<O>()))
221    }
222
223    /// Reference the type erased when builder.
224    pub fn as_any(&mut self) -> &mut AnyWhenVarBuilder {
225        &mut self.builder
226    }
227
228    /// If the `var` was built by [`build`] clones the internal conditions, values and default variables into a new builder.
229    ///
230    /// [`build`]: Self::build
231    pub fn try_from_built(var: &Var<O>) -> Option<Self> {
232        // this is used by #[easing(_)] in PropertyAttribute to modify widget properties
233
234        let builder = AnyWhenVarBuilder::try_from_built(var)?;
235        Some(Self { builder, _t: PhantomData })
236    }
237}
238
239fn when_var(builder: AnyWhenVarBuilder, value_type: TypeId) -> AnyVar {
240    if builder.is_contextual() {
241        return any_contextual_var_impl(smallbox!(builder), value_type);
242    }
243    when_var_tail(builder)
244}
245impl ContextInitFnImpl for AnyWhenVarBuilder {
246    fn init(&mut self) -> AnyVar {
247        let builder = self.current_context();
248        when_var_tail(builder)
249    }
250}
251fn when_var_tail(mut builder: AnyWhenVarBuilder) -> AnyVar {
252    builder.conditions.retain(|(c, _)| !c.capabilities().is_const() || c.get());
253    if builder.conditions.is_empty() {
254        return builder.default;
255    }
256
257    let all_equal = builder.default.capabilities().is_const()
258        && builder.default.with(|default| {
259            builder
260                .conditions
261                .iter()
262                .all(|(_, v)| v.capabilities().is_const() && v.with(|v| v == default))
263        });
264    if all_equal {
265        // this happens usually due to a multi input property where only one input
266        // really changes over when blocks.
267        return builder.default;
268    }
269
270    AnyVar(DynAnyVar::When(when_var_tail_impl(builder)))
271}
272fn when_var_tail_impl(builder: AnyWhenVarBuilder) -> WhenVar {
273    let data = Arc::new(WhenVarData {
274        active_condition: AtomicUsize::new(builder.conditions.iter().position(|(c, _)| c.get()).unwrap_or(usize::MAX)),
275        conditions: builder.conditions.into_boxed_slice(),
276        default: builder.default,
277        hooks: MutexHooks::default(),
278        last_active_change: AtomicU32::new(VarUpdateId::never().0),
279    });
280
281    for (i, (c, v)) in data.conditions.iter().enumerate() {
282        let weak = Arc::downgrade(&data);
283        c.hook(clmv!(weak, |args| {
284            if let Some(data) = weak.upgrade() {
285                let mut changed = false;
286                let mut active = data.active_condition.load(Ordering::Relaxed);
287                if active == i {
288                    if !*args.value() {
289                        // deactivated active
290                        active = data.conditions.iter().position(|(c, _)| c.get()).unwrap_or(usize::MAX);
291                        changed = true;
292                    }
293                } else if active > i && *args.value() {
294                    // activated higher priority
295                    changed = true;
296                    active = i;
297                }
298
299                if changed {
300                    data.active_condition.store(active, Ordering::Relaxed);
301                    data.last_active_change.store(VARS.update_id().0, Ordering::Relaxed);
302
303                    let active = if active < data.conditions.len() {
304                        &data.conditions[active].1
305                    } else {
306                        &data.default
307                    };
308
309                    active.0.with(&mut |v| {
310                        data.hooks.notify(&AnyVarHookArgs {
311                            var_instance_tag: VarInstanceTag(Arc::as_ptr(&data) as _),
312                            value: v,
313                            update: args.update,
314                            tags: args.tags,
315                        });
316                    });
317                }
318
319                true
320            } else {
321                false
322            }
323        }))
324        .perm();
325
326        v.hook(move |args| {
327            if let Some(data) = weak.upgrade() {
328                if data.active_condition.load(Ordering::Relaxed) == i {
329                    data.hooks.notify(args);
330                }
331                true
332            } else {
333                false
334            }
335        })
336        .perm();
337    }
338    let weak = Arc::downgrade(&data);
339    data.default
340        .hook(move |args| {
341            if let Some(data) = weak.upgrade() {
342                if data.active_condition.load(Ordering::Relaxed) >= data.conditions.len() {
343                    data.hooks.notify(args);
344                }
345                true
346            } else {
347                false
348            }
349        })
350        .perm();
351
352    WhenVar(data)
353}
354
355struct WhenVarData {
356    conditions: Box<[(Var<bool>, AnyVar)]>,
357    default: AnyVar,
358    active_condition: AtomicUsize,
359    hooks: MutexHooks,
360    // Atomic<VarUpdateId>
361    last_active_change: AtomicU32,
362}
363pub(crate) struct WhenVar(Arc<WhenVarData>);
364impl WhenVar {
365    fn active(&self) -> &AnyVar {
366        let i = self.0.active_condition.load(Ordering::Relaxed);
367        if i < self.0.conditions.len() {
368            &self.0.conditions[i].1
369        } else {
370            &self.0.default
371        }
372    }
373}
374impl fmt::Debug for WhenVar {
375    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376        let mut b = f.debug_struct("MergeVar");
377        b.field("var_instance_tag()", &self.var_instance_tag());
378        b.field("inputs", &self.0.conditions);
379        b.field("default", &self.0.default);
380        let n = self.0.active_condition.load(Ordering::Relaxed);
381        b.field("active_condition", if n < self.0.conditions.len() { &n } else { &None::<usize> });
382        b.field(
383            "last_active_change",
384            &VarUpdateId(self.0.last_active_change.load(Ordering::Relaxed)),
385        );
386        b.field("hooks", &self.0.hooks);
387        b.finish()
388    }
389}
390impl VarImpl for WhenVar {
391    fn clone_dyn(&self) -> DynAnyVar {
392        DynAnyVar::When(Self(self.0.clone()))
393    }
394
395    fn value_type(&self) -> TypeId {
396        self.0.default.0.value_type()
397    }
398
399    #[cfg(feature = "type_names")]
400    fn value_type_name(&self) -> &'static str {
401        self.0.default.0.value_type_name()
402    }
403
404    fn strong_count(&self) -> usize {
405        Arc::strong_count(&self.0)
406    }
407
408    fn var_eq(&self, other: &DynAnyVar) -> bool {
409        match other {
410            DynAnyVar::When(o) => Arc::ptr_eq(&self.0, &o.0),
411            _ => false,
412        }
413    }
414
415    fn var_instance_tag(&self) -> VarInstanceTag {
416        VarInstanceTag(Arc::as_ptr(&self.0) as _)
417    }
418
419    fn downgrade(&self) -> DynWeakAnyVar {
420        DynWeakAnyVar::When(WeakWhenVar(Arc::downgrade(&self.0)))
421    }
422
423    fn capabilities(&self) -> VarCapability {
424        fn cap_changes(caps: VarCapability) -> VarCapability {
425            let mut out = VarCapability::NEW;
426            if caps.contains(VarCapability::MODIFY) || caps.contains(VarCapability::MODIFY_CHANGES) {
427                out |= VarCapability::MODIFY_CHANGES;
428            }
429            // this is never true, already contextualized
430            // if caps.contains(VarCapability::CONTEXT) || caps.contains(VarCapability::CONTEXT_CHANGES) {
431            //     out |= VarCapability::CONTEXT_CHANGES;
432            // }
433            out
434        }
435        self.active().0.capabilities()
436            | cap_changes(self.0.default.capabilities())
437            | self
438                .0
439                .conditions
440                .iter()
441                .map(|(_, v)| cap_changes(v.capabilities()))
442                .fold(VarCapability::empty(), |a, b| a | b)
443    }
444
445    fn with(&self, visitor: &mut dyn FnMut(&dyn AnyVarValue)) {
446        self.active().0.with(visitor)
447    }
448
449    fn get(&self) -> BoxAnyVarValue {
450        self.active().0.get()
451    }
452
453    fn set(&self, new_value: BoxAnyVarValue) -> bool {
454        self.active().0.set(new_value)
455    }
456
457    fn update(&self) -> bool {
458        self.active().0.update()
459    }
460
461    fn modify(&self, modify: SmallBox<dyn FnMut(&mut AnyVarModify) + Send + 'static, smallbox::space::S4>) -> bool {
462        self.active().0.modify(modify)
463    }
464
465    fn hook(&self, on_new: SmallBox<dyn FnMut(&AnyVarHookArgs) -> bool + Send + 'static, smallbox::space::S4>) -> VarHandle {
466        self.0.hooks.push(on_new)
467    }
468
469    fn last_update(&self) -> VarUpdateId {
470        // can be active update, or any of the conditions that updated and caused this update.
471        VarUpdateId(self.0.last_active_change.load(Ordering::Relaxed)).max(self.active().0.last_update())
472    }
473
474    fn modify_info(&self) -> ModifyInfo {
475        self.active().0.modify_info()
476    }
477
478    fn modify_importance(&self) -> usize {
479        self.active().0.modify_importance()
480    }
481
482    // the conditions could be animating too, but this was not handled in the previous impl either
483
484    fn is_animating(&self) -> bool {
485        self.active().0.is_animating()
486    }
487
488    fn hook_animation_stop(&self, handler: AnimationStopFn) -> VarHandle {
489        self.active().0.hook_animation_stop(handler)
490    }
491
492    fn current_context(&self) -> DynAnyVar {
493        self.clone_dyn()
494    }
495}
496
497pub(crate) struct WeakWhenVar(Weak<WhenVarData>);
498impl fmt::Debug for WeakWhenVar {
499    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500        f.debug_tuple("WeakWhenVar").field(&self.0.as_ptr()).finish()
501    }
502}
503impl WeakVarImpl for WeakWhenVar {
504    fn clone_dyn(&self) -> DynWeakAnyVar {
505        DynWeakAnyVar::When(Self(self.0.clone()))
506    }
507
508    fn strong_count(&self) -> usize {
509        self.0.strong_count()
510    }
511
512    fn upgrade(&self) -> Option<DynAnyVar> {
513        Some(DynAnyVar::When(WhenVar(self.0.upgrade()?)))
514    }
515}
516
517fn when_var_easing<O: VarValue + Transitionable>(builder: AnimatingWhenVarBuilder<O>) -> Var<O> {
518    if builder.builder.is_contextual() {
519        let any = any_contextual_var_impl(smallbox!(builder), TypeId::of::<O>());
520        return Var::new_any(any);
521    }
522    when_var_easing_tail(builder)
523}
524struct AnimatingWhenVarBuilder<O: VarValue + Transitionable> {
525    builder: AnyWhenVarBuilder,
526    condition_easing: Vec<Option<EasingData>>,
527    default_easing: EasingData,
528    _t: PhantomData<fn() -> O>,
529}
530impl<O: VarValue + Transitionable> ContextInitFnImpl for AnimatingWhenVarBuilder<O> {
531    fn init(&mut self) -> AnyVar {
532        let builder = AnimatingWhenVarBuilder {
533            builder: self.builder.current_context(),
534            condition_easing: self.condition_easing.clone(),
535            default_easing: self.default_easing.clone(),
536            _t: self._t,
537        };
538        when_var_easing_tail(builder).any
539    }
540}
541fn when_var_easing_tail<O: VarValue + Transitionable>(builder: AnimatingWhenVarBuilder<O>) -> Var<O> {
542    let source = when_var_tail_impl(builder.builder);
543    let weak_source = Arc::downgrade(&source.0);
544    let output = var(source.get().downcast::<O>().unwrap());
545    let weak_output = output.downgrade();
546    let condition_easing = builder.condition_easing.into_boxed_slice();
547    let default_easing = builder.default_easing;
548    let mut _animation_handle = AnimationHandle::dummy();
549    source
550        .hook(smallbox!(move |args: &AnyVarHookArgs| {
551            if let Some(output) = weak_output.upgrade() {
552                let source = weak_source.upgrade().unwrap();
553                for ((c, _), easing) in source.conditions.iter().zip(&condition_easing) {
554                    if let Some((duration, func)) = easing
555                        && c.get()
556                    {
557                        _animation_handle = output.ease(args.downcast_value::<O>().unwrap().clone(), *duration, clmv!(func, |t| func(t)));
558                        return true;
559                    }
560                }
561                // else default
562                let (duration, func) = &default_easing;
563                _animation_handle = output.ease(args.downcast_value::<O>().unwrap().clone(), *duration, clmv!(func, |t| func(t)));
564                true
565            } else {
566                false
567            }
568        }))
569        .perm();
570    output.hold(source).perm();
571    output
572}
573
574type EasingData = (Duration, Arc<dyn Fn(EasingTime) -> EasingStep + Send + Sync>);
575
576impl<O: VarValue + Transitionable> WhenVarBuilder<O> {
577    /// Build a variable similar to [`Var::easing`], but with different duration and easing functions for each condition.
578    ///
579    /// The `condition_easing` must contain one entry for each when condition, entries can be `None`, the easing used
580    /// is the first entry that corresponds to a `true` condition, or falls back to the `default_easing`.
581    pub fn build_easing(self, condition_easing: Vec<Option<EasingData>>, default_easing: EasingData) -> Var<O> {
582        when_var_easing(AnimatingWhenVarBuilder {
583            builder: self.builder,
584            condition_easing,
585            default_easing,
586            _t: PhantomData,
587        })
588    }
589}