waterui_core/
animation.rs

1//! # `WaterUI` Animation System
2//!
3//! A reactive animation system that seamlessly integrates with `WaterUI`'s reactive state management.
4//!
5//! ## Overview
6//!
7//! The `WaterUI` animation system leverages the reactive framework to create smooth, declarative
8//! animations that automatically run when reactive values change. By attaching animation metadata
9//! to reactive values through convenient extension methods, the system can intelligently
10//! determine how to animate between different states without requiring explicit animation code.
11//!
12//! ```text
13//! ┌───────────────────┐      ┌───────────────────┐      ┌───────────────────┐
14//! │  Reactive Values  │─────>│ Change Propagation│─────>│  Animation System │
15//! │  (Binding/Compute)│      │ (With Animations) │      │  (Renderer)       │
16//! └───────────────────┘      └───────────────────┘      └───────────────────┘
17//! ```
18//!
19//! ## Core Concepts
20//!
21//! ### Animation Extension Methods
22//!
23//! `WaterUI` provides convenient extension methods on all reactive types to easily attach
24//! animation configurations:
25//!
26//! ```rust
27//! use waterui_core::{animation::Animation, AnimationExt, SignalExt};
28//! use nami::binding;
29//! use core::time::Duration;
30//!
31//! let opacity: nami::Binding<f32> = binding(1.0);
32//!
33//! // Use the .animated() method to apply default animation
34//! let _animated_opacity = opacity.clone().animated();
35//!
36//! // Or specify a specific animation type
37//! let faded: nami::Binding<f32> = binding(0.0);
38//! let _custom_animated =
39//!     faded.with_animation(Animation::ease_in_out(Duration::from_millis(300)));
40//! ```
41//!
42//! The system supports various animation types:
43//!
44//! - **`Linear`**: Constant velocity from start to finish
45//! - **`EaseIn`**: Starts slow and accelerates
46//! - **`EaseOut`**: Starts fast and decelerates
47//! - **`EaseInOut`**: Combines ease-in and ease-out for natural movement
48//! - **`Spring`**: Physics-based animation with configurable stiffness and damping
49//!
50//! ### Integration with UI Components
51//!
52//! UI components automatically respect animation metadata when rendering:
53//!
54//! ```rust
55//! use waterui_core::{animation::Animation, AnimationExt, SignalExt};
56//! use nami::binding;
57//! use core::time::Duration;
58//!
59//! let scale: nami::Binding<f32> = binding(1.0);
60//!
61//! // Three different ways to animate properties:
62//!
63//! // 1. Default animation (uses system defaults)
64//! let _view1_scale = scale.clone().animated();
65//!
66//! // 2. Custom animation using convenience methods
67//! let expanded: nami::Binding<f32> = binding(2.0);
68//! let _view2_scale =
69//!     expanded.with_animation(Animation::ease_in_out(Duration::from_millis(300)));
70//!
71//! // 3. Spring animation using the convenience method
72//! let bouncing: nami::Binding<f32> = binding(0.5);
73//! let _view3_scale = bouncing.with_animation(Animation::spring(100.0, 10.0));
74//! ```
75//!
76//! ## Animation Pipeline
77//!
78//! 1. **Reactive Setup**: Reactive values are wrapped with animation metadata using extension methods
79//! 2. **State Change**: When the underlying value changes, the animation information is preserved
80//! 3. **Propagation**: The change and animation details are propagated through the reactive system
81//! 4. **Value Interpolation**: The renderer calculates intermediate values based on animation type
82//! 5. **Rendering**: The UI is continuously updated with interpolated values until animation completes
83//!
84//! ## Advanced Features
85//!
86//! ### Animation Choreography
87//!
88//! Complex animations can be created by coordinating multiple animated values:
89//!
90//! ```rust
91//! use waterui_core::{animation::Animation, AnimationExt, SignalExt};
92//! use nami::binding;
93//! use core::time::Duration;
94//!
95//! let opacity: nami::Binding<f32> = binding(0.0);
96//! let position: nami::Binding<(f32, f32)> = binding((0.0, 0.0));
97//!
98//! // Create a choreographed animation sequence
99//! let animated_opacity =
100//!     opacity.with_animation(Animation::ease_in_out(Duration::from_millis(300)));
101//!
102//! // Position animates with a spring physics model
103//! let animated_position = position.with_animation(Animation::spring(100.0, 10.0));
104//!
105//! // Both animated values can be used in views
106//! // The UI framework will automatically handle the animation timing
107//! drop((animated_opacity, animated_position));
108//! ```
109//!
110//! ### Composition with Other Reactive Features
111//!
112//! Animation metadata seamlessly composes with other reactive features:
113//!
114//! ```rust
115//! use waterui_core::{animation::Animation, AnimationExt, SignalExt};
116//! use nami::binding;
117//! use core::time::Duration;
118//!
119//! let count: nami::Binding<i32> = binding(0i32);
120//! let value1: nami::Binding<i32> = binding(1i32);
121//! let value2: nami::Binding<i32> = binding(2i32);
122//!
123//! // Combine mapping and animation
124//! let opacity = count
125//!     .map(|n: i32| if n > 5 { 1.0 } else { 0.5 })
126//!     .animated();  // Apply animation to the mapped result
127//!
128//! // Combine multiple reactive values with animation
129//! let combined = value1
130//!     .zip(value2)
131//!     .map(|(a, b)| a + b)
132//!     .with_animation(Animation::ease_in_out(Duration::from_millis(250)));
133//!
134//! drop((opacity, combined)); // Prevent unused variable warnings
135//! ```
136//!
137
138use core::time::Duration;
139
140/// An enumeration representing different types of animations
141///
142/// This enum provides various animation types for UI elements or graphics:
143/// - `Linear`: Constant speed from start to finish
144/// - `EaseIn`: Starts slow and accelerates
145/// - `EaseOut`: Starts fast and decelerates
146/// - `EaseInOut`: Starts and ends slowly with acceleration in the middle
147/// - `Spring`: Physics-based movement with configurable stiffness and damping
148///
149/// Each animation type (except Spring) takes a Duration parameter that specifies
150/// how long the animation should take to complete.
151#[derive(Debug, Default, Clone, PartialEq)]
152#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
153pub enum Animation {
154    /// Default animation behavior (uses system defaults)
155    #[default]
156    Default,
157    /// Linear animation with constant velocity
158    Linear(Duration),
159    /// Ease-in animation that starts slow and accelerates
160    EaseIn(Duration),
161    /// Ease-out animation that starts fast and decelerates
162    EaseOut(Duration),
163    /// Ease-in-out animation that starts and ends slowly with acceleration in the middle
164    EaseInOut(Duration),
165    /// Spring animation with physics-based movement
166    Spring {
167        /// Stiffness of the spring (higher values create faster animations)
168        stiffness: f32,
169        /// Damping factor to control oscillation (higher values reduce bouncing)
170        damping: f32,
171    },
172}
173
174impl Animation {
175    /// Creates a new Linear animation with the specified duration
176    ///
177    /// This is an ergonomic constructor that accepts any type that can be converted
178    /// into a Duration (such as u64 milliseconds, etc.)
179    ///
180    /// # Examples
181    ///
182    /// ```
183    /// use waterui_core::animation::Animation;
184    /// use core::time::Duration;
185    ///
186    /// let animation = Animation::linear(Duration::from_millis(300)); // 300ms
187    /// let animation = Animation::linear(Duration::from_secs(1)); // 1 second
188    /// ```
189    pub fn linear(duration: impl Into<Duration>) -> Self {
190        Self::Linear(duration.into())
191    }
192
193    /// Creates a new ease-in animation with the specified duration
194    ///
195    /// This is an ergonomic constructor that accepts any type that can be converted
196    /// into a Duration (such as u64 milliseconds, etc.)
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// use waterui_core::animation::Animation;
202    /// use core::time::Duration;
203    ///
204    /// let animation = Animation::ease_in(Duration::from_millis(300)); // 300ms
205    /// let animation = Animation::ease_in(Duration::from_secs(1)); // 1 second
206    /// ```
207    pub fn ease_in(duration: impl Into<Duration>) -> Self {
208        Self::EaseIn(duration.into())
209    }
210
211    /// Creates a new ease-out animation with the specified duration
212    ///
213    /// This is an ergonomic constructor that accepts any type that can be converted
214    /// into a Duration (such as u64 milliseconds, etc.)
215    ///
216    /// # Examples
217    ///
218    /// ```
219    /// use waterui_core::animation::Animation;
220    /// use core::time::Duration;
221    ///
222    /// let animation = Animation::ease_out(Duration::from_millis(300)); // 300ms
223    /// let animation = Animation::ease_out(Duration::from_secs(1)); // 1 second
224    /// ```
225    pub fn ease_out(duration: impl Into<Duration>) -> Self {
226        Self::EaseOut(duration.into())
227    }
228
229    /// Creates a new ease-in-out animation with the specified duration
230    ///
231    /// This is an ergonomic constructor that accepts any type that can be converted
232    /// into a Duration (such as u64 milliseconds, etc.)
233    ///
234    /// # Examples
235    ///
236    /// ```
237    /// use waterui_core::animation::Animation;
238    /// use core::time::Duration;
239    ///
240    /// let animation = Animation::ease_in_out(Duration::from_millis(300)); // 300ms
241    /// let animation = Animation::ease_in_out(Duration::from_secs(1)); // 1 second
242    /// ```
243    pub fn ease_in_out(duration: impl Into<Duration>) -> Self {
244        Self::EaseInOut(duration.into())
245    }
246
247    /// Creates a new Spring animation with the specified stiffness and damping
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// use waterui_core::animation::Animation;
253    ///
254    /// let animation = Animation::spring(100.0, 10.0);
255    /// ```
256    #[must_use]
257    pub const fn spring(stiffness: f32, damping: f32) -> Self {
258        Self::Spring { stiffness, damping }
259    }
260}
261
262use nami::signal::WithMetadata;
263
264/// Extension trait providing animation methods for reactive values
265pub trait AnimationExt: nami::SignalExt {
266    /// Apply default animation to this reactive value
267    ///
268    /// Uses a reasonable default animation (ease-in-out with 250ms duration)
269    fn animated(self) -> WithMetadata<Self, Animation>
270    where
271        Self: Sized,
272    {
273        self.with(Animation::ease_in_out(Duration::from_millis(250)))
274    }
275
276    /// Apply a specific animation to this reactive value
277    ///
278    /// # Arguments
279    ///
280    /// * `animation` - The animation to apply
281    fn with_animation(self, animation: Animation) -> WithMetadata<Self, Animation>
282    where
283        Self: Sized,
284    {
285        self.with(animation)
286    }
287}
288
289// Implement AnimationExt for all types that implement SignalExt
290impl<S: nami::SignalExt> AnimationExt for S {}