Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keyframe Animations #563

Merged
merged 16 commits into from
Sep 5, 2024
222 changes: 50 additions & 172 deletions examples/animations/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,186 +1,64 @@
use std::time::Duration;

use floem::{
animate::{animation, EasingFn},
event::EventListener,
animate::Animation,
event::EventListener as EL,
peniko::Color,
reactive::{create_rw_signal, create_signal, SignalGet, SignalUpdate},
style_class,
views::{container, empty, h_stack, label, stack, static_label, text, v_stack, Decorators},
reactive::{RwSignal, SignalGet, Trigger},
unit::DurationUnitExt,
views::{empty, h_stack, Decorators},
IntoView,
};

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

style_class!(pub Button);
fn progress_bar_container() -> impl IntoView {
let width = 300.0;
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"),
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()
}
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_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))
let animation = RwSignal::new(
Animation::new()
.duration(5.seconds())
.keyframe(50, |kf| {
kf.style(|s| s.background(Color::BLACK).size(30, 30))
.easing_in()
})
.width(400)
.border(1.0)
.border_color(Color::DIM_GRAY)
})
}
.keyframe(100, |kf| {
kf.style(|s| s.background(Color::AQUAMARINE).size(10, 300))
.easing_out()
})
.repeat(true)
.auto_reverse(true),
);

fn cube_container() -> impl IntoView {
let (counter, set_counter) = create_signal(0.0);
let (is_hovered, set_is_hovered) = create_signal(false);
let pause = Trigger::new();
let resume = Trigger::new();

stack({
(label(|| "Hover or click me!")
.on_click_stop(move |_| {
set_counter.update(|value| *value += 1.0);
})
.on_event_stop(EventListener::PointerEnter, move |_| {
set_is_hovered.update(|val| *val = true);
})
.on_event_stop(EventListener::PointerLeave, move |_| {
set_is_hovered.update(|val| *val = false);
})
.style(|s| {
s.border(1.0)
.background(Color::RED)
.color(Color::BLACK)
.padding(10.0)
.margin(20.0)
.size(120.0, 120.0)
.active(|s| s.color(Color::BLACK))
})
.animation(
animation()
//TODO:
// .border_radius(move || if is_hovered.get() { 1.0 } else { 40.0 })
.border_color(|| Color::CYAN)
.color(|| Color::CYAN)
.background(move || {
if is_hovered.get() {
Color::DEEP_PINK
} else {
Color::DARK_ORANGE
}
})
.easing_fn(EasingFn::Quartic)
.ease_in_out()
.duration(Duration::from_secs(1)),
),)
})
.style(|s| {
s.border(5.0)
.background(Color::BLUE)
.padding(10.0)
.size(400.0, 400.0)
.color(Color::BLACK)
})
.animation(
animation()
.width(move || {
if counter.get() % 2.0 == 0.0 {
400.0
} else {
600.0
}
h_stack((
empty()
.style(|s| s.background(Color::RED).size(500, 100))
.animation(move |_| animation.get().duration(10.seconds())),
empty()
.style(|s| s.background(Color::BLUE).size(50, 100))
.animation(move |_| animation.get())
.animation(move |a| {
a.keyframe(100, |kf| {
kf.style(|s| s.border(5).border_color(Color::PURPLE))
})
.duration(5.seconds())
.repeat(true)
.auto_reverse(true)
}),
empty()
.style(|s| s.background(Color::GREEN).size(100, 300))
.animation(move |_| {
animation
.get()
.pause(move || pause.track())
.resume(move || resume.track())
.delay(3.seconds())
})
.height(move || {
if counter.get() % 2.0 == 0.0 {
200.0
} else {
500.0
}
.on_event_stop(EL::PointerEnter, move |_| {
pause.notify();
})
.border_color(|| Color::CYAN)
.color(|| Color::CYAN)
.background(|| Color::LAVENDER)
.easing_fn(EasingFn::Cubic)
.ease_in_out()
.auto_reverse(true)
.duration(Duration::from_secs(2)),
)
.on_event_stop(EL::PointerLeave, move |_| {
resume.notify();
}),
))
.style(|s| s.size_full().gap(10).items_center().justify_center())
}

fn main() {
Expand Down
31 changes: 17 additions & 14 deletions examples/themes/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use floem::{
reactive::{create_signal, SignalGet, SignalUpdate},
style::{Background, BorderColor, Outline, OutlineColor, Style, TextColor, Transition},
style_class,
unit::DurationUnitExt,
views::{label, stack, text, Decorators},
IntoView, View,
};
Expand All @@ -20,10 +21,10 @@ fn app_view() -> impl IntoView {
.border(1.0)
.border_color(Color::rgb8(109, 121, 135))
.hover(|s| s.background(Color::rgb8(170, 175, 187)))
.transition(TextColor, Transition::linear(0.06))
.transition(BorderColor, Transition::linear(0.06))
.transition(Background, Transition::linear(0.06))
.transition(Outline, Transition::linear(0.1))
.transition(TextColor, Transition::ease_in_out(60.millis()))
.transition(BorderColor, Transition::ease_in_out(60.millis()))
.transition(Background, Transition::ease_in_out(60.millis()))
.transition(Outline, Transition::ease_in_out(100.millis()))
.focus_visible(|s| {
s.outline(2.0)
.outline_color(Color::WHITE.with_alpha_factor(0.7))
Expand All @@ -38,12 +39,13 @@ fn app_view() -> impl IntoView {
.border_radius(5.0);
let blue_theme = Style::new()
.background(Color::rgb8(95, 102, 118))
.transition(Background, Transition::linear(0.1))
.transition(TextColor, Transition::linear(0.1))
.transition(Background, Transition::ease_in_out(500.millis()))
.transition(TextColor, Transition::ease_in_out(500.millis()))
.color(Color::WHITE)
.class(Button, move |_| blue_button)
.class(Label, |s| {
s.margin(4.0).transition(TextColor, Transition::linear(0.1))
s.margin(4.0)
.transition(TextColor, Transition::ease_in_out(100.millis()))
})
.font_size(12.0);

Expand All @@ -57,11 +59,11 @@ fn app_view() -> impl IntoView {
.active(|s| s.background(Color::rgb8(95, 105, 88)).color(Color::WHITE))
.color(Color::BLACK.with_alpha_factor(0.7))
.border(2.0)
.transition(TextColor, Transition::linear(0.3))
.transition(BorderColor, Transition::linear(0.3))
.transition(Background, Transition::linear(0.3))
.transition(Outline, Transition::linear(0.2))
.transition(OutlineColor, Transition::linear(0.2))
.transition(TextColor, Transition::ease_in_out(300.millis()))
.transition(BorderColor, Transition::ease_in_out(300.millis()))
.transition(Background, Transition::ease_in_out(300.millis()))
.transition(Outline, Transition::ease_in_out(200.millis()))
.transition(OutlineColor, Transition::ease_in_out(200.millis()))
.outline_color(Color::rgba8(131, 145, 123, 0))
.focus_visible(|s| {
s.outline(10.0)
Expand All @@ -74,10 +76,11 @@ fn app_view() -> impl IntoView {
.margin(6.0);
let green_theme = Style::new()
.background(Color::rgb8(227, 231, 226))
.transition(Background, Transition::linear(0.5))
.transition(Background, Transition::ease_in_out(500.millis()))
.class(Button, move |_| green_button)
.class(Label, |s| {
s.margin(4.0).transition(TextColor, Transition::linear(0.5))
s.margin(4.0)
.transition(TextColor, Transition::ease_in_out(500.millis()))
})
.class(Frame, |s| {
s.border(2.0)
Expand Down
4 changes: 2 additions & 2 deletions examples/widget-gallery/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use floem::{
peniko::Color,
reactive::{create_signal, SignalGet, SignalUpdate},
style::{Background, CursorStyle, Transition},
unit::UnitExt,
unit::{DurationUnitExt, UnitExt},
views::{
button, h_stack, label, scroll, stack, tab, v_stack, virtual_stack, Decorators,
VirtualDirection, VirtualItemSize,
Expand Down Expand Up @@ -102,7 +102,7 @@ fn app_view() -> impl IntoView {
.padding(5.0)
.width(100.pct())
.height(36.0)
.transition(Background, Transition::linear(0.4))
.transition(Background, Transition::ease_in_out(400.millis()))
.items_center()
.border_bottom(1.0)
.border_color(Color::LIGHT_GRAY)
Expand Down
Loading