mirror of
https://github.com/danbulant/cushy
synced 2026-06-16 13:01: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 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<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 {
|
||||
($($type:ident $field:tt),+) => {
|
||||
($($type:ident $field:tt $var:ident),+) => {
|
||||
impl<$($type),+> AnimationTarget for ($($type,)+) where $($type: AnimationTarget),+ {
|
||||
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;
|
||||
|
||||
/// 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,)+);
|
||||
|
|
|
|||
171
src/value.rs
171
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<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()
|
||||
}
|
||||
|
|
@ -298,6 +312,7 @@ impl<T> DynamicData<T> {
|
|||
returned
|
||||
}
|
||||
}
|
||||
|
||||
struct State<T> {
|
||||
wrapped: GenerationalValue<T>,
|
||||
callbacks: Vec<Box<dyn ValueCallback<T>>>,
|
||||
|
|
@ -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> {
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue