Skip to content

Commit

Permalink
Keyframe Animations (lapce#563)
Browse files Browse the repository at this point in the history
* add keyframed animations

* Make animations reactive and stackable

* remove old files

* fix bezier

* add reactive animation state control

* remove animation message comment

* improve easing

* clippy

* add delay

* Fix transition on layout props

* Fix animate towards default

* fix animation view state

* fix animation view state again

* fix clippy

* remove old comment

* fix clippy 2
  • Loading branch information
jrmoulton committed Sep 9, 2024
1 parent 53c3998 commit 791c9ab
Show file tree
Hide file tree
Showing 38 changed files with 1,143 additions and 1,221 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ raw-window-handle = "0.6.0"
wgpu = { version = "0.19.0" }
parking_lot = { version = "0.12.1" }
swash = { version = "0.1.17" }
vello = "0.2.0"

[dependencies]
slotmap = "1.0.7"
Expand Down Expand Up @@ -122,3 +123,8 @@ tokio = ["dep:tokio"]
rfd-async-std = ["dep:rfd", "rfd/async-std"]
rfd-tokio = ["dep:rfd", "rfd/tokio"]
futures = ["dep:futures"]

[patch.crates-io]
vello = {path = "../../vello/vello"}
vello_svg = {path = "../../vello_svg"}

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
12 changes: 4 additions & 8 deletions examples/counter-simple/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use floem::{
peniko::Color,
reactive::create_signal,
reactive::{create_signal, SignalGet, SignalUpdate},
views::{label, ButtonClass, Decorators},
IntoView,
};
Expand All @@ -15,12 +14,9 @@ fn app_view() -> impl IntoView {
label(move || format!("Value: {}", counter.get())),
// Create a horizontal layout
(
"Increment"
.class(ButtonClass)
.style(|s| s.border(0.9).background(Color::TRANSPARENT))
.on_click_stop(move |_| {
set_counter.update(|value| *value += 1);
}),
"Increment".class(ButtonClass).on_click_stop(move |_| {
set_counter.update(|value| *value += 1);
}),
"Decrement".class(ButtonClass).on_click_stop(move |_| {
set_counter.update(|value| *value -= 1);
}),
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
Loading

0 comments on commit 791c9ab

Please sign in to comment.