use blinc_animation::{AnimatedValue, SchedulerHandle, SpringConfig};
use crate::element::ElementBounds;
#[deprecated(
since = "0.3.0",
note = "Use VisualAnimationConfig from visual_animation module. The old system modifies taffy which causes issues."
)]
#[derive(Clone, Debug)]
pub struct LayoutAnimationConfig {
pub stable_key: Option<String>,
pub height: bool,
pub width: bool,
pub x: bool,
pub y: bool,
pub spring: SpringConfig,
pub threshold: f32,
}
impl Default for LayoutAnimationConfig {
fn default() -> Self {
Self::height()
}
}
impl LayoutAnimationConfig {
pub fn height() -> Self {
Self {
stable_key: None,
height: true,
width: false,
x: false,
y: false,
spring: SpringConfig::snappy(),
threshold: 1.0,
}
}
pub fn width() -> Self {
Self {
stable_key: None,
height: false,
width: true,
x: false,
y: false,
spring: SpringConfig::snappy(),
threshold: 1.0,
}
}
pub fn size() -> Self {
Self {
stable_key: None,
height: true,
width: true,
x: false,
y: false,
spring: SpringConfig::snappy(),
threshold: 1.0,
}
}
pub fn position() -> Self {
Self {
stable_key: None,
height: false,
width: false,
x: true,
y: true,
spring: SpringConfig::snappy(),
threshold: 1.0,
}
}
pub fn all() -> Self {
Self {
stable_key: None,
height: true,
width: true,
x: true,
y: true,
spring: SpringConfig::snappy(),
threshold: 1.0,
}
}
pub fn with_key(mut self, key: impl Into<String>) -> Self {
self.stable_key = Some(key.into());
self
}
pub fn with_spring(mut self, spring: SpringConfig) -> Self {
self.spring = spring;
self
}
pub fn with_threshold(mut self, threshold: f32) -> Self {
self.threshold = threshold;
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 struct LayoutAnimationState {
pub start_bounds: ElementBounds,
pub end_bounds: ElementBounds,
pub height_anim: Option<AnimatedValue>,
pub width_anim: Option<AnimatedValue>,
pub x_anim: Option<AnimatedValue>,
pub y_anim: Option<AnimatedValue>,
}
impl LayoutAnimationState {
pub fn from_bounds_change(
old_bounds: ElementBounds,
new_bounds: ElementBounds,
config: &LayoutAnimationConfig,
scheduler: SchedulerHandle,
) -> Option<Self> {
let height_changed =
config.height && (new_bounds.height - old_bounds.height).abs() > config.threshold;
let width_changed =
config.width && (new_bounds.width - old_bounds.width).abs() > config.threshold;
let x_changed = config.x && (new_bounds.x - old_bounds.x).abs() > config.threshold;
let y_changed = config.y && (new_bounds.y - old_bounds.y).abs() > config.threshold;
if !height_changed && !width_changed && !x_changed && !y_changed {
return None;
}
let height_anim = if height_changed {
let mut anim = AnimatedValue::new(scheduler.clone(), old_bounds.height, config.spring);
anim.set_target(new_bounds.height);
Some(anim)
} else {
None
};
let width_anim = if width_changed {
let mut anim = AnimatedValue::new(scheduler.clone(), old_bounds.width, config.spring);
anim.set_target(new_bounds.width);
Some(anim)
} else {
None
};
let x_anim = if x_changed {
let mut anim = AnimatedValue::new(scheduler.clone(), old_bounds.x, config.spring);
anim.set_target(new_bounds.x);
Some(anim)
} else {
None
};
let y_anim = if y_changed {
let mut anim = AnimatedValue::new(scheduler.clone(), old_bounds.y, config.spring);
anim.set_target(new_bounds.y);
Some(anim)
} else {
None
};
Some(Self {
start_bounds: old_bounds,
end_bounds: new_bounds,
height_anim,
width_anim,
x_anim,
y_anim,
})
}
pub fn update_target(&mut self, new_bounds: ElementBounds, config: &LayoutAnimationConfig) {
self.end_bounds = new_bounds;
if let Some(ref mut anim) = self.height_anim {
if config.height {
anim.set_target(new_bounds.height);
}
}
if let Some(ref mut anim) = self.width_anim {
if config.width {
anim.set_target(new_bounds.width);
}
}
if let Some(ref mut anim) = self.x_anim {
if config.x {
anim.set_target(new_bounds.x);
}
}
if let Some(ref mut anim) = self.y_anim {
if config.y {
anim.set_target(new_bounds.y);
}
}
}
pub fn current_bounds(&self) -> ElementBounds {
ElementBounds {
x: self
.x_anim
.as_ref()
.map(|a| a.get())
.unwrap_or(self.end_bounds.x),
y: self
.y_anim
.as_ref()
.map(|a| a.get())
.unwrap_or(self.end_bounds.y),
width: self
.width_anim
.as_ref()
.map(|a| a.get())
.unwrap_or(self.end_bounds.width),
height: self
.height_anim
.as_ref()
.map(|a| a.get())
.unwrap_or(self.end_bounds.height),
}
}
pub fn is_animating(&self) -> bool {
self.height_anim
.as_ref()
.map(|a| a.is_animating())
.unwrap_or(false)
|| self
.width_anim
.as_ref()
.map(|a| a.is_animating())
.unwrap_or(false)
|| self
.x_anim
.as_ref()
.map(|a| a.is_animating())
.unwrap_or(false)
|| self
.y_anim
.as_ref()
.map(|a| a.is_animating())
.unwrap_or(false)
}
pub fn current_height(&self) -> f32 {
self.height_anim
.as_ref()
.map(|a| a.get())
.unwrap_or(self.end_bounds.height)
}
pub fn current_width(&self) -> f32 {
self.width_anim
.as_ref()
.map(|a| a.get())
.unwrap_or(self.end_bounds.width)
}
pub fn is_width_collapsing(&self) -> bool {
self.width_anim
.as_ref()
.map(|a| a.get() > self.end_bounds.width)
.unwrap_or(false)
}
pub fn is_height_collapsing(&self) -> bool {
self.height_anim
.as_ref()
.map(|a| a.get() > self.end_bounds.height)
.unwrap_or(false)
}
pub fn is_collapsing(&self) -> bool {
self.is_width_collapsing() || self.is_height_collapsing()
}
pub fn layout_constraint_bounds(&self) -> ElementBounds {
let current = self.current_bounds();
ElementBounds {
x: current.x,
y: current.y,
width: current.width.max(self.end_bounds.width),
height: current.height.max(self.end_bounds.height),
}
}
pub fn snap_to_target(&mut self) {
if let Some(ref mut anim) = self.height_anim {
anim.snap_to_target();
}
if let Some(ref mut anim) = self.width_anim {
anim.snap_to_target();
}
if let Some(ref mut anim) = self.x_anim {
anim.snap_to_target();
}
if let Some(ref mut anim) = self.y_anim {
anim.snap_to_target();
}
}
}
pub type LayoutAnimation = LayoutAnimationConfig;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_builders() {
let config = LayoutAnimation::height();
assert!(config.height);
assert!(!config.width);
assert!(!config.x);
assert!(!config.y);
let config = LayoutAnimation::all();
assert!(config.height);
assert!(config.width);
assert!(config.x);
assert!(config.y);
let config = LayoutAnimation::size().with_threshold(2.0);
assert!(config.height);
assert!(config.width);
assert!(!config.x);
assert!(!config.y);
assert_eq!(config.threshold, 2.0);
}
#[test]
fn test_spring_presets() {
let gentle = LayoutAnimation::height().gentle();
let wobbly = LayoutAnimation::height().wobbly();
let stiff = LayoutAnimation::height().stiff();
let snappy = LayoutAnimation::height().snappy();
assert!(gentle.spring.stiffness < stiff.spring.stiffness);
assert!(wobbly.spring.damping < stiff.spring.damping);
assert!(snappy.spring.stiffness > gentle.spring.stiffness);
}
}