leptos_motion_layout/
simplified_layout_api.rs

1//! Simplified Layout/Scroll API
2//!
3//! This module provides a simplified, user-friendly layout/scroll API
4//! that hides complexity and provides a clean interface.
5
6use crate::*;
7use std::collections::HashMap;
8use std::time::Instant;
9use web_sys::{DomRect, Element};
10
11/// Mock layout tracker for non-WASM targets to avoid WASM function access issues
12#[cfg(not(target_arch = "wasm32"))]
13struct MockLayoutTracker;
14
15#[cfg(not(target_arch = "wasm32"))]
16impl MockLayoutTracker {
17    fn new() -> Self {
18        Self
19    }
20
21    fn track_element(&self, _element: Element) -> Result<(), String> {
22        Ok(())
23    }
24
25    fn untrack_element(&self, _element_id: &str) -> Result<(), String> {
26        Ok(())
27    }
28}
29
30/// Mock FLIP animator for non-WASM targets
31#[cfg(not(target_arch = "wasm32"))]
32struct MockFLIPAnimator;
33
34#[cfg(not(target_arch = "wasm32"))]
35impl MockFLIPAnimator {
36    fn new() -> Self {
37        Self
38    }
39
40    fn animate(
41        &self,
42        _element_id: String,
43        _element: Element,
44        _from_rect: DomRect,
45        _to_rect: DomRect,
46        _config: LayoutAnimationConfig,
47    ) -> Result<(), String> {
48        Ok(())
49    }
50}
51
52/// Mock shared element manager for non-WASM targets
53#[cfg(not(target_arch = "wasm32"))]
54struct MockSharedElementManager;
55
56#[cfg(not(target_arch = "wasm32"))]
57impl MockSharedElementManager {
58    fn new(_strategy: ZIndexStrategy) -> Self {
59        Self
60    }
61
62    fn start_transition(&self, _transition_id: &str) -> Result<(), String> {
63        Ok(())
64    }
65}
66
67/// Simplified easing functions
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum SimplifiedEasing {
70    /// Linear easing
71    Linear,
72    /// Ease in
73    EaseIn,
74    /// Ease out
75    EaseOut,
76    /// Ease in out
77    EaseInOut,
78    /// Ease in cubic
79    EaseInCubic,
80    /// Ease out cubic
81    EaseOutCubic,
82    /// Ease in out cubic
83    EaseInOutCubic,
84}
85
86/// Simplified layout configuration
87#[derive(Debug, Clone, PartialEq)]
88pub struct SimplifiedLayoutConfig {
89    /// Animation duration in seconds
90    pub duration: f64,
91    /// Easing function
92    pub easing: SimplifiedEasing,
93    /// Whether to use hardware acceleration
94    pub hardware_accelerated: bool,
95    /// Whether to enable FLIP animations
96    pub enable_flip: bool,
97    /// Whether to enable shared element transitions
98    pub enable_shared_elements: bool,
99}
100
101/// Simplified animation status
102#[derive(Debug, Clone, PartialEq)]
103pub struct SimplifiedAnimationStatus {
104    /// Whether animation is currently running
105    pub is_animating: bool,
106    /// Whether animation is paused
107    pub is_paused: bool,
108    /// Animation progress (0.0 to 1.0)
109    pub progress: f64,
110    /// Animation start time
111    pub start_time: Option<Instant>,
112    /// Animation duration
113    pub duration: f64,
114}
115
116/// Simplified performance metrics
117#[derive(Debug, Clone, PartialEq)]
118pub struct SimplifiedPerformanceMetrics {
119    /// Total number of animations
120    pub total_animations: usize,
121    /// Average animation duration
122    pub average_duration: f64,
123    /// Frame rate during animations
124    pub frame_rate: f64,
125    /// Memory usage in bytes
126    pub memory_usage: usize,
127    /// CPU usage percentage
128    pub cpu_usage: f64,
129}
130
131/// Simplified layout manager that provides a clean, simple interface
132///
133/// This is the main public API for layout animations. It provides
134/// a clean, simple interface while hiding the complexity of the
135/// underlying layout tracking and animation systems.
136pub struct SimplifiedLayoutManager {
137    /// Internal layout tracker (hidden from public API)
138    /// Uses different implementations for WASM vs native targets
139    #[cfg(target_arch = "wasm32")]
140    internal_tracker: LayoutTracker,
141    #[cfg(not(target_arch = "wasm32"))]
142    internal_tracker: MockLayoutTracker,
143    /// Internal FLIP animator (hidden from public API)
144    #[cfg(target_arch = "wasm32")]
145    internal_flip_animator: FLIPAnimator,
146    #[cfg(not(target_arch = "wasm32"))]
147    internal_flip_animator: MockFLIPAnimator,
148    /// Internal shared element manager (hidden from public API)
149    #[cfg(target_arch = "wasm32")]
150    internal_shared_manager: SharedElementManager,
151    #[cfg(not(target_arch = "wasm32"))]
152    internal_shared_manager: MockSharedElementManager,
153    /// Current configuration
154    config: SimplifiedLayoutConfig,
155    /// Tracked elements
156    tracked_elements: HashMap<String, Element>,
157    /// Active animations
158    active_animations: HashMap<String, SimplifiedAnimationStatus>,
159    /// Performance metrics
160    performance_metrics: SimplifiedPerformanceMetrics,
161}
162
163impl SimplifiedLayoutManager {
164    /// Create a new simplified layout manager with default configuration
165    pub fn new() -> Self {
166        Self::with_config(SimplifiedLayoutConfig::default())
167    }
168
169    /// Create a new simplified layout manager with custom configuration
170    pub fn with_config(config: SimplifiedLayoutConfig) -> Self {
171        #[cfg(target_arch = "wasm32")]
172        {
173            Self {
174                internal_tracker: LayoutTracker::new(),
175                internal_flip_animator: FLIPAnimator::new(),
176                internal_shared_manager: SharedElementManager::new(ZIndexStrategy::default()),
177                config,
178                tracked_elements: HashMap::new(),
179                active_animations: HashMap::new(),
180                performance_metrics: SimplifiedPerformanceMetrics {
181                    total_animations: 0,
182                    average_duration: 0.0,
183                    frame_rate: 60.0,
184                    memory_usage: 0,
185                    cpu_usage: 0.0,
186                },
187            }
188        }
189        #[cfg(not(target_arch = "wasm32"))]
190        {
191            Self {
192                internal_tracker: MockLayoutTracker::new(),
193                internal_flip_animator: MockFLIPAnimator::new(),
194                internal_shared_manager: MockSharedElementManager::new(ZIndexStrategy::default()),
195                config,
196                tracked_elements: HashMap::new(),
197                active_animations: HashMap::new(),
198                performance_metrics: SimplifiedPerformanceMetrics {
199                    total_animations: 0,
200                    average_duration: 0.0,
201                    frame_rate: 60.0,
202                    memory_usage: 0,
203                    cpu_usage: 0.0,
204                },
205            }
206        }
207    }
208
209    /// Start tracking an element for layout changes
210    pub fn start_tracking(&mut self, element_id: &str, element: &Element) -> Result<(), String> {
211        if self.tracked_elements.contains_key(element_id) {
212            return Err(format!("Element '{}' is already being tracked", element_id));
213        }
214
215        self.tracked_elements
216            .insert(element_id.to_string(), element.clone());
217        let _ = self.internal_tracker.track_element(element.clone());
218
219        Ok(())
220    }
221
222    /// Stop tracking an element
223    pub fn stop_tracking(&mut self, element_id: &str) -> Result<(), String> {
224        if !self.tracked_elements.contains_key(element_id) {
225            return Err(format!("Element '{}' is not being tracked", element_id));
226        }
227
228        self.tracked_elements.remove(element_id);
229        let _ = self.internal_tracker.untrack_element(element_id);
230        self.active_animations.remove(element_id);
231
232        Ok(())
233    }
234
235    /// Animate layout change for an element
236    pub fn animate_layout_change(
237        &mut self,
238        element_id: &str,
239        from_layout: &LayoutInfo,
240        to_layout: &LayoutInfo,
241    ) -> Result<(), String> {
242        if !self.tracked_elements.contains_key(element_id) {
243            return Err(format!("Element '{}' is not being tracked", element_id));
244        }
245
246        let element = self.tracked_elements.get(element_id).unwrap();
247
248        // Create FLIP animation using the correct API
249        let from_rect = web_sys::DomRect::new_with_x_and_y_and_width_and_height(
250            from_layout.x,
251            from_layout.y,
252            from_layout.width,
253            from_layout.height,
254        )
255        .unwrap();
256        let to_rect = web_sys::DomRect::new_with_x_and_y_and_width_and_height(
257            to_layout.x,
258            to_layout.y,
259            to_layout.width,
260            to_layout.height,
261        )
262        .unwrap();
263
264        let animation_config = LayoutAnimationConfig {
265            enabled: true,
266            duration: self.config.duration,
267            easing: self.convert_easing(self.config.easing),
268            hardware_accelerated: self.config.hardware_accelerated,
269        };
270
271        let _ = self.internal_flip_animator.animate(
272            element_id.to_string(),
273            element.clone(),
274            from_rect,
275            to_rect,
276            animation_config,
277        );
278
279        // Update animation status
280        self.active_animations.insert(
281            element_id.to_string(),
282            SimplifiedAnimationStatus {
283                is_animating: true,
284                is_paused: false,
285                progress: 0.0,
286                start_time: Some(Instant::now()),
287                duration: self.config.duration,
288            },
289        );
290
291        // Update performance metrics
292        self.performance_metrics.total_animations += 1;
293
294        Ok(())
295    }
296
297    /// Perform FLIP animation
298    pub fn flip_animate(
299        &mut self,
300        element_id: &str,
301        from_layout: &LayoutInfo,
302        to_layout: &LayoutInfo,
303    ) -> Result<(), String> {
304        if !self.config.enable_flip {
305            return Err("FLIP animations are disabled".to_string());
306        }
307
308        self.animate_layout_change(element_id, from_layout, to_layout)
309    }
310
311    /// Perform shared element transition
312    pub fn shared_element_transition(
313        &mut self,
314        from_element_id: &str,
315        to_element_id: &str,
316        _from_layout: &LayoutInfo,
317        _to_layout: &LayoutInfo,
318    ) -> Result<(), String> {
319        if !self.config.enable_shared_elements {
320            return Err("Shared element transitions are disabled".to_string());
321        }
322
323        if !self.tracked_elements.contains_key(from_element_id) {
324            return Err(format!(
325                "Element '{}' is not being tracked",
326                from_element_id
327            ));
328        }
329
330        if !self.tracked_elements.contains_key(to_element_id) {
331            return Err(format!("Element '{}' is not being tracked", to_element_id));
332        }
333
334        let _from_element = self.tracked_elements.get(from_element_id).unwrap();
335        let _to_element = self.tracked_elements.get(to_element_id).unwrap();
336
337        // Create shared element transition using the correct API
338        let _config = SharedElementConfig {
339            maintain_aspect_ratio: true,
340            hardware_accelerated: self.config.hardware_accelerated,
341            duration: self.config.duration,
342            easing: self.convert_easing(self.config.easing),
343        };
344
345        let _ = self
346            .internal_shared_manager
347            .start_transition("shared-transition");
348
349        // Update animation status for both elements
350        let status = SimplifiedAnimationStatus {
351            is_animating: true,
352            is_paused: false,
353            progress: 0.0,
354            start_time: Some(Instant::now()),
355            duration: self.config.duration,
356        };
357
358        self.active_animations
359            .insert(from_element_id.to_string(), status.clone());
360        self.active_animations
361            .insert(to_element_id.to_string(), status);
362
363        // Update performance metrics
364        self.performance_metrics.total_animations += 1;
365
366        Ok(())
367    }
368
369    /// Batch start tracking multiple elements
370    pub fn batch_start_tracking(&mut self, elements: Vec<(&str, &Element)>) -> Result<(), String> {
371        for (element_id, element) in elements {
372            self.start_tracking(element_id, element)?;
373        }
374        Ok(())
375    }
376
377    /// Batch animate multiple elements
378    pub fn batch_animate(
379        &mut self,
380        animations: Vec<(&str, &LayoutInfo, &LayoutInfo)>,
381    ) -> Result<(), String> {
382        for (element_id, from_layout, to_layout) in animations {
383            self.animate_layout_change(element_id, from_layout, to_layout)?;
384        }
385        Ok(())
386    }
387
388    /// Get layout information for an element
389    pub fn get_layout_info(&self, element_id: &str) -> Option<LayoutInfo> {
390        if let Some(element) = self.tracked_elements.get(element_id) {
391            let rect = element.get_bounding_client_rect();
392            Some(LayoutInfo::new(
393                rect.left(),
394                rect.top(),
395                rect.width(),
396                rect.height(),
397            ))
398        } else {
399            None
400        }
401    }
402
403    /// Get animation status for an element
404    pub fn get_animation_status(&self, element_id: &str) -> Option<&SimplifiedAnimationStatus> {
405        self.active_animations.get(element_id)
406    }
407
408    /// Pause animation for an element
409    pub fn pause_animation(&mut self, element_id: &str) -> Result<(), String> {
410        if let Some(status) = self.active_animations.get_mut(element_id) {
411            status.is_paused = true;
412            Ok(())
413        } else {
414            Err(format!("No active animation for element '{}'", element_id))
415        }
416    }
417
418    /// Resume animation for an element
419    pub fn resume_animation(&mut self, element_id: &str) -> Result<(), String> {
420        if let Some(status) = self.active_animations.get_mut(element_id) {
421            status.is_paused = false;
422            Ok(())
423        } else {
424            Err(format!("No active animation for element '{}'", element_id))
425        }
426    }
427
428    /// Cancel animation for an element
429    pub fn cancel_animation(&mut self, element_id: &str) -> Result<(), String> {
430        if let Some(status) = self.active_animations.get_mut(element_id) {
431            status.is_animating = false;
432            status.is_paused = false;
433            status.progress = 0.0;
434            status.start_time = None;
435            Ok(())
436        } else {
437            Err(format!("No active animation for element '{}'", element_id))
438        }
439    }
440
441    /// Clear all tracking and animations
442    pub fn clear_all(&mut self) {
443        self.tracked_elements.clear();
444        self.active_animations.clear();
445        // Clear tracking and animations (methods may not exist, so we'll handle gracefully)
446        // self.internal_tracker.clear_all();
447        // self.internal_flip_animator.clear_all();
448        // self.internal_shared_manager.clear_all();
449    }
450
451    /// Get performance metrics
452    pub fn get_performance_metrics(&self) -> Option<SimplifiedPerformanceMetrics> {
453        Some(self.performance_metrics.clone())
454    }
455
456    /// Update configuration
457    pub fn update_config(&mut self, config: SimplifiedLayoutConfig) {
458        self.config = config;
459    }
460
461    /// Get current configuration
462    pub fn get_config(&self) -> SimplifiedLayoutConfig {
463        self.config.clone()
464    }
465
466    /// Check if any elements are being tracked
467    pub fn is_tracking(&self) -> bool {
468        !self.tracked_elements.is_empty()
469    }
470
471    /// Get number of tracked elements
472    pub fn tracked_count(&self) -> usize {
473        self.tracked_elements.len()
474    }
475
476    /// Get number of active animations
477    pub fn animation_count(&self) -> usize {
478        self.active_animations
479            .values()
480            .filter(|s| s.is_animating)
481            .count()
482    }
483
484    /// Convert simplified easing to internal easing
485    fn convert_easing(&self, easing: SimplifiedEasing) -> EasingFunction {
486        match easing {
487            SimplifiedEasing::Linear => EasingFunction::Linear,
488            SimplifiedEasing::EaseIn => EasingFunction::EaseIn,
489            SimplifiedEasing::EaseOut => EasingFunction::EaseOut,
490            SimplifiedEasing::EaseInOut => EasingFunction::EaseInOut,
491            SimplifiedEasing::EaseInCubic => EasingFunction::CubicBezier(0.55, 0.055, 0.675, 0.19),
492            SimplifiedEasing::EaseOutCubic => EasingFunction::CubicBezier(0.215, 0.61, 0.355, 1.0),
493            SimplifiedEasing::EaseInOutCubic => {
494                EasingFunction::CubicBezier(0.645, 0.045, 0.355, 1.0)
495            }
496        }
497    }
498}
499
500impl Default for SimplifiedLayoutManager {
501    fn default() -> Self {
502        Self::new()
503    }
504}
505
506impl Clone for SimplifiedLayoutManager {
507    fn clone(&self) -> Self {
508        #[cfg(target_arch = "wasm32")]
509        {
510            Self {
511                internal_tracker: LayoutTracker::new(),
512                internal_flip_animator: FLIPAnimator::new(),
513                internal_shared_manager: SharedElementManager::new(ZIndexStrategy::default()),
514                config: self.config.clone(),
515                tracked_elements: HashMap::new(), // Can't clone DOM elements
516                active_animations: HashMap::new(),
517                performance_metrics: self.performance_metrics.clone(),
518            }
519        }
520        #[cfg(not(target_arch = "wasm32"))]
521        {
522            Self {
523                internal_tracker: MockLayoutTracker::new(),
524                internal_flip_animator: MockFLIPAnimator::new(),
525                internal_shared_manager: MockSharedElementManager::new(ZIndexStrategy::default()),
526                config: self.config.clone(),
527                tracked_elements: HashMap::new(), // Can't clone DOM elements
528                active_animations: HashMap::new(),
529                performance_metrics: self.performance_metrics.clone(),
530            }
531        }
532    }
533}
534
535impl std::fmt::Debug for SimplifiedLayoutManager {
536    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
537        f.debug_struct("SimplifiedLayoutManager")
538            .field("is_tracking", &self.is_tracking())
539            .field("tracked_count", &self.tracked_count())
540            .field("animation_count", &self.animation_count())
541            .finish()
542    }
543}
544
545impl Default for SimplifiedLayoutConfig {
546    fn default() -> Self {
547        Self {
548            duration: 0.3,
549            easing: SimplifiedEasing::EaseInOut,
550            hardware_accelerated: true,
551            enable_flip: true,
552            enable_shared_elements: true,
553        }
554    }
555}
556
557impl SimplifiedLayoutConfig {
558    /// Create new simplified layout config
559    pub fn new() -> Self {
560        Self::default()
561    }
562
563    /// Set animation duration
564    pub fn duration(mut self, duration: f64) -> Self {
565        self.duration = duration;
566        self
567    }
568
569    /// Set easing function
570    pub fn easing(mut self, easing: SimplifiedEasing) -> Self {
571        self.easing = easing;
572        self
573    }
574
575    /// Set hardware acceleration
576    pub fn hardware_accelerated(mut self, enabled: bool) -> Self {
577        self.hardware_accelerated = enabled;
578        self
579    }
580
581    /// Enable/disable FLIP animations
582    pub fn enable_flip(mut self, enabled: bool) -> Self {
583        self.enable_flip = enabled;
584        self
585    }
586
587    /// Enable/disable shared element transitions
588    pub fn enable_shared_elements(mut self, enabled: bool) -> Self {
589        self.enable_shared_elements = enabled;
590        self
591    }
592}
593
594#[cfg(test)]
595mod tests {
596    use super::*;
597
598    #[test]
599    fn test_simplified_layout_manager_creation() {
600        let manager = SimplifiedLayoutManager::new();
601        assert!(!manager.is_tracking());
602        assert_eq!(manager.tracked_count(), 0);
603        assert_eq!(manager.animation_count(), 0);
604    }
605
606    #[test]
607    fn test_simplified_layout_manager_with_config() {
608        let config = SimplifiedLayoutConfig::new()
609            .duration(0.5)
610            .easing(SimplifiedEasing::EaseOut);
611
612        let manager = SimplifiedLayoutManager::with_config(config);
613        assert!(!manager.is_tracking());
614        assert_eq!(manager.tracked_count(), 0);
615    }
616
617    #[test]
618    fn test_simplified_layout_config_fluent_api() {
619        let config = SimplifiedLayoutConfig::new()
620            .duration(0.5)
621            .easing(SimplifiedEasing::EaseOut)
622            .hardware_accelerated(false)
623            .enable_flip(false)
624            .enable_shared_elements(false);
625
626        assert_eq!(config.duration, 0.5);
627        assert_eq!(config.easing, SimplifiedEasing::EaseOut);
628        assert!(!config.hardware_accelerated);
629        assert!(!config.enable_flip);
630        assert!(!config.enable_shared_elements);
631    }
632
633    #[test]
634    fn test_simplified_layout_manager_clone() {
635        let manager1 = SimplifiedLayoutManager::new();
636        let manager2 = manager1.clone();
637
638        assert_eq!(manager1.is_tracking(), manager2.is_tracking());
639        assert_eq!(manager1.tracked_count(), manager2.tracked_count());
640        assert_eq!(manager1.animation_count(), manager2.animation_count());
641    }
642
643    #[test]
644    fn test_simplified_layout_manager_debug() {
645        let manager = SimplifiedLayoutManager::new();
646        let debug_str = format!("{:?}", manager);
647        assert!(debug_str.contains("SimplifiedLayoutManager"));
648        assert!(debug_str.contains("is_tracking"));
649        assert!(debug_str.contains("tracked_count"));
650        assert!(debug_str.contains("animation_count"));
651    }
652}