mirror of
https://github.com/danbulant/cushy
synced 2026-06-24 17:12:11 +00:00
Generic ForEach/MapEach
This commit is contained in:
parent
6d41902002
commit
ad57e02e4f
4 changed files with 185 additions and 34 deletions
|
|
@ -1,16 +1,17 @@
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
use gooey::value::Dynamic;
|
use gooey::value::{Dynamic, MapEach};
|
||||||
use gooey::widget::MakeWidget;
|
use gooey::widget::MakeWidget;
|
||||||
use gooey::widgets::{Align, Button, Expand, Input, Label, Resize, Stack};
|
use gooey::widgets::{Align, Button, Expand, Input, Label, Resize, Stack};
|
||||||
use gooey::{children, Run, WithClone};
|
use gooey::{children, Run};
|
||||||
use kludgine::figures::units::Lp;
|
use kludgine::figures::units::Lp;
|
||||||
|
|
||||||
fn main() -> gooey::Result {
|
fn main() -> gooey::Result {
|
||||||
let username = Dynamic::default();
|
let username = Dynamic::default();
|
||||||
let password = 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(
|
Expand::new(Align::centered(Resize::width(
|
||||||
// TODO We need a min/max range for the Resize widget
|
// TODO We need a min/max range for the Resize widget
|
||||||
|
|
@ -44,7 +45,7 @@ fn main() -> gooey::Result {
|
||||||
println!("Welcome, {}", username.get());
|
println!("Welcome, {}", username.get());
|
||||||
exit(0);
|
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 {
|
fn validate(username: &String, password: &String) -> bool {
|
||||||
!username.is_empty() && !password.is_empty()
|
!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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -327,7 +327,7 @@ pub trait IntoAnimate: Sized + Send + Sync {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_tuple_animate {
|
macro_rules! impl_tuple_animate {
|
||||||
($($type:ident $field:tt),+) => {
|
($($type:ident $field:tt $var:ident),+) => {
|
||||||
impl<$($type),+> AnimationTarget for ($($type,)+) where $($type: AnimationTarget),+ {
|
impl<$($type),+> AnimationTarget for ($($type,)+) where $($type: AnimationTarget),+ {
|
||||||
type Running = ($(<$type>::Running,)+);
|
type Running = ($(<$type>::Running,)+);
|
||||||
|
|
||||||
|
|
|
||||||
16
src/utils.rs
16
src/utils.rs
|
|
@ -5,17 +5,17 @@ use kludgine::app::winit::event::Modifiers;
|
||||||
use kludgine::app::winit::keyboard::ModifiersState;
|
use kludgine::app::winit::keyboard::ModifiersState;
|
||||||
|
|
||||||
/// Invokes the provided macro with a pattern that can be matched using this
|
/// 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
|
/// identifier to use for the generic parameter and `$field` is the field index
|
||||||
/// inside of the tuple.
|
/// inside of the tuple.
|
||||||
macro_rules! impl_all_tuples {
|
macro_rules! impl_all_tuples {
|
||||||
($macro_name:ident) => {
|
($macro_name:ident) => {
|
||||||
$macro_name!(T0 0);
|
$macro_name!(T0 0 t0);
|
||||||
$macro_name!(T0 0, T1 1);
|
$macro_name!(T0 0 t0, T1 1 t1);
|
||||||
$macro_name!(T0 0, T1 1, T2 2);
|
$macro_name!(T0 0 t0, T1 1 t1, T2 2 t2);
|
||||||
$macro_name!(T0 0, T1 1, T2 2, T3 3);
|
$macro_name!(T0 0 t0, T1 1 t1, T2 2 t2, T3 3 t3);
|
||||||
$macro_name!(T0 0, T1 1, T2 2, T3 3, T4 4);
|
$macro_name!(T0 0 t0, T1 1 t1, T2 2 t2, T3 3 t3, T4 4 t4);
|
||||||
$macro_name!(T0 0, T1 1, T2 2, T3 3, T4 4, T5 5);
|
$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 {
|
macro_rules! impl_with_clone {
|
||||||
($($name:ident $field:tt),+) => {
|
($($name:ident $field:tt $var:ident),+) => {
|
||||||
impl<'a, $($name: Clone,)+> WithClone for ($(&'a $name,)+)
|
impl<'a, $($name: Clone,)+> WithClone for ($(&'a $name,)+)
|
||||||
{
|
{
|
||||||
type Cloned = ($($name,)+);
|
type Cloned = ($($name,)+);
|
||||||
|
|
|
||||||
171
src/value.rs
171
src/value.rs
|
|
@ -2,12 +2,14 @@
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::panic::AssertUnwindSafe;
|
use std::panic::AssertUnwindSafe;
|
||||||
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
|
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
|
||||||
use std::task::{Poll, Waker};
|
use std::task::{Poll, Waker};
|
||||||
|
|
||||||
use crate::animation::{DynamicTransition, LinearInterpolate};
|
use crate::animation::{DynamicTransition, LinearInterpolate};
|
||||||
use crate::context::{WidgetContext, WindowHandle};
|
use crate::context::{WidgetContext, WindowHandle};
|
||||||
|
use crate::utils::WithClone;
|
||||||
|
|
||||||
/// An instance of a value that provides APIs to observe and react to its
|
/// An instance of a value that provides APIs to observe and react to its
|
||||||
/// contents.
|
/// contents.
|
||||||
|
|
@ -161,6 +163,18 @@ impl<T> Dynamic<T> {
|
||||||
self.create_reader()
|
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>> {
|
fn state(&self) -> MutexGuard<'_, State<T>> {
|
||||||
self.0.state()
|
self.0.state()
|
||||||
}
|
}
|
||||||
|
|
@ -298,6 +312,7 @@ impl<T> DynamicData<T> {
|
||||||
returned
|
returned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct State<T> {
|
struct State<T> {
|
||||||
wrapped: GenerationalValue<T>,
|
wrapped: GenerationalValue<T>,
|
||||||
callbacks: Vec<Box<dyn ValueCallback<T>>>,
|
callbacks: Vec<Box<dyn ValueCallback<T>>>,
|
||||||
|
|
@ -337,6 +352,36 @@ struct GenerationalValue<T> {
|
||||||
pub generation: Generation,
|
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.
|
/// A reader that tracks the last generation accessed through this reader.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DynamicReader<T> {
|
pub struct DynamicReader<T> {
|
||||||
|
|
@ -650,3 +695,129 @@ impl<T> IntoValue<Option<T>> for T {
|
||||||
Value::Constant(Some(self))
|
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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue