mod layout;
mod node;
mod reconcile;
pub(crate) use self::layout::measure_animated;
pub use self::node::AnimatedNode;
pub(crate) use self::reconcile::reconcile_animated;
use std::hash::Hash;
use crate::animation::TransitionConfig;
use crate::callback::Callback;
use crate::core::element::{Element, ElementKind};
use crate::layout::hash::LayoutHash;
use crate::style::{Color, LayoutConstraints, Length};
use crate::widgets::Spacer;
#[derive(Clone)]
pub struct Animated {
pub(crate) child: Box<Element>,
pub(crate) opacity: f32,
pub(crate) opacity_fg_only: bool,
pub(crate) opacity_target: Option<Color>,
pub(crate) fg: Option<Color>,
pub(crate) bg: Option<Color>,
pub(crate) transition: TransitionConfig,
pub(crate) height: Option<Length>,
pub(crate) layout_height: Option<Length>,
pub(crate) position_transition: bool,
pub(crate) on_opacity_transition_end: Option<Callback<()>>,
pub(crate) on_height_transition_end: Option<Callback<()>>,
pub(crate) on_position_transition_end: Option<Callback<()>>,
}
impl Default for Animated {
fn default() -> Self {
Self {
child: Box::new(Spacer::new().into()),
opacity: 1.0,
opacity_fg_only: false,
opacity_target: None,
fg: None,
bg: None,
transition: TransitionConfig::default(),
height: None,
layout_height: None,
position_transition: false,
on_opacity_transition_end: None,
on_height_transition_end: None,
on_position_transition_end: None,
}
}
}
impl Animated {
pub fn new(child: impl Into<Element>) -> Self {
Self {
child: Box::new(child.into()),
..Self::default()
}
}
pub fn child(mut self, child: impl Into<Element>) -> Self {
self.child = Box::new(child.into());
self
}
pub fn opacity(mut self, opacity: f32) -> Self {
self.opacity = opacity.clamp(0.0, 1.0);
self
}
pub fn opacity_fg_only(mut self, fg_only: bool) -> Self {
self.opacity_fg_only = fg_only;
self
}
pub fn opacity_target(mut self, color: Color) -> Self {
self.opacity_target = Some(color);
self
}
pub fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
pub fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub fn transition(mut self, transition: TransitionConfig) -> Self {
self.transition = transition;
self
}
pub fn duration(mut self, ms: u64) -> Self {
self.transition.duration = std::time::Duration::from_millis(ms);
self
}
pub fn easing(mut self, easing: crate::animation::Easing) -> Self {
self.transition.easing = easing;
self
}
pub fn height(mut self, height: Length) -> Self {
self.height = Some(height);
self
}
pub fn layout_height(mut self, height: Option<Length>) -> Self {
self.layout_height = height;
self
}
pub fn position_transition(mut self, enabled: bool) -> Self {
self.position_transition = enabled;
self
}
pub fn on_height_transition_end(mut self, cb: Callback<()>) -> Self {
self.on_height_transition_end = Some(cb);
self
}
pub fn on_opacity_transition_end(mut self, cb: Callback<()>) -> Self {
self.on_opacity_transition_end = Some(cb);
self
}
pub fn on_position_transition_end(mut self, cb: Callback<()>) -> Self {
self.on_position_transition_end = Some(cb);
self
}
pub fn exit(mut self, visible: bool, duration_ms: u64) -> Self {
self.opacity = if visible { 1.0 } else { 0.0 };
self.height = Some(if visible { Length::Auto } else { Length::Px(0) });
self.transition.duration = std::time::Duration::from_millis(duration_ms);
self
}
pub fn on_exit_complete(self, cb: Callback<()>) -> Self {
self.on_height_transition_end(cb)
}
}
impl From<Animated> for Element {
fn from(value: Animated) -> Self {
let (min_w, min_h) = measure_animated(&value, None, None);
let mut layout = LayoutConstraints::default().min_width(Length::Px(min_w));
if value.height.is_none() {
layout = layout.min_height(Length::Px(min_h));
}
Element::new(ElementKind::Animated(value)).with_layout(layout)
}
}
impl LayoutHash for Animated {
fn layout_hash(
&self,
hasher: &mut impl std::hash::Hasher,
recurse: &dyn Fn(&Element) -> Option<u64>,
) -> Option<()> {
self.opacity.to_bits().hash(hasher);
self.opacity_fg_only.hash(hasher);
self.opacity_target.hash(hasher);
self.transition.duration.hash(hasher);
self.transition.easing.hash(hasher);
self.height.hash(hasher);
self.layout_height.hash(hasher);
self.position_transition.hash(hasher);
recurse(self.child.as_ref())?.hash(hasher);
Some(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::widgets::Spacer;
#[test]
fn exit_visible_sets_full_opacity_and_auto_height() {
let a = Animated::new(Spacer::new()).exit(true, 200);
assert_eq!(a.opacity, 1.0);
assert_eq!(a.height, Some(Length::Auto));
assert_eq!(a.transition.duration.as_millis(), 200);
}
#[test]
fn exit_hidden_sets_zero_opacity_and_zero_height() {
let a = Animated::new(Spacer::new()).exit(false, 150);
assert_eq!(a.opacity, 0.0);
assert_eq!(a.height, Some(Length::Px(0)));
assert_eq!(a.transition.duration.as_millis(), 150);
}
#[test]
fn on_exit_complete_aliases_height_transition_end() {
let cb = Callback::new(|_: ()| {});
let a = Animated::new(Spacer::new()).on_exit_complete(cb);
assert!(a.on_height_transition_end.is_some());
}
}