use blinc_animation::{AnimatedValue, Easing, SchedulerHandle, SpringConfig};
pub struct AnimationBuilder {
handle: Option<SchedulerHandle>,
opacity: Option<(f32, f32)>,
scale: Option<(f32, f32)>,
translate_x: Option<(f32, f32)>,
translate_y: Option<(f32, f32)>,
rotate: Option<(f32, f32)>,
spring_config: SpringConfig,
duration_ms: Option<u32>,
easing: Easing,
auto_start: bool,
}
impl Default for AnimationBuilder {
fn default() -> Self {
Self::new()
}
}
impl AnimationBuilder {
pub fn new() -> Self {
Self {
handle: None,
opacity: None,
scale: None,
translate_x: None,
translate_y: None,
rotate: None,
spring_config: SpringConfig::stiff(),
duration_ms: None,
easing: Easing::EaseInOut,
auto_start: true,
}
}
pub fn with_handle(mut self, handle: SchedulerHandle) -> Self {
self.handle = Some(handle);
self
}
pub fn opacity(mut self, from: f32, to: f32) -> Self {
self.opacity = Some((from, to));
self
}
pub fn scale(mut self, from: f32, to: f32) -> Self {
self.scale = Some((from, to));
self
}
pub fn translate_x(mut self, from: f32, to: f32) -> Self {
self.translate_x = Some((from, to));
self
}
pub fn translate_y(mut self, from: f32, to: f32) -> Self {
self.translate_y = Some((from, to));
self
}
pub fn rotate(mut self, from: f32, to: f32) -> Self {
self.rotate = Some((from, to));
self
}
pub fn rotate_deg(self, from: f32, to: f32) -> Self {
let to_rad = |deg: f32| deg * std::f32::consts::PI / 180.0;
self.rotate(to_rad(from), to_rad(to))
}
pub fn with_spring(mut self, config: SpringConfig) -> Self {
self.spring_config = config;
self.duration_ms = None; self
}
pub fn gentle(self) -> Self {
self.with_spring(SpringConfig::gentle())
}
pub fn wobbly(self) -> Self {
self.with_spring(SpringConfig::wobbly())
}
pub fn stiff(self) -> Self {
self.with_spring(SpringConfig::stiff())
}
pub fn snappy(self) -> Self {
self.with_spring(SpringConfig::snappy())
}
pub fn with_duration(mut self, duration_ms: u32) -> Self {
self.duration_ms = Some(duration_ms);
self
}
pub fn with_easing(mut self, easing: Easing) -> Self {
self.easing = easing;
self
}
pub fn paused(mut self) -> Self {
self.auto_start = false;
self
}
pub fn get_opacity(&self) -> Option<f32> {
self.opacity.map(|(from, _)| from)
}
pub fn get_scale(&self) -> Option<f32> {
self.scale.map(|(from, _)| from)
}
pub fn get_translate_x(&self) -> Option<f32> {
self.translate_x.map(|(from, _)| from)
}
pub fn get_translate_y(&self) -> Option<f32> {
self.translate_y.map(|(from, _)| from)
}
pub fn get_rotate(&self) -> Option<f32> {
self.rotate.map(|(from, _)| from)
}
}
#[derive(Clone)]
pub struct AnimatedProperties {
pub opacity: Option<AnimatedValue>,
pub scale: Option<AnimatedValue>,
pub translate_x: Option<AnimatedValue>,
pub translate_y: Option<AnimatedValue>,
pub rotate: Option<AnimatedValue>,
}
impl AnimatedProperties {
pub fn from_builder(builder: &AnimationBuilder, handle: SchedulerHandle) -> Self {
let config = builder.spring_config;
let opacity = builder.opacity.map(|(from, to)| {
let mut anim = AnimatedValue::new(handle.clone(), from, config);
if builder.auto_start {
anim.set_target(to);
}
anim
});
let scale = builder.scale.map(|(from, to)| {
let mut anim = AnimatedValue::new(handle.clone(), from, config);
if builder.auto_start {
anim.set_target(to);
}
anim
});
let translate_x = builder.translate_x.map(|(from, to)| {
let mut anim = AnimatedValue::new(handle.clone(), from, config);
if builder.auto_start {
anim.set_target(to);
}
anim
});
let translate_y = builder.translate_y.map(|(from, to)| {
let mut anim = AnimatedValue::new(handle.clone(), from, config);
if builder.auto_start {
anim.set_target(to);
}
anim
});
let rotate = builder.rotate.map(|(from, to)| {
let mut anim = AnimatedValue::new(handle.clone(), from, config);
if builder.auto_start {
anim.set_target(to);
}
anim
});
Self {
opacity,
scale,
translate_x,
translate_y,
rotate,
}
}
pub fn opacity(&self) -> f32 {
self.opacity.as_ref().map(|a| a.get()).unwrap_or(1.0)
}
pub fn scale(&self) -> f32 {
self.scale.as_ref().map(|a| a.get()).unwrap_or(1.0)
}
pub fn translate_x(&self) -> f32 {
self.translate_x.as_ref().map(|a| a.get()).unwrap_or(0.0)
}
pub fn translate_y(&self) -> f32 {
self.translate_y.as_ref().map(|a| a.get()).unwrap_or(0.0)
}
pub fn rotate(&self) -> f32 {
self.rotate.as_ref().map(|a| a.get()).unwrap_or(0.0)
}
pub fn is_animating(&self) -> bool {
self.opacity
.as_ref()
.map(|a| a.is_animating())
.unwrap_or(false)
|| self
.scale
.as_ref()
.map(|a| a.is_animating())
.unwrap_or(false)
|| self
.translate_x
.as_ref()
.map(|a| a.is_animating())
.unwrap_or(false)
|| self
.translate_y
.as_ref()
.map(|a| a.is_animating())
.unwrap_or(false)
|| self
.rotate
.as_ref()
.map(|a| a.is_animating())
.unwrap_or(false)
}
}
use crate::div::Div;
impl Div {
pub fn animate<F>(self, f: F) -> Self
where
F: FnOnce(AnimationBuilder) -> AnimationBuilder,
{
let builder = f(AnimationBuilder::new());
let mut result = self;
if let Some(opacity) = builder.get_opacity() {
result = result.opacity(opacity);
}
if let Some(scale) = builder.get_scale() {
result = result.scale(scale);
}
let tx = builder.get_translate_x().unwrap_or(0.0);
let ty = builder.get_translate_y().unwrap_or(0.0);
if tx != 0.0 || ty != 0.0 {
result = result.translate(tx, ty);
}
if let Some(rot) = builder.get_rotate() {
result = result.rotate(rot);
}
result
}
pub fn with_animated<F>(self, anim: &AnimatedValue, f: F) -> Self
where
F: FnOnce(Self, f32) -> Self,
{
f(self, anim.get())
}
pub fn apply_animations(self, props: &AnimatedProperties) -> Self {
let mut result = self.opacity(props.opacity());
let scale = props.scale();
if (scale - 1.0).abs() > 0.001 {
result = result.scale(scale);
}
let tx = props.translate_x();
let ty = props.translate_y();
if tx.abs() > 0.001 || ty.abs() > 0.001 {
result = result.translate(tx, ty);
}
let rot = props.rotate();
if rot.abs() > 0.001 {
result = result.rotate(rot);
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_animation_builder() {
let builder = AnimationBuilder::new()
.opacity(0.0, 1.0)
.scale(0.5, 1.0)
.wobbly();
assert_eq!(builder.get_opacity(), Some(0.0));
assert_eq!(builder.get_scale(), Some(0.5));
}
#[test]
fn test_div_animate() {
use crate::div::div;
let d = div()
.w(100.0)
.animate(|a| a.opacity(0.5, 1.0).scale(0.8, 1.0));
}
}