diff --git a/examples/login.rs b/examples/login.rs index 9617e01e4..afc6e9899 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -1,16 +1,17 @@ use std::process::exit; -use gooey::value::Dynamic; +use gooey::value::{Dynamic, MapEach}; use gooey::widget::MakeWidget; use gooey::widgets::{Align, Button, Expand, Input, Label, Resize, Stack}; -use gooey::{children, Run, WithClone}; +use gooey::{children, Run}; use kludgine::figures::units::Lp; fn main() -> gooey::Result { let username = Dynamic::default(); let password = Dynamic::default(); - let valid = setup_validation(&username, &password); + let valid = + (&username, &password).map_each(|(username, password)| validate(username, password)); Expand::new(Align::centered(Resize::width( // TODO We need a min/max range for the Resize widget @@ -44,7 +45,7 @@ fn main() -> gooey::Result { println!("Welcome, {}", username.get()); exit(0); }) - .into_default(), // TODO enable/disable based on valid + .into_default(), ]), ]), ))) @@ -54,24 +55,3 @@ fn main() -> gooey::Result { fn validate(username: &String, password: &String) -> bool { !username.is_empty() && !password.is_empty() } - -fn setup_validation(username: &Dynamic, password: &Dynamic) -> Dynamic { - // TODO This is absolutely horrible. The problem is that within for_each, - // the value is still locked. Thus, we can't have a generic callback that - // tries to lock the value that is being mapped in for_each. - // - // We might be able to make a genericized implementation for_each for - // tuples, ie, (&Dynamic, &Dynamic).for_each(|(a, b)| ..). - let valid = Dynamic::default(); - username.for_each((&valid, password).with_clone(|(valid, password)| { - move |username: &String| { - password.map_ref(|password| valid.update(validate(username, password))) - } - })); - password.for_each((&valid, username).with_clone(|(valid, username)| { - move |password: &String| { - username.map_ref(|username| valid.update(validate(username, password))) - } - })); - valid -} diff --git a/src/animation.rs b/src/animation.rs index e1e7af9a7..7622a6812 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -327,7 +327,7 @@ pub trait IntoAnimate: Sized + Send + Sync { } macro_rules! impl_tuple_animate { - ($($type:ident $field:tt),+) => { + ($($type:ident $field:tt $var:ident),+) => { impl<$($type),+> AnimationTarget for ($($type,)+) where $($type: AnimationTarget),+ { type Running = ($(<$type>::Running,)+); diff --git a/src/utils.rs b/src/utils.rs index ed7484556..ec57975b6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,17 +5,17 @@ use kludgine::app::winit::event::Modifiers; use kludgine::app::winit::keyboard::ModifiersState; /// Invokes the provided macro with a pattern that can be matched using this -/// `macro_rules!` expression: `$($type:ident $field:tt),+`, where `$type` is an +/// `macro_rules!` expression: `$($type:ident $field:tt $var:ident),+`, where `$type` is an /// identifier to use for the generic parameter and `$field` is the field index /// inside of the tuple. macro_rules! impl_all_tuples { ($macro_name:ident) => { - $macro_name!(T0 0); - $macro_name!(T0 0, T1 1); - $macro_name!(T0 0, T1 1, T2 2); - $macro_name!(T0 0, T1 1, T2 2, T3 3); - $macro_name!(T0 0, T1 1, T2 2, T3 3, T4 4); - $macro_name!(T0 0, T1 1, T2 2, T3 3, T4 4, T5 5); + $macro_name!(T0 0 t0); + $macro_name!(T0 0 t0, T1 1 t1); + $macro_name!(T0 0 t0, T1 1 t1, T2 2 t2); + $macro_name!(T0 0 t0, T1 1 t1, T2 2 t2, T3 3 t3); + $macro_name!(T0 0 t0, T1 1 t1, T2 2 t2, T3 3 t3, T4 4 t4); + $macro_name!(T0 0 t0, T1 1 t1, T2 2 t2, T3 3 t3, T4 4 t4, T5 5 t5); } } @@ -29,7 +29,7 @@ pub trait WithClone: Sized { } macro_rules! impl_with_clone { - ($($name:ident $field:tt),+) => { + ($($name:ident $field:tt $var:ident),+) => { impl<'a, $($name: Clone,)+> WithClone for ($(&'a $name,)+) { type Cloned = ($($name,)+); diff --git a/src/value.rs b/src/value.rs index 0b6c328cc..a759ad446 100644 --- a/src/value.rs +++ b/src/value.rs @@ -2,12 +2,14 @@ use std::fmt::Debug; use std::future::Future; +use std::ops::{Deref, DerefMut}; use std::panic::AssertUnwindSafe; use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError}; use std::task::{Poll, Waker}; use crate::animation::{DynamicTransition, LinearInterpolate}; use crate::context::{WidgetContext, WindowHandle}; +use crate::utils::WithClone; /// An instance of a value that provides APIs to observe and react to its /// contents. @@ -161,6 +163,18 @@ impl Dynamic { self.create_reader() } + /// Returns an exclusive reference to the contents of this dynamic. + /// + /// This call will block until all other guards for this dynamic have been + /// dropped. + #[must_use] + pub fn lock(&self) -> DynamicGuard<'_, T> { + DynamicGuard { + guard: self.0.state(), + accessed_mut: false, + } + } + fn state(&self) -> MutexGuard<'_, State> { self.0.state() } @@ -298,6 +312,7 @@ impl DynamicData { returned } } + struct State { wrapped: GenerationalValue, callbacks: Vec>>, @@ -337,6 +352,36 @@ struct GenerationalValue { pub generation: Generation, } +/// An exclusive reference to the contents of a [`Dynamic`]. +#[derive(Debug)] +pub struct DynamicGuard<'a, T> { + guard: MutexGuard<'a, State>, + accessed_mut: bool, +} + +impl<'a, T> Deref for DynamicGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.guard.wrapped.value + } +} + +impl<'a, T> DerefMut for DynamicGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.accessed_mut = true; + &mut self.guard.wrapped.value + } +} + +impl Drop for DynamicGuard<'_, T> { + fn drop(&mut self) { + if self.accessed_mut { + todo!("trigger callbacks") + } + } +} + /// A reader that tracks the last generation accessed through this reader. #[derive(Debug)] pub struct DynamicReader { @@ -650,3 +695,129 @@ impl IntoValue> for T { Value::Constant(Some(self)) } } + +/// A type that can have a `for_each` operation applied to it. +pub trait ForEach { + /// The borrowed representation of T to pass into the `for_each` function. + type Ref<'a>; + + /// Apply `for_each` to each value contained within `self`. + fn for_each(&self, for_each: F) + where + F: for<'a> FnMut(Self::Ref<'a>) + Send + 'static; +} + +macro_rules! impl_tuple_for_each { + ($($type:ident $field:tt $var:ident),+) => { + impl<$($type,)+> ForEach<($($type,)+)> for ($(&Dynamic<$type>,)+) + where + $($type: Send + 'static,)+ + { + type Ref<'a> = ($(&'a $type,)+); + + #[allow(unused_mut)] + fn for_each(&self, mut for_each: F) + where + F: for<'a> FnMut(Self::Ref<'a>) + Send + 'static, + { + impl_tuple_for_each!(self for_each [] [$($type $field $var),+]); + } + } + }; + ($self:ident $for_each:ident [] [$type:ident $field:tt $var:ident]) => { + $self.$field.for_each(move |field: &$type| $for_each((field,))); + }; + ($self:ident $for_each:ident [] [$($type:ident $field:tt $var:ident),+]) => { + let $for_each = Arc::new(Mutex::new($for_each)); + $(let $var = $self.$field.clone();)* + + + impl_tuple_for_each!(invoke $self $for_each [] [$($type $field $var),+]); + }; + ( + invoke + // Identifiers used from the outer method + $self:ident $for_each:ident + // List of all tuple fields that have already been positioned as the focused call + [$($ltype:ident $lfield:tt $lvar:ident),*] + // + [$type:ident $field:tt $var:ident, $($rtype:ident $rfield:tt $rvar:ident),+] + ) => { + + impl_tuple_for_each!( + invoke + $self $for_each + $type $field $var + [$($ltype $lfield $lvar,)* $type $field $var, $($rtype $rfield $rvar),+] + [$($ltype $lfield $lvar,)* $($rtype $rfield $rvar),+] + ) + }; + ( + invoke + // Identifiers used from the outer method + $self:ident $for_each:ident + // Tuple field that for_each is being invoked on + $type:ident $field:tt $var:ident + // The list of all tuple fields in this invocation, in the correct order. + [$($atype:ident $afield:tt $avar:ident),+] + // The list of tuple fields excluding the one being invoked. + [$($rtype:ident $rfield:tt $rvar:ident),+] + ) => { + $var.for_each((&$for_each, $(&$rvar,)+).with_clone(|(for_each, $($rvar,)+)| { + move |$var: &$type| { + $(let $rvar = $rvar.lock();)+ + let mut for_each = + for_each.lock().map_or_else(PoisonError::into_inner, |g| g); + (for_each)(($(&$avar,)+)); + } + })); + }; +} + +impl_all_tuples!(impl_tuple_for_each); + +/// A type that can create a `Dynamic` from a `T` passed into a mapping +/// function. +pub trait MapEach { + /// The borrowed representation of `T` passed into the mapping function. + type Ref<'a>; + + /// Apply `map_each` to each value in `self`, storing the result in the + /// returned dynamic. + fn map_each(&self, map_each: F) -> Dynamic + where + F: for<'a> FnMut(Self::Ref<'a>) -> U + Send + 'static; +} + +macro_rules! impl_tuple_map_each { + ($($type:ident $field:tt $var:ident),+) => { + impl MapEach<($($type,)+), U> for ($(&Dynamic<$type>,)+) + where + U: Send + 'static, + $($type: Send + 'static),+ + { + type Ref<'a> = ($(&'a $type,)+); + + fn map_each(&self, mut map_each: F) -> Dynamic + where + F: for<'a> FnMut(Self::Ref<'a>) -> U + Send + 'static, + { + let dynamic = { + $(let $var = self.$field.lock();)+ + + Dynamic::new(map_each(($(&$var,)+))) + }; + self.for_each({ + let dynamic = dynamic.clone(); + + move |tuple| { + dynamic.set(map_each(tuple)); + } + }); + dynamic + } + } + }; +} + +impl_all_tuples!(impl_tuple_map_each);