Skip to content

Commit

Permalink
Add start/stop for animation. Dont advance animation if paused/stopped (
Browse files Browse the repository at this point in the history
#386)

* Add start/stop for animation. Dont advance animation if paused/stopped

* Fix false positive clippy warning for unwrap_or_default
  • Loading branch information
presiyan-ivanov authored Mar 23, 2024
1 parent 37903ea commit 19090b6
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 75 deletions.
117 changes: 83 additions & 34 deletions examples/animations/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,108 @@ use floem::{
animate::{animation, EasingFn},
event::EventListener,
peniko::Color,
reactive::create_signal,
reactive::{create_rw_signal, create_signal},
style_class,
view::View,
views::{empty, label, stack, text, v_stack, Decorators},
views::{container, empty, h_stack, label, stack, static_label, text, v_stack, Decorators},
};

fn app_view() -> impl View {
v_stack((progress_bar_container(), cube_container()))
}

style_class!(pub Button);
fn progress_bar_container() -> impl View {
let width = 300.0;
let (anim_id, set_anim_id) = create_signal(None);
let (is_paused, set_is_paused) = create_signal(false);
let anim_id = create_rw_signal(None);
let is_stopped = create_rw_signal(false);
let is_paused = create_rw_signal(false);

v_stack((
text("Progress bar"),
empty()
.style(|s| {
s.border(1.0)
.border_color(Color::DIM_GRAY)
.background(Color::LIME_GREEN)
.border_radius(10)
.width(0)
.height(20.)
.margin_vert(10)
.active(|s| s.color(Color::BLACK))
})
.animation(
//TODO: add on_update so we can track the animation state(completed/paused/running etc.)
animation()
.on_create(move |id| set_anim_id.update(|aid| *aid = Some(id)))
// Animate from 0 to 300px in 10 seconds
.width(move || width)
.easing_fn(EasingFn::Quartic)
.ease_in_out()
.duration(Duration::from_secs(10)),
),
//TODO: add restart
label(move || if is_paused.get() { "Resume" } else { "Pause" })
.on_click_stop(move |_| {
if let Some(anim_id) = anim_id.get() {
container(
empty()
.style(|s| {
s.border_color(Color::DIM_GRAY)
.background(Color::LIME_GREEN)
.border_radius(3)
.width(0)
.height(20.)
.active(|s| s.color(Color::BLACK))
})
.animation(
animation()
.on_create(move |id| anim_id.update(|aid| *aid = Some(id)))
// Animate from 0 to 300px in 10 seconds
.width(move || width)
.easing_fn(EasingFn::Quartic)
.ease_in_out()
.duration(Duration::from_secs(10)),
),
)
.style(move |s| {
s.width(width)
.border(1.0)
.border_radius(2)
.box_shadow_blur(3.0)
.border_color(Color::DIM_GRAY)
.background(Color::DIM_GRAY)
.margin_vert(10)
}),
h_stack((
label(move || if is_stopped.get() { "Start" } else { "Stop" })
.on_click_stop(move |_| {
let anim_id = anim_id.get().expect("id should be set in on_create");
let stopped = is_stopped.get();
if stopped {
anim_id.start()
} else {
anim_id.stop()
}
is_stopped.update(|val| *val = !stopped);
is_paused.update(|val| *val = false);
})
.class(Button),
label(move || if is_paused.get() { "Resume" } else { "Pause" })
.on_click_stop(move |_| {
let anim_id = anim_id.get().expect("id should be set in on_create");
let paused = is_paused.get();
if paused {
anim_id.resume()
} else {
anim_id.pause()
}
set_is_paused.update(|val| *val = !paused);
}
})
.style(|s| s.width(70).border(1.0).padding_left(10).border_radius(5)),
is_paused.update(|val| *val = !paused);
})
.disabled(move || is_stopped.get())
.class(Button),
static_label("Restart")
.on_click_stop(move |_| {
let anim_id = anim_id.get().expect("id should be set in on_create");
anim_id.stop();
anim_id.start();
is_stopped.update(|val| *val = false);
is_paused.update(|val| *val = false);
})
.class(Button),
)),
))
.style(|s| s.margin_bottom(80).padding_left(5))
.style(|s| {
s.margin_vert(20)
.margin_horiz(10)
.padding(8)
.class(Button, |s| {
s.width(70)
.border(1.0)
.padding_left(10)
.border_radius(5)
.margin_left(5.)
.disabled(|s| s.background(Color::DIM_GRAY))
})
.width(400)
.border(1.0)
.border_color(Color::DIM_GRAY)
})
}

fn cube_container() -> impl View {
Expand Down
14 changes: 14 additions & 0 deletions src/animate/anim_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ impl AnimId {
AnimId(id)
}

pub fn start(&self) {
ANIM_UPDATE_MESSAGES.with(|msgs| {
let mut msgs = msgs.borrow_mut();
msgs.push(AnimUpdateMsg::Start(*self));
});
}

pub fn stop(&self) {
ANIM_UPDATE_MESSAGES.with(|msgs| {
let mut msgs = msgs.borrow_mut();
msgs.push(AnimUpdateMsg::Stop(*self));
});
}

pub fn pause(&self) {
ANIM_UPDATE_MESSAGES.with(|msgs| {
let mut msgs = msgs.borrow_mut();
Expand Down
4 changes: 3 additions & 1 deletion src/animate/anim_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub(crate) enum AnimState {
Idle,
Stopped,
Paused {
elapsed: Option<Duration>,
},
Expand All @@ -24,10 +25,11 @@ pub(crate) enum AnimState {
},
}

#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
pub enum AnimStateKind {
Idle,
Paused,
Stopped,
PassInProgress,
PassFinished,
Completed,
Expand Down
67 changes: 39 additions & 28 deletions src/animate/animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ pub enum AnimUpdateMsg {
},
Pause(AnimId),
Resume(AnimId),
//TODO: restart/stop
Start(AnimId),
Stop(AnimId),
}

#[derive(Clone, Debug)]
Expand All @@ -92,22 +93,35 @@ impl Animation {
}

pub fn is_idle(&self) -> bool {
matches!(self.state_kind(), AnimStateKind::Idle)
self.state_kind() == AnimStateKind::Idle
}

pub fn is_in_progress(&self) -> bool {
matches!(self.state_kind(), AnimStateKind::PassInProgress)
self.state_kind() == AnimStateKind::PassInProgress
}

pub fn is_completed(&self) -> bool {
matches!(self.state_kind(), AnimStateKind::Completed)
self.state_kind() == AnimStateKind::Completed
}

pub fn is_stopped(&self) -> bool {
self.state_kind() == AnimStateKind::Stopped
}

pub fn can_advance(&self) -> bool {
match self.state_kind() {
AnimStateKind::PassFinished | AnimStateKind::PassInProgress | AnimStateKind::Idle => {
true
}
AnimStateKind::Paused | AnimStateKind::Stopped | AnimStateKind::Completed => false,
}
}

pub fn is_auto_reverse(&self) -> bool {
self.auto_reverse
}

/// Returns the ID of the animation. Use this when you want to control(pause or resume) the animation
/// Returns the ID of the animation. Use this when you want to control(stop/pause/resume) the animation
pub fn on_create(mut self, on_create_fn: impl Fn(AnimId) + 'static) -> Self {
self.on_create_listener = Some(Rc::new(on_create_fn));
self
Expand Down Expand Up @@ -223,17 +237,21 @@ impl Animation {
self.ease_mode(EasingMode::InOut)
}

//TODO: Pausing is currently suboptimal because it will keep requesting styling even though the anim
// won't change
pub fn pause(&mut self) {
// TODO: Should we warn/error if the animation is already paused or completed?
debug_assert!(
self.state_kind() != AnimStateKind::Paused,
"Tried to pause an already paused animation"
);
self.state = AnimState::Paused {
elapsed: self.elapsed(),
};
}

pub(crate) fn resume(&mut self) {
// TODO: Should we warn/error if the user tries to resume an animation that is not paused?
debug_assert!(
self.state_kind() == AnimStateKind::Paused,
"Tried to resume an animation that is not paused"
);
if let AnimState::Paused { elapsed } = &self.state {
self.state = AnimState::PassInProgress {
started_on: Instant::now(),
Expand All @@ -242,7 +260,7 @@ impl Animation {
}
}

pub fn begin(&mut self) {
pub fn start(&mut self) {
self.repeat_count = 0;
self.state = AnimState::PassInProgress {
started_on: Instant::now(),
Expand All @@ -251,27 +269,14 @@ impl Animation {
}

pub fn stop(&mut self) {
match &mut self.state {
AnimState::Idle
| AnimState::Completed { .. }
| AnimState::PassFinished { .. }
| AnimState::Paused { .. } => {}
AnimState::PassInProgress {
started_on,
elapsed,
} => {
let duration = Instant::now() - *started_on;
let elapsed = *elapsed + duration;
self.state = AnimState::Completed {
elapsed: Some(elapsed),
}
}
}
self.repeat_count = 0;
self.state = AnimState::Stopped;
}

pub fn state_kind(&self) -> AnimStateKind {
match self.state {
AnimState::Idle => AnimStateKind::Idle,
AnimState::Stopped => AnimStateKind::Stopped,
AnimState::PassInProgress { .. } => AnimStateKind::PassInProgress,
AnimState::PassFinished { .. } => AnimStateKind::PassFinished,
AnimState::Completed { .. } => AnimStateKind::Completed,
Expand All @@ -282,6 +287,7 @@ impl Animation {
pub fn elapsed(&self) -> Option<Duration> {
match &self.state {
AnimState::Idle => None,
AnimState::Stopped => None,
AnimState::PassInProgress {
started_on,
elapsed,
Expand All @@ -298,7 +304,7 @@ impl Animation {
pub fn advance(&mut self) {
match &mut self.state {
AnimState::Idle => {
self.begin();
self.start();
}
AnimState::PassInProgress {
started_on,
Expand Down Expand Up @@ -333,7 +339,12 @@ impl Animation {
}
}
},
AnimState::Paused { .. } => {}
AnimState::Paused { .. } => {
debug_assert!(false, "Tried to advance a paused animation")
}
AnimState::Stopped => {
debug_assert!(false, "Tried to advance a stopped animation")
}
AnimState::Completed { .. } => {}
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/view_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ impl ViewState {

'anim: {
if let Some(animation) = self.animation.as_mut() {
// Means effectively no changes should be applied - bail out
if animation.is_completed() && animation.is_auto_reverse() {
break 'anim;
}
Expand All @@ -258,8 +259,10 @@ impl ViewState {
}
}

animation.advance();
debug_assert!(!animation.is_idle());
if animation.can_advance() {
animation.advance();
debug_assert!(!animation.is_idle());
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/views/text_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,7 @@ impl Widget for TextInput {
let text_node = self.text_node.unwrap();

// FIXME: This layout is undefined.
#[allow(clippy::unwrap_or_default)]
let layout = cx.app_state.get_layout(self.id()).unwrap_or(Layout::new());
let style = cx.app_state_mut().get_builtin_style(self.id());
let node_width = layout.size.width;
Expand Down
Loading

0 comments on commit 19090b6

Please sign in to comment.