Skip to content

Commit bd26fcc

Browse files
thearturcaLGUG2Z
authored andcommitted
feat(animation): cubic-bezier for styles
This commit adds ability to use cubic-bezier as an animation style, which allows users to customize the smoothness of animations.
1 parent 5e308b9 commit bd26fcc

File tree

2 files changed

+135
-1
lines changed

2 files changed

+135
-1
lines changed

komorebi/src/animation/style.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,61 @@ impl Ease for EaseInOutBounce {
355355
}
356356
}
357357

358+
pub struct CubicBezier {
359+
pub x1: f64,
360+
pub y1: f64,
361+
pub x2: f64,
362+
pub y2: f64,
363+
}
364+
365+
impl CubicBezier {
366+
fn x(&self, s: f64) -> f64 {
367+
3.0 * self.x1 * s * (1.0 - s).powi(2) + 3.0 * self.x2 * s.powi(2) * (1.0 - s) + s.powi(3)
368+
}
369+
370+
fn y(&self, s: f64) -> f64 {
371+
3.0 * self.y1 * s * (1.0 - s).powi(2) + 3.0 * self.y2 * s.powi(2) * (1.0 - s) + s.powi(3)
372+
}
373+
374+
fn dx_ds(&self, s: f64) -> f64 {
375+
3.0 * self.x1 * (1.0 - s) * (1.0 - 3.0 * s)
376+
+ 3.0 * self.x2 * (2.0 * s - 3.0 * s.powi(2))
377+
+ 3.0 * s.powi(2)
378+
}
379+
380+
fn find_s(&self, t: f64) -> f64 {
381+
if t <= 0.0 {
382+
return 0.0;
383+
}
384+
385+
if t >= 1.0 {
386+
return 1.0;
387+
}
388+
389+
let mut s = t;
390+
391+
for _ in 0..8 {
392+
let x_val = self.x(s);
393+
let dx_val = self.dx_ds(s);
394+
if dx_val.abs() < 1e-6 {
395+
break;
396+
}
397+
let delta = (x_val - t) / dx_val;
398+
s = (s - delta).clamp(0.0, 1.0);
399+
if delta.abs() < 1e-6 {
400+
break;
401+
}
402+
}
403+
404+
s
405+
}
406+
407+
fn evaluate(&self, t: f64) -> f64 {
408+
let s = self.find_s(t.clamp(0.0, 1.0));
409+
self.y(s)
410+
}
411+
}
412+
358413
pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {
359414
match style {
360415
AnimationStyle::Linear => Linear::evaluate(t),
@@ -387,5 +442,6 @@ pub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {
387442
AnimationStyle::EaseInBounce => EaseInBounce::evaluate(t),
388443
AnimationStyle::EaseOutBounce => EaseOutBounce::evaluate(t),
389444
AnimationStyle::EaseInOutBounce => EaseInOutBounce::evaluate(t),
445+
AnimationStyle::CubicBezier(x1, y1, x2, y2) => CubicBezier { x1, y1, x2, y2 }.evaluate(t),
390446
}
391447
}

komorebi/src/core/animation.rs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use clap::ValueEnum;
22

3+
use serde::ser::SerializeSeq;
34
use serde::Deserialize;
45
use serde::Serialize;
56
use strum::Display;
67
use strum::EnumString;
78

8-
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]
9+
#[derive(Copy, Clone, Debug, Display, EnumString, ValueEnum, PartialEq)]
910
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1011
pub enum AnimationStyle {
1112
Linear,
@@ -38,4 +39,81 @@ pub enum AnimationStyle {
3839
EaseInBounce,
3940
EaseOutBounce,
4041
EaseInOutBounce,
42+
#[value(skip)]
43+
CubicBezier(f64, f64, f64, f64),
44+
}
45+
46+
// Custom serde implementation
47+
impl<'de> Deserialize<'de> for AnimationStyle {
48+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
49+
where
50+
D: serde::Deserializer<'de>,
51+
{
52+
struct AnimationStyleVisitor;
53+
54+
impl<'de> serde::de::Visitor<'de> for AnimationStyleVisitor {
55+
type Value = AnimationStyle;
56+
57+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
58+
formatter.write_str("a string or an array of four f64 values")
59+
}
60+
61+
// Handle string variants (e.g., "EaseInOutExpo")
62+
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
63+
where
64+
E: serde::de::Error,
65+
{
66+
value.parse().map_err(|_| E::unknown_variant(value, &[]))
67+
}
68+
69+
// Handle CubicBezier array (e.g., [0.32, 0.72, 0.0, 1.0])
70+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
71+
where
72+
A: serde::de::SeqAccess<'de>,
73+
{
74+
let x1 = seq
75+
.next_element()?
76+
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
77+
let y1 = seq
78+
.next_element()?
79+
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
80+
let x2 = seq
81+
.next_element()?
82+
.ok_or_else(|| serde::de::Error::invalid_length(2, &self))?;
83+
let y2 = seq
84+
.next_element()?
85+
.ok_or_else(|| serde::de::Error::invalid_length(3, &self))?;
86+
87+
// Ensure no extra elements
88+
if seq.next_element::<serde::de::IgnoredAny>()?.is_some() {
89+
return Err(serde::de::Error::invalid_length(5, &self));
90+
}
91+
92+
Ok(AnimationStyle::CubicBezier(x1, y1, x2, y2))
93+
}
94+
}
95+
96+
deserializer.deserialize_any(AnimationStyleVisitor)
97+
}
98+
}
99+
100+
impl Serialize for AnimationStyle {
101+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
102+
where
103+
S: serde::Serializer,
104+
{
105+
match self {
106+
// Serialize CubicBezier as an array
107+
AnimationStyle::CubicBezier(x1, y1, x2, y2) => {
108+
let mut seq = serializer.serialize_seq(Some(4))?;
109+
seq.serialize_element(x1)?;
110+
seq.serialize_element(y1)?;
111+
seq.serialize_element(x2)?;
112+
seq.serialize_element(y2)?;
113+
seq.end()
114+
}
115+
// Serialize all other variants as strings
116+
_ => serializer.serialize_str(&self.to_string()),
117+
}
118+
}
41119
}

0 commit comments

Comments
 (0)