mirror of
https://github.com/danbulant/cushy
synced 2026-06-20 23:11:12 +00:00
Animation docs, on_complete
This commit is contained in:
parent
32b5e16695
commit
126b324b55
7 changed files with 177 additions and 13 deletions
|
|
@ -15,7 +15,7 @@ Gooey uses a reactive data model. To see [an example][button-example] of how
|
|||
reactive data models work, consider this example that displays a button that
|
||||
increments its own label:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
// Create a dynamic usize.
|
||||
let count = Dynamic::new(0_usize);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Gooey uses a reactive data model. To see [an example][button-example] of how
|
|||
reactive data models work, consider this example that displays a button that
|
||||
increments its own label:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
$../examples/button.rs:readme$
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Gooey uses a reactive data model. To see [an example][button-example] of how
|
|||
reactive data models work, consider this example that displays a button that
|
||||
increments its own label:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
// Create a dynamic usize.
|
||||
let count = Dynamic::new(0_usize);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use gooey::animation::{AnimationHandle, AnimationTarget, Spawn};
|
||||
use gooey::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn};
|
||||
use gooey::value::Dynamic;
|
||||
use gooey::widgets::{Button, Label, Stack};
|
||||
use gooey::{widgets, Run, WithClone};
|
||||
|
|
@ -9,6 +9,14 @@ fn main() -> gooey::Result {
|
|||
let animation = Dynamic::new(AnimationHandle::new());
|
||||
let value = Dynamic::new(50);
|
||||
let label = value.map_each(|value| value.to_string());
|
||||
|
||||
// Gooey's animation system supports using a `Duration` as a step in
|
||||
// animation to create a delay. This can also be used to call a function
|
||||
// after a specified amount of time:
|
||||
Duration::from_secs(1)
|
||||
.on_complete(|| println!("Gooey animations are neat!"))
|
||||
.launch();
|
||||
|
||||
Stack::columns(widgets![
|
||||
Button::new("To 0").on_click(animate_to(&animation, &value, 0)),
|
||||
Label::new(label),
|
||||
|
|
@ -24,6 +32,12 @@ fn animate_to(
|
|||
) -> impl FnMut(()) {
|
||||
(animation, value).with_clone(|(animation, value)| {
|
||||
move |_| {
|
||||
// Here we use spawn to schedule the animation, which returns an
|
||||
// `AnimationHandle`. When dropped, the animation associated with
|
||||
// the `AnimationHandle` will be cancelled. The effect is that this
|
||||
// line of code will ensure we only keep one animation running at
|
||||
// all times in this example, despite how many times the buttons are
|
||||
// pressed.
|
||||
animation.set(
|
||||
value
|
||||
.transition_to(target)
|
||||
|
|
|
|||
150
src/animation.rs
150
src/animation.rs
|
|
@ -1,4 +1,39 @@
|
|||
//! Types for creating animations.
|
||||
//!
|
||||
//! Animations in Gooey are performed by transitioning a [`Dynamic`]'s contained
|
||||
//! value over time. This starts with [`Dynamic::transition_to()`], which
|
||||
//! returns a [`DynamicTransition`].
|
||||
//!
|
||||
//! [`DynamicTransition`] implements [`AnimationTarget`], a trait that describes
|
||||
//! types that can be updated using [linear interpolation](LinearInterpolate).
|
||||
//! `AnimationTarget` is also implemented for tuples of `AnimationTarget`
|
||||
//! implementors, allowing multiple transitions to be an `AnimationTarget`.
|
||||
//!
|
||||
//! Next, the [`AnimationTarget`] is turned into an animation by invoking
|
||||
//! [`AnimationTarget::over()`] with the [`Duration`] the transition should
|
||||
//! occur over. The animation can further be customized using
|
||||
//! [`Animation::with_easing()`] to utilize any [`Easing`] implementor.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use std::time::Duration;
|
||||
//!
|
||||
//! use gooey::animation::{AnimationTarget, Spawn};
|
||||
//! use gooey::value::Dynamic;
|
||||
//!
|
||||
//! let value = Dynamic::new(0);
|
||||
//!
|
||||
//! value
|
||||
//! .transition_to(100)
|
||||
//! .over(Duration::from_millis(100))
|
||||
//! .launch();
|
||||
//!
|
||||
//! let mut reader = value.into_reader();
|
||||
//! while reader.block_until_updated() {
|
||||
//! println!("{}", reader.get());
|
||||
//! }
|
||||
//!
|
||||
//! assert_eq!(reader.get(), 100);
|
||||
//! ```
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
|
|
@ -43,7 +78,11 @@ fn animation_thread() {
|
|||
let mut index = 0;
|
||||
while index < state.running.len() {
|
||||
let animation_id = *state.running.member(index).expect("index in bounds");
|
||||
if state.animations[animation_id].animate(elapsed).is_break() {
|
||||
let animation_state = &mut state.animations[animation_id];
|
||||
if animation_state.animation.animate(elapsed).is_break() {
|
||||
if !animation_state.handle_attached {
|
||||
state.animations.remove(animation_id);
|
||||
}
|
||||
state.running.remove_member(index);
|
||||
} else {
|
||||
index += 1;
|
||||
|
|
@ -62,8 +101,13 @@ fn animation_thread() {
|
|||
}
|
||||
}
|
||||
|
||||
struct AnimationState {
|
||||
animation: Box<dyn Animate>,
|
||||
handle_attached: bool,
|
||||
}
|
||||
|
||||
struct Animating {
|
||||
animations: Lots<Box<dyn Animate>>,
|
||||
animations: Lots<AnimationState>,
|
||||
running: Set<LotId>,
|
||||
last_updated: Option<Instant>,
|
||||
}
|
||||
|
|
@ -78,7 +122,10 @@ impl Animating {
|
|||
}
|
||||
|
||||
fn spawn(&mut self, animation: Box<dyn Animate>) -> AnimationHandle {
|
||||
let id = self.animations.push(animation);
|
||||
let id = self.animations.push(AnimationState {
|
||||
animation,
|
||||
handle_attached: true,
|
||||
});
|
||||
|
||||
if self.running.is_empty() {
|
||||
NEW_ANIMATIONS.notify_one();
|
||||
|
|
@ -93,6 +140,14 @@ impl Animating {
|
|||
self.animations.remove(id);
|
||||
self.running.remove(&id);
|
||||
}
|
||||
|
||||
fn run_unattached(&mut self, id: LotId) {
|
||||
if self.running.contains(&id) {
|
||||
self.animations[id].handle_attached = false;
|
||||
} else {
|
||||
self.animations.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that can animate.
|
||||
|
|
@ -256,6 +311,14 @@ pub trait IntoAnimate: Sized + Send + Sync {
|
|||
fn and_then<Other: IntoAnimate>(self, other: Other) -> Chain<Self, Other> {
|
||||
Chain::new(self, other)
|
||||
}
|
||||
|
||||
/// Invokes `on_complete` after this animation finishes.
|
||||
fn on_complete<F>(self, on_complete: F) -> OnCompleteAnimation<Self>
|
||||
where
|
||||
F: FnMut() + Send + Sync + 'static,
|
||||
{
|
||||
OnCompleteAnimation::new(self, on_complete)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_tuple_animate {
|
||||
|
|
@ -300,6 +363,14 @@ pub trait Spawn {
|
|||
///
|
||||
/// When the returned handle is dropped, the animation is stopped.
|
||||
fn spawn(self) -> AnimationHandle;
|
||||
|
||||
/// Launches this animation, running it to completion in the background.
|
||||
fn launch(self)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.spawn().detach();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Spawn for T
|
||||
|
|
@ -372,6 +443,18 @@ impl AnimationHandle {
|
|||
thread_state().remove_animation(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Detaches the animation from the [`AnimationHandle`], allowing the
|
||||
/// animation to continue running to completion.
|
||||
///
|
||||
/// Normally, dropping an [`AnimationHandle`] will cancel the underlying
|
||||
/// animation. This API provides a way to continue running an animation
|
||||
/// through completion without needing to hold onto the handle.
|
||||
pub fn detach(mut self) {
|
||||
if let Some(id) = self.0.take() {
|
||||
thread_state().run_unattached(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AnimationHandle {
|
||||
|
|
@ -437,6 +520,67 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// An animation wrapper that invokes a callback upon the animation completing.
|
||||
///
|
||||
/// This type guarantees the callback will only be invoked once per animation
|
||||
/// completion. If the animation is restarted after completing, the callback
|
||||
/// will be invoked again.
|
||||
pub struct OnCompleteAnimation<A> {
|
||||
animation: A,
|
||||
callback: Box<dyn FnMut() + Send + Sync + 'static>,
|
||||
completed: bool,
|
||||
}
|
||||
|
||||
impl<A> OnCompleteAnimation<A> {
|
||||
/// Returns a pending animation that performs `animation` then invokes
|
||||
/// `on_complete`.
|
||||
pub fn new<F>(animation: A, on_complete: F) -> Self
|
||||
where
|
||||
F: FnMut() + Send + Sync + 'static,
|
||||
{
|
||||
Self {
|
||||
animation,
|
||||
callback: Box::new(on_complete),
|
||||
completed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> IntoAnimate for OnCompleteAnimation<A>
|
||||
where
|
||||
A: IntoAnimate,
|
||||
{
|
||||
type Animate = OnCompleteAnimation<A::Animate>;
|
||||
|
||||
fn into_animate(self) -> Self::Animate {
|
||||
OnCompleteAnimation {
|
||||
animation: self.animation.into_animate(),
|
||||
callback: self.callback,
|
||||
completed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> Animate for OnCompleteAnimation<A>
|
||||
where
|
||||
A: Animate,
|
||||
{
|
||||
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
|
||||
if self.completed {
|
||||
ControlFlow::Break(elapsed)
|
||||
} else {
|
||||
match self.animation.animate(elapsed) {
|
||||
ControlFlow::Break(remaining) => {
|
||||
self.completed = true;
|
||||
(self.callback)();
|
||||
ControlFlow::Break(remaining)
|
||||
}
|
||||
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAnimate for Duration {
|
||||
type Animate = Self;
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ impl<T> Deref for Lazy<T> {
|
|||
}
|
||||
|
||||
/// 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),+`, 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 {
|
||||
|
|
|
|||
16
src/value.rs
16
src/value.rs
|
|
@ -68,7 +68,7 @@ impl<T> Dynamic<T> {
|
|||
/// code may produce slightly more readable code.
|
||||
///
|
||||
/// ```rust
|
||||
/// let value = gooey::dynamic::Dynamic::new(1);
|
||||
/// let value = gooey::value::Dynamic::new(1);
|
||||
///
|
||||
/// // Using with_clone
|
||||
/// value.with_clone(|value| {
|
||||
|
|
@ -136,7 +136,7 @@ impl<T> Dynamic<T> {
|
|||
|
||||
/// Returns a new reference-based reader for this dynamic value.
|
||||
#[must_use]
|
||||
pub fn create_ref_reader(&self) -> DynamicReader<T> {
|
||||
pub fn create_reader(&self) -> DynamicReader<T> {
|
||||
self.state().readers += 1;
|
||||
DynamicReader {
|
||||
source: self.0.clone(),
|
||||
|
|
@ -144,6 +144,12 @@ impl<T> Dynamic<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts this [`Dynamic`] into a reader.
|
||||
#[must_use]
|
||||
pub fn into_reader(self) -> DynamicReader<T> {
|
||||
self.create_reader()
|
||||
}
|
||||
|
||||
fn state(&self) -> MutexGuard<'_, State<T>> {
|
||||
self.0.state()
|
||||
}
|
||||
|
|
@ -193,7 +199,7 @@ impl<T> Drop for Dynamic<T> {
|
|||
|
||||
impl<T> From<Dynamic<T>> for DynamicReader<T> {
|
||||
fn from(value: Dynamic<T>) -> Self {
|
||||
value.create_ref_reader()
|
||||
value.create_reader()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -375,7 +381,7 @@ impl<T> DynamicReader<T> {
|
|||
/// updated or there are no remaining writers for the value.
|
||||
///
|
||||
/// Returns true if a newly updated value was discovered.
|
||||
pub fn block_until_updated_async(&mut self) -> BlockUntilUpdatedFuture<'_, T> {
|
||||
pub fn wait_until_updated(&mut self) -> BlockUntilUpdatedFuture<'_, T> {
|
||||
BlockUntilUpdatedFuture(self)
|
||||
}
|
||||
}
|
||||
|
|
@ -424,7 +430,7 @@ impl<'a, T> Future for BlockUntilUpdatedFuture<'a, T> {
|
|||
#[test]
|
||||
fn disconnecting_reader_from_dynamic() {
|
||||
let value = Dynamic::new(1);
|
||||
let mut ref_reader = value.create_ref_reader();
|
||||
let mut ref_reader = value.create_reader();
|
||||
drop(value);
|
||||
assert!(!ref_reader.block_until_updated());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue