zng_var/
util.rs

1///Implements `T: IntoVar<U>`, `T: IntoValue<U>` and optionally `U: From<T>` without boilerplate.
2///
3/// The macro syntax is one or more functions with signature `fn from(t: T) -> U`. The [`LocalVar<U>`]
4/// type is selected for variables. The syntax also supports generic types and constraints, but not `where` constraints.
5/// You can also destructure the input if it is a tuple using the pattern `fn from((a, b): (A, B)) -> U`, but no other pattern
6/// matching in the input is supported.
7///
8/// The `U: From<T>` implement is optional, you can use the syntax `fn from(t: T) -> U;` to only generate
9/// the `T: IntoVar<U>` and `T: IntoValue<U>` implementations using an already implemented `U: From<T>`.
10///
11/// [`LocalVar<U>`]: crate::LocalVar
12///
13/// # Examples
14///
15/// The example declares an `enum` that represents the values possible in a property `foo` and
16/// then implements conversions from literals the user may want to type in a widget:
17///
18/// ```
19/// # use zng_var::impl_from_and_into_var;
20/// #[derive(Debug, Clone, PartialEq)]
21/// pub enum FooValue {
22///     On,
23///     Off,
24///     NotSet
25/// }
26///
27/// impl_from_and_into_var! {
28///     fn from(b: bool) -> FooValue {
29///         if b {
30///             FooValue::On
31///         } else {
32///             FooValue::Off
33///         }
34///     }
35///
36///     fn from(s: &str) -> FooValue {
37///         match s {
38///             "on" => FooValue::On,
39///             "off" => FooValue::Off,
40///             _ => FooValue::NotSet
41///         }
42///     }
43///
44///     fn from(f: Foo) -> FooValue;
45/// }
46///
47/// impl From<Foo> for FooValue {
48///     fn from(foo: Foo) -> Self {
49///         Self::On
50///     }
51/// }///
52/// # pub struct Foo;
53/// # fn assert(_: impl zng_var::IntoVar<FooValue> + Into<FooValue>) { }
54/// # assert(true);
55/// # assert("on");
56/// ```
57#[macro_export]
58macro_rules! impl_from_and_into_var {
59    ($($tt:tt)+) => {
60        $crate::__impl_from_and_into_var! { $($tt)* }
61    };
62}
63
64use parking_lot::RwLock;
65
66use crate::{AnyVarHookArgs, AnyVarValue};
67
68use super::{VARS, VarHandle, VarHook, VarModify, VarUpdateId, VarValue, animation::ModifyInfo};
69
70#[doc(hidden)]
71#[macro_export]
72macro_rules! __impl_from_and_into_var {
73    // START:
74    (
75        $(#[$docs:meta])*
76        fn from ( $($input:tt)+ )
77        $($rest:tt)+
78    ) => {
79        $crate::__impl_from_and_into_var! {
80            =input=>
81            [
82                input { $($input)+ }
83                generics { }
84                docs { $(#[$docs])* }
85            ]
86            ( $($input)+ ) $($rest)+
87        }
88    };
89    // GENERICS START:
90    (
91        $(#[$docs:meta])*
92        fn from <
93        $($rest:tt)+
94    ) => {
95        $crate::__impl_from_and_into_var! {
96            =generics=>
97            [
98                generics { < }
99                docs { $(#[$docs])* }
100            ]
101            $($rest)+
102        }
103    };
104    // GENERICS END `>`:
105    (
106        =generics=>
107        [
108            generics { $($generics:tt)+ }
109            $($config:tt)*
110        ]
111
112        >( $($input:tt)+ ) $($rest:tt)+
113    ) => {
114        $crate::__impl_from_and_into_var! {
115            =input=>
116            [
117                input { $($input)+ }
118                generics { $($generics)+ > }
119                $($config)*
120            ]
121            ( $($input)+ ) $($rest)+
122        }
123    };
124    // GENERICS END `>>`:
125    (
126        =generics=>
127        [
128            generics { $($generics:tt)+ }
129            $($config:tt)*
130        ]
131
132        >>( $($input:tt)+ ) $($rest:tt)+
133    ) => {
134        $crate::__impl_from_and_into_var! {
135            =input=>
136            [
137                input { $($input)+ }
138                generics { $($generics)+ >> }
139                $($config)*
140            ]
141            ( $($input)+ ) $($rest)+
142        }
143    };
144    // collect generics:
145    (
146        =generics=>
147        [
148            generics { $($generics:tt)+ }
149            $($config:tt)*
150        ]
151
152        $tt:tt $($rest:tt)+
153    ) => {
154        //zng_proc_macros::trace! {
155        $crate::__impl_from_and_into_var! {
156            =generics=>
157            [
158                generics { $($generics)+ $tt }
159                $($config)*
160            ]
161            $($rest)*
162        }
163        //}
164    };
165    // INPUT SIMPLE:
166    (
167        =input=>
168        [$($config:tt)*]
169        ($ident:ident : $Input:ty $(,)?) $($rest:tt)+
170    ) => {
171        $crate::__impl_from_and_into_var! {
172            =output=>
173            [
174                input_type { $Input }
175                $($config)*
176            ]
177            $($rest)+
178        }
179    };
180    // INPUT TUPLE:
181    (
182        =input=>
183        [$($config:tt)*]
184        (( $($destructure:tt)+ ) : $Input:ty $(,)?) $($rest:tt)+
185    ) => {
186        $crate::__impl_from_and_into_var! {
187            =output=>
188            [
189                input_type { $Input }
190                $($config)*
191            ]
192            $($rest)+
193        }
194    };
195    // INPUT ARRAY:
196    (
197        =input=>
198        [$($config:tt)*]
199        ([ $($destructure:tt)+ ] : $Input:ty $(,)?) $($rest:tt)+
200    ) => {
201        $crate::__impl_from_and_into_var! {
202            =output=>
203            [
204                input_type { $Input }
205                $($config)*
206            ]
207            $($rest)+
208        }
209    };
210
211    // OUTPUT (without From):
212    (
213        =output=>
214        [
215            input_type { $Input:ty }
216            input { $($input:tt)+ }
217            generics { $($generics:tt)* }
218            docs { $($docs:tt)* }
219        ]
220        -> $Output:ty
221        ;
222
223        $($rest:tt)*
224    ) => {
225        impl $($generics)* $crate::IntoVar<$Output> for $Input {
226            type Var = $crate::LocalVar<$Output>;
227
228            $($docs)*
229
230            fn into_var(self) -> Self::Var {
231                $crate::LocalVar(self.into())
232            }
233        }
234
235        impl $($generics)* $crate::IntoValue<$Output> for $Input { }
236
237        // NEXT CONVERSION:
238        $crate::__impl_from_and_into_var! {
239            $($rest)*
240        }
241    };
242
243    // OUTPUT (with From):
244    (
245        =output=>
246        [
247            input_type { $Input:ty }
248            input { $($input:tt)+ }
249            generics { $($generics:tt)* }
250            docs { $($docs:tt)* }
251        ]
252        -> $Output:ty
253        $convert:block
254
255        $($rest:tt)*
256    ) => {
257        impl $($generics)* From<$Input> for $Output {
258            $($docs)*
259
260            fn from($($input)+) -> Self
261            $convert
262        }
263
264        impl $($generics)* $crate::IntoVar<$Output> for $Input {
265            type Var = $crate::LocalVar<$Output>;
266
267            $($docs)*
268
269            fn into_var(self) -> Self::Var {
270                $crate::LocalVar(self.into())
271            }
272        }
273
274        impl $($generics)* $crate::IntoValue<$Output> for $Input { }
275
276        // NEXT CONVERSION:
277        $crate::__impl_from_and_into_var! {
278            $($rest)*
279        }
280    };
281
282    () => {
283        // END
284    };
285}
286
287struct VarMeta {
288    last_update: VarUpdateId,
289    hooks: Vec<VarHook>,
290    animation: ModifyInfo,
291}
292impl VarMeta {
293    fn push_hook(&mut self, pos_modify_action: Box<dyn Fn(&AnyVarHookArgs) -> bool + Send + Sync>) -> VarHandle {
294        let (hook, weak) = VarHandle::new(pos_modify_action);
295        self.hooks.push(weak);
296        hook
297    }
298
299    fn skip_modify(&mut self) -> bool {
300        let cur_anim = VARS.current_modify();
301        if cur_anim.importance() < self.animation.importance() {
302            return true;
303        }
304        self.animation = cur_anim;
305        false
306    }
307}
308
309struct VarDataInner {
310    value: Box<dyn AnyVarValue>,
311    meta: VarMeta,
312}
313
314pub(super) struct VarData(RwLock<VarDataInner>);
315
316impl VarData {
317    pub fn new(value: impl VarValue) -> Self {
318        Self::new_impl(Box::new(value))
319    }
320    fn new_impl(value: Box<dyn AnyVarValue>) -> Self {
321        Self(RwLock::new(VarDataInner {
322            value,
323            meta: VarMeta {
324                last_update: VarUpdateId::never(),
325                hooks: vec![],
326                animation: ModifyInfo::never(),
327            },
328        }))
329    }
330
331    pub fn into_value<T: VarValue>(self) -> T {
332        *self.0.into_inner().value.into_any().downcast::<T>().unwrap()
333    }
334
335    fn read<T: VarValue>(&self) -> parking_lot::MappedRwLockReadGuard<T> {
336        let read = self.0.read();
337        parking_lot::RwLockReadGuard::map(read, |r| r.value.as_any().downcast_ref::<T>().unwrap())
338    }
339
340    /// Read the value.
341    pub fn with<T: VarValue, R>(&self, f: impl FnOnce(&T) -> R) -> R {
342        f(&*self.read())
343    }
344
345    pub fn last_update(&self) -> VarUpdateId {
346        self.0.read().meta.last_update
347    }
348
349    pub fn is_animating(&self) -> bool {
350        self.0.read().meta.animation.is_animating()
351    }
352
353    pub fn modify_importance(&self) -> usize {
354        self.0.read().meta.animation.importance()
355    }
356
357    pub fn push_hook(&self, pos_modify_action: Box<dyn Fn(&AnyVarHookArgs) -> bool + Send + Sync>) -> VarHandle {
358        self.0.write().meta.push_hook(pos_modify_action)
359    }
360
361    pub fn push_animation_hook(&self, handler: Box<dyn FnOnce() + Send>) -> Result<(), Box<dyn FnOnce() + Send>> {
362        self.0.write().meta.animation.hook_animation_stop(handler)
363    }
364
365    #[cfg(feature = "dyn_closure")]
366    pub fn apply_modify<T: VarValue>(&self, modify: Box<dyn FnOnce(&mut VarModify<T>) + 'static>) {
367        apply_modify(
368            &self.0,
369            Box::new(move |v| {
370                let mut value = VarModify::new(v.as_any().downcast_ref::<T>().unwrap());
371                modify(&mut value);
372                let (notify, new_value, update, tags, importance) = value.finish();
373                (
374                    notify,
375                    match new_value {
376                        Some(v) => Some(Box::new(v)),
377                        None => None,
378                    },
379                    update,
380                    tags,
381                    importance,
382                )
383            }),
384        )
385    }
386
387    #[cfg(not(feature = "dyn_closure"))]
388    pub fn apply_modify<T: VarValue>(&self, modify: impl FnOnce(&mut VarModify<T>) + 'static) {
389        apply_modify(
390            &self.0,
391            Box::new(move |v| {
392                let mut value = VarModify::new(v.as_any().downcast_ref::<T>().unwrap());
393                modify(&mut value);
394                let (notify, new_value, update, tags, importance) = value.finish();
395                (
396                    notify,
397                    match new_value {
398                        Some(v) => Some(Box::new(v)),
399                        None => None,
400                    },
401                    update,
402                    tags,
403                    importance,
404                )
405            }),
406        )
407    }
408}
409
410fn apply_modify(
411    inner: &RwLock<VarDataInner>,
412    modify: Box<dyn FnOnce(&dyn AnyVarValue) -> (bool, Option<Box<dyn AnyVarValue>>, bool, Vec<Box<dyn AnyVarValue>>, Option<usize>)>,
413) {
414    let mut data = inner.write();
415    if data.meta.skip_modify() {
416        return;
417    }
418
419    let data = parking_lot::RwLockWriteGuard::downgrade(data);
420
421    let (notify, new_value, update, tags, custom_importance) = modify(&*data.value);
422
423    if notify {
424        drop(data);
425        let mut data = inner.write();
426        if let Some(nv) = new_value {
427            data.value = nv;
428        }
429        data.meta.last_update = VARS.update_id();
430
431        if let Some(i) = custom_importance {
432            data.meta.animation.importance = i;
433        }
434
435        if !data.meta.hooks.is_empty() {
436            let mut hooks = std::mem::take(&mut data.meta.hooks);
437
438            let meta = parking_lot::RwLockWriteGuard::downgrade(data);
439
440            let args = AnyVarHookArgs::new(&*meta.value, update, &tags);
441            hooks.retain(|h| h.call(&args));
442            drop(meta);
443
444            let mut data = inner.write();
445            hooks.append(&mut data.meta.hooks);
446            data.meta.hooks = hooks;
447        }
448
449        VARS.wake_app();
450    } else if let Some(i) = custom_importance {
451        drop(data);
452        let mut data = inner.write();
453        data.meta.animation.importance = i;
454    }
455}