Skip to content

Commit

Permalink
Added a duration for Tweenables and a Sequence of Tweenables (Philipp…
Browse files Browse the repository at this point in the history
…-M#19)

Tweenables now have a duration, which makes sense when using e.g. in a Sequence.
The Sequence is currently modeled via tuples.
Currently just for `(impl Tweenable, impl Tweenable)` but this may be extended (via macros) to more than just 2 element tuples.
This also adds a trait `AnyTweenableElement` which is necessary for the Sequence.
  • Loading branch information
Philipp-M authored Feb 21, 2024
1 parent 626796a commit 2f8d66b
Show file tree
Hide file tree
Showing 3 changed files with 304 additions and 39 deletions.
53 changes: 37 additions & 16 deletions examples/animatables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ fn main() -> Result<()> {
if state.selected_tab == i { 2.0 } else { 1.0 },
))
};

let play_speed = if state.maximize { 1.0 } else { -1.0 };
v_stack((
button(
"Click this button to animate!".fg(Color::Green),
Expand All @@ -48,33 +50,52 @@ fn main() -> Result<()> {
"Click these tabs to maximize each",
weighted_h_stack((tab(0), tab(1), tab(2), tab(3), tab(4))),
"Elastic Title"
.fill_max_width((0.2..1.0).elastic_in_out_ease().tween(
Duration::from_secs_f64(3.5),
if state.maximize { 1.0 } else { -1.0 },
))
.fill_max_width(
(0.2..1.0)
.duration(Duration::from_secs(3))
.elastic_in_out_ease()
.play(play_speed),
)
.border((Borders::HORIZONTAL, BorderKind::ThickStraight)),
"Quadratic Title"
.fill_max_width((0.2..1.0).quadratic_in_out_ease().tween(
Duration::from_secs_f64(1.0),
if state.maximize { 1.0 } else { -1.0 },
))
.fill_max_width((0.2..1.0).quadratic_in_out_ease().play(play_speed))
.border((Borders::HORIZONTAL, BorderKind::ThickStraight)),
"This does some weird stuff"
.fill_max_width(
(
(0.2..1.0).duration(Duration::from_secs(1)),
(1.0..0.5)
.duration(Duration::from_secs(2))
.quadratic_in_out_ease(),
)
.duration(Duration::from_secs(2)) // Note the ratio of the durations specified above stays the same
.play(play_speed),
)
.border((
Borders::HORIZONTAL,
BorderKind::ThickStraight,
Style::default().fg(Color::Blue),
)),
h_stack((
"This box resizes"
.fill_max_size(low_pass(0.05, if state.maximize { 0.2 } else { 0.8 }))
.border(Style::default().fg(Color::Red)),
"same, but different"
.border(BorderKind::Rounded)
.fill_max_height(lerp(
(0.1..1.0).quadratic_in_out_ease(),
low_pass(0.05, if state.maximize { 0.7 } else { 0.1 }),
)),
.fill_max_height(
(0.1..1.0)
.quadratic_in_out_ease()
.lerp(low_pass(0.05, if state.maximize { 0.7 } else { 0.1 })),
),
)),
"Expanding Title 2"
.fill_max_width((0.2..1.0).reverse().quadratic_out_ease().tween(
Duration::from_secs_f64(1.5),
if state.maximize { 1.0 } else { -1.0 },
))
.fill_max_width(
(0.2..1.0)
.reverse()
.duration(Duration::from_millis(500))
.quadratic_out_ease()
.play(play_speed),
)
.border((Borders::HORIZONTAL, BorderKind::DoubleStraight)),
))
},
Expand Down
143 changes: 131 additions & 12 deletions src/view/animatables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,15 +258,25 @@ pub trait Tweenable<V>: Send + Sync {
message: Box<dyn std::any::Any>,
) -> MessageResult<()>;

fn tween<PS>(self, duration: Duration, play_speed: PS) -> Tween<PS, Self>
/// Overrides the duration of any tweenable it composes
fn duration(self, duration: Duration) -> WithDuration<Self>
where
Self: Sized,
{
WithDuration {
tweenable: self,
duration,
}
}

fn play<PS>(self, play_speed: PS) -> PlayTween<PS, Self>
where
Self: Sized,
PS: Animatable<f64>,
{
Tween {
PlayTween {
play_speed,
tweenable: self,
duration,
}
}

Expand Down Expand Up @@ -372,34 +382,143 @@ impl<A: Animatable<f64>> Tweenable<f64> for Range<A> {
}
}

// Sequence of multiple tweenables, not sure yet whether this should be done via tuples (as that syntax is already used by ViewSequences)
impl<V: 'static, T1: Tweenable<V>, T2: Tweenable<V>> Tweenable<V> for (T1, T2) {
type State = ((Id, T1::State), (Id, T2::State));

type Element = widget::animatables::Sequence<V>;

fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
let (id, (state, element)) = cx.with_new_id(|cx| {
let (id0, state0, element0) = self.0.build(cx);
let (id1, state1, element1) = self.1.build(cx);
let element =
widget::animatables::Sequence::new(vec![Box::new(element0), Box::new(element1)]);
(((id0, state0), (id1, state1)), element)
});
(id, state, element)
}

fn rebuild(
&self,
cx: &mut Cx,
prev: &Self,
id: &mut Id,
((id0, state0), (id1, state1)): &mut Self::State,
element: &mut Self::Element,
) -> ChangeFlags {
cx.with_id(*id, |cx| {
self.0.rebuild(
cx,
&prev.0,
id0,
state0,
(*element.tweenables[0])
.as_any_mut()
.downcast_mut()
.unwrap(),
) | self.1.rebuild(
cx,
&prev.1,
id1,
state1,
(*element.tweenables[1])
.as_any_mut()
.downcast_mut()
.unwrap(),
)
})
}

fn message(
&self,
id_path: &[Id],
((id0, state0), (id1, state1)): &mut Self::State,
message: Box<dyn std::any::Any>,
) -> MessageResult<()> {
match id_path {
[id, rest_path @ ..] if id == id0 => self.0.message(rest_path, state0, message),
[id, rest_path @ ..] if id == id1 => self.1.message(rest_path, state1, message),
[..] => MessageResult::Stale(message),
}
}
}

// TODO should this also be used within other animatables directly (not just Tweenable)?
// TODO Duration could be animated too
/// Overrides the duration of any tweenable it composes
pub struct WithDuration<T> {
pub(crate) tweenable: T,
pub(crate) duration: Duration,
}

impl<V, T: Tweenable<V>> Tweenable<V> for WithDuration<T> {
type State = T::State;

type Element = widget::animatables::WithDuration<T::Element>;

fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
let (id, state, element) = self.tweenable.build(cx);
(
id,
state,
widget::animatables::WithDuration::new(element, self.duration),
)
}

fn rebuild(
&self,
cx: &mut Cx,
prev: &Self,
id: &mut Id,
state: &mut Self::State,
element: &mut Self::Element,
) -> ChangeFlags {
let mut changeflags = ChangeFlags::empty();
if self.duration != prev.duration {
element.duration = self.duration;
changeflags |= ChangeFlags::ANIMATION;
}
changeflags
| self
.tweenable
.rebuild(cx, &prev.tweenable, id, state, &mut element.tweenable)
}

fn message(
&self,
id_path: &[Id],
state: &mut Self::State,
message: Box<dyn std::any::Any>,
) -> MessageResult<()> {
self.tweenable.message(id_path, state, message)
}
}

// TODO Duration could also be animated, but I'm not sure it's worth the complexity (vs benefit)...
#[derive(Clone, Debug)]
pub struct Tween<PS, TW> {
pub struct PlayTween<PS, TW> {
play_speed: PS,
tweenable: TW,
duration: Duration,
}

impl<V, PS, TW> Animatable<V> for Tween<PS, TW>
impl<V, PS, TW> Animatable<V> for PlayTween<PS, TW>
where
V: 'static,
PS: Animatable<f64>,
TW: Tweenable<V>,
{
type State = (Id, PS::State, Id, TW::State);

type Element = widget::animatables::Tween<PS::Element, TW::Element>;
type Element = widget::animatables::PlayTween<PS::Element, TW::Element>;

fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
let (id, (state, element)) = cx.with_new_id(|cx| {
let (play_speed_id, play_speed_state, play_speed_element) = self.play_speed.build(cx);
let (tweenable_id, tweenable_state, tweenable_element) = self.tweenable.build(cx);

let element = widget::animatables::Tween::new(
play_speed_element,
tweenable_element,
self.duration,
);
let element =
widget::animatables::PlayTween::new(play_speed_element, tweenable_element);
(
(
play_speed_id,
Expand Down
Loading

0 comments on commit 2f8d66b

Please sign in to comment.