Skip to content

Commit

Permalink
Generic ForEach/MapEach
Browse files Browse the repository at this point in the history
  • Loading branch information
ecton committed Nov 8, 2023
1 parent 6d41902 commit ad57e02
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 34 deletions.
30 changes: 5 additions & 25 deletions examples/login.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -44,7 +45,7 @@ fn main() -> gooey::Result {
println!("Welcome, {}", username.get());
exit(0);
})
.into_default(), // TODO enable/disable based on valid
.into_default(),
]),
]),
)))
Expand All @@ -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<String>, password: &Dynamic<String>) -> Dynamic<bool> {
// 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
}
2 changes: 1 addition & 1 deletion src/animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,)+);

Expand Down
16 changes: 8 additions & 8 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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,)+);
Expand Down
171 changes: 171 additions & 0 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -161,6 +163,18 @@ impl<T> Dynamic<T> {
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<T>> {
self.0.state()
}
Expand Down Expand Up @@ -298,6 +312,7 @@ impl<T> DynamicData<T> {
returned
}
}

struct State<T> {
wrapped: GenerationalValue<T>,
callbacks: Vec<Box<dyn ValueCallback<T>>>,
Expand Down Expand Up @@ -337,6 +352,36 @@ struct GenerationalValue<T> {
pub generation: Generation,
}

/// An exclusive reference to the contents of a [`Dynamic`].
#[derive(Debug)]
pub struct DynamicGuard<'a, T> {
guard: MutexGuard<'a, State<T>>,
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<T> 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<T> {
Expand Down Expand Up @@ -650,3 +695,129 @@ impl<T> IntoValue<Option<T>> for T {
Value::Constant(Some(self))
}
}

/// A type that can have a `for_each` operation applied to it.
pub trait ForEach<T> {
/// 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<F>(&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<F>(&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<U>` from a `T` passed into a mapping
/// function.
pub trait MapEach<T, U> {
/// 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<F>(&self, map_each: F) -> Dynamic<U>
where
F: for<'a> FnMut(Self::Ref<'a>) -> U + Send + 'static;
}

macro_rules! impl_tuple_map_each {
($($type:ident $field:tt $var:ident),+) => {
impl<U, $($type),+> MapEach<($($type,)+), U> for ($(&Dynamic<$type>,)+)
where
U: Send + 'static,
$($type: Send + 'static),+
{
type Ref<'a> = ($(&'a $type,)+);

fn map_each<F>(&self, mut map_each: F) -> Dynamic<U>
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);

0 comments on commit ad57e02

Please sign in to comment.