leptos_motion_layout/
lib.rs

1//! Leptos Motion Layout
2//!
3//! Layout animation system providing FLIP animations, shared element transitions,
4//! and layout change detection for smooth UI transitions.
5
6#![warn(missing_docs)]
7#![forbid(unsafe_code)]
8
9pub mod flip;
10pub mod layout_tracker;
11pub mod shared_elements;
12pub mod simplified_layout_api;
13
14// Re-export main types
15pub use flip::{EasingFunction, FLIPAnimation, FLIPAnimator, FLIPState, TransformValues};
16pub use layout_tracker::{LayoutChange, LayoutChangeType, LayoutTracker, PerformanceImpact};
17pub use shared_elements::{SharedElementConfig, SharedElementManager, ZIndexStrategy};
18
19// Re-export simplified layout API (new public API)
20pub use simplified_layout_api::{
21    SimplifiedAnimationStatus, SimplifiedEasing, SimplifiedLayoutConfig, SimplifiedLayoutManager,
22    SimplifiedPerformanceMetrics,
23};
24
25/// Layout information for FLIP animations
26#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
27pub struct LayoutInfo {
28    /// X position
29    pub x: f64,
30    /// Y position
31    pub y: f64,
32    /// Width
33    pub width: f64,
34    /// Height
35    pub height: f64,
36}
37
38impl Default for LayoutInfo {
39    fn default() -> Self {
40        Self {
41            x: 0.0,
42            y: 0.0,
43            width: 0.0,
44            height: 0.0,
45        }
46    }
47}
48
49impl LayoutInfo {
50    /// Create new layout info
51    pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
52        Self {
53            x,
54            y,
55            width,
56            height,
57        }
58    }
59
60    /// Create layout info from dimensions
61    pub fn from_dimensions(width: f64, height: f64) -> Self {
62        Self::new(0.0, 0.0, width, height)
63    }
64
65    /// Create layout info from position
66    pub fn from_position(x: f64, y: f64) -> Self {
67        Self::new(x, y, 0.0, 0.0)
68    }
69
70    /// Get area of layout
71    pub fn area(&self) -> f64 {
72        self.width * self.height
73    }
74
75    /// Get center point
76    pub fn center(&self) -> (f64, f64) {
77        (self.x + self.width / 2.0, self.y + self.height / 2.0)
78    }
79
80    /// Check if point is inside layout
81    pub fn contains_point(&self, x: f64, y: f64) -> bool {
82        x >= self.x && x <= (self.x + self.width) && y >= self.y && y <= (self.y + self.height)
83    }
84}
85
86/// Layout animation configuration
87#[derive(Debug, Clone)]
88pub struct LayoutAnimationConfig {
89    /// Whether layout animations are enabled
90    pub enabled: bool,
91    /// Animation duration in seconds
92    pub duration: f64,
93    /// Easing function
94    pub easing: EasingFunction,
95    /// Whether to use hardware acceleration
96    pub hardware_accelerated: bool,
97}
98
99impl Default for LayoutAnimationConfig {
100    fn default() -> Self {
101        Self {
102            enabled: true,
103            duration: 0.3,
104            easing: EasingFunction::default(),
105            hardware_accelerated: true,
106        }
107    }
108}
109
110impl LayoutAnimationConfig {
111    /// Create new config
112    pub fn new() -> Self {
113        Self::default()
114    }
115
116    /// Set duration
117    pub fn with_duration(mut self, duration: f64) -> Self {
118        self.duration = duration;
119        self
120    }
121
122    /// Set easing function
123    pub fn with_easing(mut self, easing: EasingFunction) -> Self {
124        self.easing = easing;
125        self
126    }
127
128    /// Enable hardware acceleration
129    pub fn hardware_accelerated(mut self, enabled: bool) -> Self {
130        self.hardware_accelerated = enabled;
131        self
132    }
133
134    /// Enable/disable animations
135    pub fn enabled(mut self, enabled: bool) -> Self {
136        self.enabled = enabled;
137        self
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_layout_info_new() {
147        let info = LayoutInfo::new(10.0, 20.0, 100.0, 200.0);
148        assert_eq!(info.x, 10.0);
149        assert_eq!(info.y, 20.0);
150        assert_eq!(info.width, 100.0);
151        assert_eq!(info.height, 200.0);
152    }
153
154    #[test]
155    fn test_layout_info_default() {
156        let info = LayoutInfo::default();
157        assert_eq!(info.x, 0.0);
158        assert_eq!(info.y, 0.0);
159        assert_eq!(info.width, 0.0);
160        assert_eq!(info.height, 0.0);
161    }
162
163    #[test]
164    fn test_layout_info_from_dimensions() {
165        let info = LayoutInfo::from_dimensions(100.0, 200.0);
166        assert_eq!(info.x, 0.0);
167        assert_eq!(info.y, 0.0);
168        assert_eq!(info.width, 100.0);
169        assert_eq!(info.height, 200.0);
170    }
171
172    #[test]
173    fn test_layout_info_from_position() {
174        let info = LayoutInfo::from_position(10.0, 20.0);
175        assert_eq!(info.x, 10.0);
176        assert_eq!(info.y, 20.0);
177        assert_eq!(info.width, 0.0);
178        assert_eq!(info.height, 0.0);
179    }
180
181    #[test]
182    fn test_layout_info_area() {
183        let info = LayoutInfo::new(0.0, 0.0, 10.0, 20.0);
184        assert_eq!(info.area(), 200.0);
185    }
186
187    #[test]
188    fn test_layout_info_center() {
189        let info = LayoutInfo::new(10.0, 20.0, 100.0, 200.0);
190        let (cx, cy) = info.center();
191        assert_eq!(cx, 60.0); // 10 + 100/2
192        assert_eq!(cy, 120.0); // 20 + 200/2
193    }
194
195    #[test]
196    fn test_layout_info_contains_point() {
197        let info = LayoutInfo::new(10.0, 20.0, 100.0, 200.0);
198
199        // Inside
200        assert!(info.contains_point(50.0, 100.0));
201        assert!(info.contains_point(10.0, 20.0)); // Top-left corner
202        assert!(info.contains_point(110.0, 220.0)); // Bottom-right corner
203
204        // Outside
205        assert!(!info.contains_point(5.0, 100.0)); // Left of bounds
206        assert!(!info.contains_point(115.0, 100.0)); // Right of bounds
207        assert!(!info.contains_point(50.0, 15.0)); // Above bounds
208        assert!(!info.contains_point(50.0, 225.0)); // Below bounds
209    }
210
211    #[test]
212    fn test_layout_animation_config_default() {
213        let config = LayoutAnimationConfig::default();
214        assert!(config.enabled);
215        assert_eq!(config.duration, 0.3);
216        assert!(config.hardware_accelerated);
217    }
218
219    #[test]
220    fn test_layout_animation_config_new() {
221        let config = LayoutAnimationConfig::new();
222        assert!(config.enabled);
223        assert_eq!(config.duration, 0.3);
224    }
225
226    #[test]
227    fn test_layout_animation_config_builder() {
228        let config = LayoutAnimationConfig::new()
229            .with_duration(0.5)
230            .hardware_accelerated(false)
231            .enabled(false);
232
233        assert!(!config.enabled);
234        assert_eq!(config.duration, 0.5);
235        assert!(!config.hardware_accelerated);
236    }
237
238    #[test]
239    fn test_layout_animation_config_with_easing() {
240        let config = LayoutAnimationConfig::new().with_easing(EasingFunction::Linear);
241
242        match config.easing {
243            EasingFunction::Linear => {}
244            _ => panic!("Expected Linear easing function"),
245        }
246    }
247}
248
249// Include simplified layout tests
250#[cfg(test)]
251mod simplified_layout_tests {
252    include!("simplified_layout_tests.rs");
253}