use crate::easing::Easing;
#[derive(Clone, Debug)]
pub struct Keyframe {
pub time: f32,
pub value: f32,
pub easing: Easing,
}
#[derive(Clone, Debug)]
pub struct KeyframeAnimation {
duration_ms: u32,
keyframes: Vec<Keyframe>,
current_time: f32,
playing: bool,
}
impl KeyframeAnimation {
pub fn new(duration_ms: u32, keyframes: Vec<Keyframe>) -> Self {
Self {
duration_ms,
keyframes,
current_time: 0.0,
playing: false,
}
}
pub fn start(&mut self) {
self.current_time = 0.0;
self.playing = true;
}
pub fn stop(&mut self) {
self.playing = false;
}
pub fn is_playing(&self) -> bool {
self.playing
}
pub fn progress(&self) -> f32 {
self.current_time / self.duration_ms as f32
}
pub fn value(&self) -> f32 {
if self.keyframes.is_empty() {
return 0.0;
}
let progress = self.progress().clamp(0.0, 1.0);
let mut prev_kf = &self.keyframes[0];
let mut next_kf = &self.keyframes[0];
for kf in &self.keyframes {
if kf.time <= progress {
prev_kf = kf;
}
if kf.time >= progress {
next_kf = kf;
break;
}
}
if (prev_kf.time - next_kf.time).abs() < f32::EPSILON {
return prev_kf.value;
}
let local_progress = (progress - prev_kf.time) / (next_kf.time - prev_kf.time);
let eased = next_kf.easing.apply(local_progress);
prev_kf.value + (next_kf.value - prev_kf.value) * eased
}
pub fn tick(&mut self, dt_ms: f32) {
if !self.playing {
return;
}
self.current_time += dt_ms;
if self.current_time >= self.duration_ms as f32 {
self.current_time = self.duration_ms as f32;
self.playing = false;
}
}
pub fn keyframes(&self) -> &[Keyframe] {
&self.keyframes
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct KeyframeProperties {
pub opacity: Option<f32>,
pub scale_x: Option<f32>,
pub scale_y: Option<f32>,
pub translate_x: Option<f32>,
pub translate_y: Option<f32>,
pub rotate: Option<f32>,
pub rotate_x: Option<f32>,
pub rotate_y: Option<f32>,
pub perspective: Option<f32>,
pub depth: Option<f32>,
pub translate_z: Option<f32>,
pub blend_3d: Option<f32>,
pub clip_inset: Option<[f32; 4]>,
pub background_color: Option<[f32; 4]>,
pub gradient_start_color: Option<[f32; 4]>,
pub gradient_end_color: Option<[f32; 4]>,
pub gradient_angle: Option<f32>,
pub border_color: Option<[f32; 4]>,
pub text_color: Option<[f32; 4]>,
pub corner_radius: Option<[f32; 4]>,
pub corner_shape: Option<[f32; 4]>,
pub border_width: Option<f32>,
pub outline_width: Option<f32>,
pub outline_color: Option<[f32; 4]>,
pub outline_offset: Option<f32>,
pub clip_circle_radius: Option<f32>,
pub clip_ellipse_radii: Option<[f32; 2]>,
pub overflow_fade: Option<[f32; 4]>,
pub shadow_params: Option<[f32; 4]>,
pub shadow_color: Option<[f32; 4]>,
pub text_shadow_params: Option<[f32; 4]>,
pub text_shadow_color: Option<[f32; 4]>,
pub light_intensity: Option<f32>,
pub ambient: Option<f32>,
pub specular: Option<f32>,
pub light_direction: Option<[f32; 3]>,
pub filter_grayscale: Option<f32>,
pub filter_invert: Option<f32>,
pub filter_sepia: Option<f32>,
pub filter_brightness: Option<f32>,
pub filter_contrast: Option<f32>,
pub filter_saturate: Option<f32>,
pub filter_hue_rotate: Option<f32>,
pub filter_blur: Option<f32>,
pub backdrop_blur: Option<f32>,
pub backdrop_saturation: Option<f32>,
pub backdrop_brightness: Option<f32>,
pub width: Option<f32>,
pub height: Option<f32>,
pub min_width: Option<f32>,
pub max_width: Option<f32>,
pub min_height: Option<f32>,
pub max_height: Option<f32>,
pub padding: Option<[f32; 4]>,
pub margin: Option<[f32; 4]>,
pub gap: Option<f32>,
pub flex_grow: Option<f32>,
pub flex_shrink: Option<f32>,
pub inset_top: Option<f32>,
pub inset_right: Option<f32>,
pub inset_bottom: Option<f32>,
pub inset_left: Option<f32>,
pub font_size: Option<f32>,
pub skew_x: Option<f32>,
pub skew_y: Option<f32>,
pub transform_origin: Option<[f32; 2]>,
pub z_index: Option<f32>,
pub mask_gradient: Option<[f32; 8]>,
pub svg_fill: Option<[f32; 4]>,
pub svg_stroke: Option<[f32; 4]>,
pub svg_stroke_width: Option<f32>,
pub svg_stroke_dashoffset: Option<f32>,
pub svg_path_data: Option<String>,
}
impl KeyframeProperties {
pub fn opacity(value: f32) -> Self {
Self {
opacity: Some(value),
..Default::default()
}
}
pub fn scale(value: f32) -> Self {
Self {
scale_x: Some(value),
scale_y: Some(value),
..Default::default()
}
}
pub fn translate(x: f32, y: f32) -> Self {
Self {
translate_x: Some(x),
translate_y: Some(y),
..Default::default()
}
}
pub fn rotation(degrees: f32) -> Self {
Self {
rotate: Some(degrees),
..Default::default()
}
}
pub fn with_opacity(mut self, value: f32) -> Self {
self.opacity = Some(value);
self
}
pub fn with_scale(mut self, value: f32) -> Self {
self.scale_x = Some(value);
self.scale_y = Some(value);
self
}
pub fn with_scale_xy(mut self, x: f32, y: f32) -> Self {
self.scale_x = Some(x);
self.scale_y = Some(y);
self
}
pub fn with_translate(mut self, x: f32, y: f32) -> Self {
self.translate_x = Some(x);
self.translate_y = Some(y);
self
}
pub fn with_rotate(mut self, degrees: f32) -> Self {
self.rotate = Some(degrees);
self
}
pub fn with_rotate_x(mut self, degrees: f32) -> Self {
self.rotate_x = Some(degrees);
self
}
pub fn with_rotate_y(mut self, degrees: f32) -> Self {
self.rotate_y = Some(degrees);
self
}
pub fn with_perspective(mut self, px: f32) -> Self {
self.perspective = Some(px);
self
}
pub fn with_depth(mut self, px: f32) -> Self {
self.depth = Some(px);
self
}
pub fn with_translate_z(mut self, px: f32) -> Self {
self.translate_z = Some(px);
self
}
pub fn with_blend_3d(mut self, px: f32) -> Self {
self.blend_3d = Some(px);
self
}
pub fn lerp(&self, other: &Self, t: f32) -> Self {
Self {
opacity: lerp_opt(self.opacity, other.opacity, t),
scale_x: lerp_opt(self.scale_x, other.scale_x, t),
scale_y: lerp_opt(self.scale_y, other.scale_y, t),
translate_x: lerp_opt(self.translate_x, other.translate_x, t),
translate_y: lerp_opt(self.translate_y, other.translate_y, t),
rotate: lerp_opt(self.rotate, other.rotate, t),
rotate_x: lerp_opt(self.rotate_x, other.rotate_x, t),
rotate_y: lerp_opt(self.rotate_y, other.rotate_y, t),
perspective: lerp_opt(self.perspective, other.perspective, t),
depth: lerp_opt(self.depth, other.depth, t),
translate_z: lerp_opt(self.translate_z, other.translate_z, t),
blend_3d: lerp_opt(self.blend_3d, other.blend_3d, t),
clip_inset: lerp_opt_array4(self.clip_inset, other.clip_inset, t),
background_color: lerp_opt_array4(self.background_color, other.background_color, t),
gradient_start_color: lerp_opt_array4(
self.gradient_start_color,
other.gradient_start_color,
t,
),
gradient_end_color: lerp_opt_array4(
self.gradient_end_color,
other.gradient_end_color,
t,
),
gradient_angle: lerp_opt(self.gradient_angle, other.gradient_angle, t),
border_color: lerp_opt_array4(self.border_color, other.border_color, t),
text_color: lerp_opt_array4(self.text_color, other.text_color, t),
corner_radius: lerp_opt_array4(self.corner_radius, other.corner_radius, t),
corner_shape: lerp_opt_array4(self.corner_shape, other.corner_shape, t),
border_width: lerp_opt(self.border_width, other.border_width, t),
outline_width: lerp_opt(self.outline_width, other.outline_width, t),
outline_color: lerp_opt_array4(self.outline_color, other.outline_color, t),
outline_offset: lerp_opt(self.outline_offset, other.outline_offset, t),
clip_circle_radius: lerp_opt(self.clip_circle_radius, other.clip_circle_radius, t),
clip_ellipse_radii: lerp_opt_array2(
self.clip_ellipse_radii,
other.clip_ellipse_radii,
t,
),
overflow_fade: lerp_opt_array4(self.overflow_fade, other.overflow_fade, t),
shadow_params: lerp_opt_array4(self.shadow_params, other.shadow_params, t),
shadow_color: lerp_opt_array4(self.shadow_color, other.shadow_color, t),
text_shadow_params: lerp_opt_array4(
self.text_shadow_params,
other.text_shadow_params,
t,
),
text_shadow_color: lerp_opt_array4(self.text_shadow_color, other.text_shadow_color, t),
light_intensity: lerp_opt(self.light_intensity, other.light_intensity, t),
ambient: lerp_opt(self.ambient, other.ambient, t),
specular: lerp_opt(self.specular, other.specular, t),
light_direction: lerp_opt_array3(self.light_direction, other.light_direction, t),
filter_grayscale: lerp_opt(self.filter_grayscale, other.filter_grayscale, t),
filter_invert: lerp_opt(self.filter_invert, other.filter_invert, t),
filter_sepia: lerp_opt(self.filter_sepia, other.filter_sepia, t),
filter_brightness: lerp_opt(self.filter_brightness, other.filter_brightness, t),
filter_contrast: lerp_opt(self.filter_contrast, other.filter_contrast, t),
filter_saturate: lerp_opt(self.filter_saturate, other.filter_saturate, t),
filter_hue_rotate: lerp_opt(self.filter_hue_rotate, other.filter_hue_rotate, t),
filter_blur: lerp_opt(self.filter_blur, other.filter_blur, t),
backdrop_blur: lerp_opt(self.backdrop_blur, other.backdrop_blur, t),
backdrop_saturation: lerp_opt(self.backdrop_saturation, other.backdrop_saturation, t),
backdrop_brightness: lerp_opt(self.backdrop_brightness, other.backdrop_brightness, t),
width: lerp_opt(self.width, other.width, t),
height: lerp_opt(self.height, other.height, t),
min_width: lerp_opt(self.min_width, other.min_width, t),
max_width: lerp_opt(self.max_width, other.max_width, t),
min_height: lerp_opt(self.min_height, other.min_height, t),
max_height: lerp_opt(self.max_height, other.max_height, t),
padding: lerp_opt_array4(self.padding, other.padding, t),
margin: lerp_opt_array4(self.margin, other.margin, t),
gap: lerp_opt(self.gap, other.gap, t),
flex_grow: lerp_opt(self.flex_grow, other.flex_grow, t),
flex_shrink: lerp_opt(self.flex_shrink, other.flex_shrink, t),
inset_top: lerp_opt(self.inset_top, other.inset_top, t),
inset_right: lerp_opt(self.inset_right, other.inset_right, t),
inset_bottom: lerp_opt(self.inset_bottom, other.inset_bottom, t),
inset_left: lerp_opt(self.inset_left, other.inset_left, t),
font_size: lerp_opt(self.font_size, other.font_size, t),
skew_x: lerp_opt(self.skew_x, other.skew_x, t),
skew_y: lerp_opt(self.skew_y, other.skew_y, t),
transform_origin: lerp_opt_array2(self.transform_origin, other.transform_origin, t),
z_index: lerp_opt(self.z_index, other.z_index, t),
mask_gradient: lerp_opt_array8(self.mask_gradient, other.mask_gradient, t),
svg_fill: lerp_opt_array4(self.svg_fill, other.svg_fill, t),
svg_stroke: lerp_opt_array4(self.svg_stroke, other.svg_stroke, t),
svg_stroke_width: lerp_opt(self.svg_stroke_width, other.svg_stroke_width, t),
svg_stroke_dashoffset: lerp_opt(
self.svg_stroke_dashoffset,
other.svg_stroke_dashoffset,
t,
),
svg_path_data: lerp_path_data(&self.svg_path_data, &other.svg_path_data, t),
}
}
pub fn resolved_opacity(&self) -> f32 {
self.opacity.unwrap_or(1.0)
}
pub fn resolved_scale(&self) -> (f32, f32) {
(self.scale_x.unwrap_or(1.0), self.scale_y.unwrap_or(1.0))
}
pub fn resolved_translate(&self) -> (f32, f32) {
(
self.translate_x.unwrap_or(0.0),
self.translate_y.unwrap_or(0.0),
)
}
pub fn resolved_rotate(&self) -> f32 {
self.rotate.unwrap_or(0.0)
}
pub fn resolved_rotate_x(&self) -> f32 {
self.rotate_x.unwrap_or(0.0)
}
pub fn resolved_rotate_y(&self) -> f32 {
self.rotate_y.unwrap_or(0.0)
}
pub fn resolved_perspective(&self) -> f32 {
self.perspective.unwrap_or(0.0)
}
pub fn resolved_depth(&self) -> f32 {
self.depth.unwrap_or(0.0)
}
}
fn lerp_opt(a: Option<f32>, b: Option<f32>, t: f32) -> Option<f32> {
match (a, b) {
(Some(a), Some(b)) => Some(a + (b - a) * t),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
}
}
fn lerp_opt_array2(a: Option<[f32; 2]>, b: Option<[f32; 2]>, t: f32) -> Option<[f32; 2]> {
match (a, b) {
(Some(a), Some(b)) => Some([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
}
}
fn lerp_opt_array3(a: Option<[f32; 3]>, b: Option<[f32; 3]>, t: f32) -> Option<[f32; 3]> {
match (a, b) {
(Some(a), Some(b)) => Some([
a[0] + (b[0] - a[0]) * t,
a[1] + (b[1] - a[1]) * t,
a[2] + (b[2] - a[2]) * t,
]),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
}
}
fn lerp_path_data(a: &Option<String>, b: &Option<String>, t: f32) -> Option<String> {
match (a, b) {
(Some(from), Some(to)) => {
crate::morph::interpolate_paths(from, to, t).or_else(|| Some(to.clone()))
}
(Some(s), None) => Some(s.clone()),
(None, Some(s)) => Some(s.clone()),
(None, None) => None,
}
}
fn lerp_opt_array4(a: Option<[f32; 4]>, b: Option<[f32; 4]>, t: f32) -> Option<[f32; 4]> {
match (a, b) {
(Some(a), Some(b)) => Some([
a[0] + (b[0] - a[0]) * t,
a[1] + (b[1] - a[1]) * t,
a[2] + (b[2] - a[2]) * t,
a[3] + (b[3] - a[3]) * t,
]),
(Some(a), None) => Some([
a[0] * (1.0 - t),
a[1] * (1.0 - t),
a[2] * (1.0 - t),
a[3] * (1.0 - t),
]),
(None, Some(b)) => Some([b[0] * t, b[1] * t, b[2] * t, b[3] * t]),
(None, None) => None,
}
}
fn lerp_opt_array8(a: Option<[f32; 8]>, b: Option<[f32; 8]>, t: f32) -> Option<[f32; 8]> {
match (a, b) {
(Some(a), Some(b)) => Some([
a[0] + (b[0] - a[0]) * t,
a[1] + (b[1] - a[1]) * t,
a[2] + (b[2] - a[2]) * t,
a[3] + (b[3] - a[3]) * t,
a[4] + (b[4] - a[4]) * t,
a[5] + (b[5] - a[5]) * t,
a[6] + (b[6] - a[6]) * t,
a[7] + (b[7] - a[7]) * t,
]),
(Some(a), None) => {
let s = 1.0 - t;
Some([
a[0] * s,
a[1] * s,
a[2] * s,
a[3] * s,
a[4] * s,
a[5] * s,
a[6] * s,
a[7] * s,
])
}
(None, Some(b)) => Some([
b[0] * t,
b[1] * t,
b[2] * t,
b[3] * t,
b[4] * t,
b[5] * t,
b[6] * t,
b[7] * t,
]),
(None, None) => None,
}
}
#[derive(Clone, Debug)]
pub struct MultiKeyframe {
pub time: f32,
pub properties: KeyframeProperties,
pub easing: Easing,
}
impl MultiKeyframe {
pub fn new(time: f32, properties: KeyframeProperties, easing: Easing) -> Self {
Self {
time,
properties,
easing,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PlayDirection {
#[default]
Forward,
Reverse,
Alternate,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum FillMode {
#[default]
None,
Forwards,
Backwards,
Both,
}
#[derive(Clone, Debug)]
pub struct MultiKeyframeAnimation {
duration_ms: u32,
keyframes: Vec<MultiKeyframe>,
current_time: f32,
playing: bool,
direction: PlayDirection,
fill_mode: FillMode,
iterations: i32,
current_iteration: i32,
reversed: bool,
delay_ms: u32,
}
impl MultiKeyframeAnimation {
pub fn new(duration_ms: u32) -> Self {
Self {
duration_ms,
keyframes: Vec::new(),
current_time: 0.0,
playing: false,
direction: PlayDirection::Forward,
fill_mode: FillMode::Forwards,
iterations: 1,
current_iteration: 0,
reversed: false,
delay_ms: 0,
}
}
pub fn keyframe(mut self, time: f32, properties: KeyframeProperties, easing: Easing) -> Self {
self.keyframes
.push(MultiKeyframe::new(time, properties, easing));
self.keyframes
.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap());
self
}
pub fn direction(mut self, direction: PlayDirection) -> Self {
self.direction = direction;
self
}
pub fn fill_mode(mut self, fill_mode: FillMode) -> Self {
self.fill_mode = fill_mode;
self
}
pub fn iterations(mut self, count: i32) -> Self {
self.iterations = count;
self
}
pub fn delay(mut self, delay_ms: u32) -> Self {
self.delay_ms = delay_ms;
self
}
pub fn set_direction(&mut self, direction: PlayDirection) {
self.direction = direction;
}
pub fn set_fill_mode(&mut self, fill_mode: FillMode) {
self.fill_mode = fill_mode;
}
pub fn set_iterations(&mut self, count: i32) {
self.iterations = count;
}
pub fn set_delay(&mut self, delay_ms: u32) {
self.delay_ms = delay_ms;
}
pub fn set_reversed(&mut self, reversed: bool) {
self.reversed = reversed;
}
pub fn start(&mut self) {
self.current_time = -(self.delay_ms as f32);
self.current_iteration = 0;
self.reversed = self.direction == PlayDirection::Reverse;
self.playing = true;
}
pub fn stop(&mut self) {
self.playing = false;
}
pub fn is_playing(&self) -> bool {
self.playing
}
pub fn progress(&self) -> f32 {
if self.current_time < 0.0 {
return 0.0;
}
(self.current_time / self.duration_ms as f32).clamp(0.0, 1.0)
}
pub fn current_properties(&self) -> KeyframeProperties {
if self.keyframes.is_empty() {
return KeyframeProperties::default();
}
if self.current_time < 0.0 {
return match self.fill_mode {
FillMode::Backwards | FillMode::Both => self.keyframes[0].properties.clone(),
_ => KeyframeProperties::default(),
};
}
let mut progress = self.progress();
if self.reversed {
progress = 1.0 - progress;
}
let mut prev_kf = &self.keyframes[0];
let mut next_kf = &self.keyframes[0];
for kf in &self.keyframes {
if kf.time <= progress {
prev_kf = kf;
}
if kf.time >= progress {
next_kf = kf;
break;
}
}
if (prev_kf.time - next_kf.time).abs() < f32::EPSILON {
return prev_kf.properties.clone();
}
let local_progress = (progress - prev_kf.time) / (next_kf.time - prev_kf.time);
let eased = next_kf.easing.apply(local_progress);
prev_kf.properties.lerp(&next_kf.properties, eased)
}
pub fn tick(&mut self, dt_ms: f32) {
if !self.playing {
return;
}
self.current_time += dt_ms;
if self.current_time >= self.duration_ms as f32 {
self.current_iteration += 1;
if self.iterations < 0 || self.current_iteration < self.iterations {
self.current_time = 0.0;
if self.direction == PlayDirection::Alternate {
self.reversed = !self.reversed;
}
} else {
self.current_time = self.duration_ms as f32;
self.playing = false;
}
}
}
pub fn duration_ms(&self) -> u32 {
self.duration_ms
}
pub fn delay_ms(&self) -> u32 {
self.delay_ms
}
pub fn total_duration_ms(&self) -> u32 {
self.delay_ms + self.duration_ms
}
pub fn first_keyframe(&self) -> Option<&MultiKeyframe> {
self.keyframes.first()
}
pub fn last_keyframe(&self) -> Option<&MultiKeyframe> {
self.keyframes.last()
}
pub fn keyframes(&self) -> &[MultiKeyframe] {
&self.keyframes
}
pub fn sample_at(&self, progress: f32) -> KeyframeProperties {
if self.keyframes.is_empty() {
return KeyframeProperties::default();
}
let progress = progress.clamp(0.0, 1.0);
let mut prev_kf = &self.keyframes[0];
let mut next_kf = &self.keyframes[0];
for kf in &self.keyframes {
if kf.time <= progress {
prev_kf = kf;
}
if kf.time >= progress {
next_kf = kf;
break;
}
}
if (prev_kf.time - next_kf.time).abs() < f32::EPSILON {
return prev_kf.properties.clone();
}
let local_progress = (progress - prev_kf.time) / (next_kf.time - prev_kf.time);
let eased = next_kf.easing.apply(local_progress);
prev_kf.properties.lerp(&next_kf.properties, eased)
}
}
impl Default for MultiKeyframeAnimation {
fn default() -> Self {
Self::new(300)
}
}
#[derive(Clone, Debug)]
pub struct KeyframePoint {
pub time_ms: u32,
pub value: f32,
pub easing: Easing,
}
#[derive(Clone, Debug)]
pub struct KeyframeTrackBuilder {
points: Vec<KeyframePoint>,
default_easing: Easing,
direction: PlayDirection,
iterations: i32,
delay_ms: u32,
fill_mode: FillMode,
auto_start: bool,
}
impl Default for KeyframeTrackBuilder {
fn default() -> Self {
Self::new()
}
}
impl KeyframeTrackBuilder {
pub fn new() -> Self {
Self {
points: Vec::new(),
default_easing: Easing::Linear,
direction: PlayDirection::Forward,
iterations: 1,
delay_ms: 0,
fill_mode: FillMode::Forwards,
auto_start: false,
}
}
pub fn at(mut self, time_ms: u32, value: f32) -> Self {
self.points.push(KeyframePoint {
time_ms,
value,
easing: self.default_easing,
});
self
}
pub fn at_with_ease(mut self, time_ms: u32, value: f32, easing: Easing) -> Self {
self.points.push(KeyframePoint {
time_ms,
value,
easing,
});
self
}
pub fn ease(mut self, easing: Easing) -> Self {
self.default_easing = easing;
for point in &mut self.points {
point.easing = easing;
}
self
}
pub fn ping_pong(mut self) -> Self {
self.direction = PlayDirection::Alternate;
self
}
pub fn alternate(self) -> Self {
self.ping_pong()
}
pub fn reverse(mut self) -> Self {
self.direction = PlayDirection::Reverse;
self
}
pub fn loop_count(mut self, count: i32) -> Self {
self.iterations = count;
self
}
pub fn loop_infinite(mut self) -> Self {
self.iterations = -1;
self
}
pub fn delay(mut self, delay_ms: u32) -> Self {
self.delay_ms = delay_ms;
self
}
pub fn fill(mut self, fill_mode: FillMode) -> Self {
self.fill_mode = fill_mode;
self
}
pub fn start(mut self) -> Self {
self.auto_start = true;
self
}
pub fn duration_ms(&self) -> u32 {
self.points.iter().map(|p| p.time_ms).max().unwrap_or(0)
}
pub fn build(mut self) -> KeyframeTrack {
self.points.sort_by_key(|p| p.time_ms);
let duration = self.duration_ms();
let keyframes: Vec<Keyframe> = self
.points
.iter()
.map(|p| {
let time = if duration > 0 {
p.time_ms as f32 / duration as f32
} else {
0.0
};
Keyframe {
time,
value: p.value,
easing: p.easing,
}
})
.collect();
let mut animation = KeyframeAnimation::new(duration, keyframes);
if self.auto_start {
animation.start();
}
KeyframeTrack {
animation,
direction: self.direction,
iterations: self.iterations,
current_iteration: 0,
reversed: self.direction == PlayDirection::Reverse,
delay_ms: self.delay_ms,
fill_mode: self.fill_mode,
duration_ms: duration,
}
}
}
#[derive(Clone, Debug)]
pub struct KeyframeTrack {
animation: KeyframeAnimation,
direction: PlayDirection,
iterations: i32,
current_iteration: i32,
reversed: bool,
#[allow(dead_code)] delay_ms: u32,
#[allow(dead_code)] fill_mode: FillMode,
duration_ms: u32,
}
impl KeyframeTrack {
pub fn builder() -> KeyframeTrackBuilder {
KeyframeTrackBuilder::new()
}
pub fn start(&mut self) {
self.current_iteration = 0;
self.reversed = self.direction == PlayDirection::Reverse;
self.animation.start();
}
pub fn stop(&mut self) {
self.animation.stop();
}
pub fn restart(&mut self) {
self.start();
}
pub fn is_playing(&self) -> bool {
self.animation.is_playing() || self.should_continue()
}
fn should_continue(&self) -> bool {
self.iterations < 0 || self.current_iteration < self.iterations
}
pub fn value(&self) -> f32 {
let keyframes = self.animation.keyframes();
if keyframes.is_empty() {
return 0.0;
}
let progress = self.animation.progress().clamp(0.0, 1.0);
let effective_progress = if self.reversed {
1.0 - progress
} else {
progress
};
let mut prev_kf = &keyframes[0];
let mut next_kf = &keyframes[0];
for kf in keyframes {
if kf.time <= effective_progress {
prev_kf = kf;
}
if kf.time >= effective_progress {
next_kf = kf;
break;
}
}
if (prev_kf.time - next_kf.time).abs() < f32::EPSILON {
return prev_kf.value;
}
let local_progress = (effective_progress - prev_kf.time) / (next_kf.time - prev_kf.time);
let eased = next_kf.easing.apply(local_progress);
prev_kf.value + (next_kf.value - prev_kf.value) * eased
}
pub fn progress(&self) -> f32 {
let progress = self.animation.progress();
if self.reversed {
1.0 - progress
} else {
progress
}
}
pub fn tick(&mut self, dt_ms: f32) {
self.animation.tick(dt_ms);
if self.animation.progress() >= 1.0 && !self.animation.is_playing() {
self.current_iteration += 1;
if self.iterations < 0 || self.current_iteration < self.iterations {
self.animation.start();
if self.direction == PlayDirection::Alternate {
self.reversed = !self.reversed;
}
}
}
}
pub fn duration_ms(&self) -> u32 {
self.duration_ms
}
}