1use std::collections::{HashMap, VecDeque};
7use wasm_bindgen::prelude::*;
8use web_sys::Element;
9
10#[derive(Debug, Clone)]
12pub struct SharedElementConfig {
13 pub duration: f64,
15 pub easing: crate::flip::EasingFunction,
17 pub maintain_aspect_ratio: bool,
19 pub hardware_accelerated: bool,
21}
22
23impl Default for SharedElementConfig {
24 fn default() -> Self {
25 Self {
26 duration: 0.3,
27 easing: crate::flip::EasingFunction::EaseOut,
28 maintain_aspect_ratio: false,
29 hardware_accelerated: true,
30 }
31 }
32}
33
34#[derive(Debug, Clone)]
36pub enum ZIndexStrategy {
37 Fixed {
39 base: i32,
41 increment: i32,
43 },
44 Dynamic {
46 base: i32,
48 max: i32,
50 },
51 CustomProperty {
53 property: String,
55 },
56 Elevate,
58 Custom(i32),
60 Maintain,
62}
63
64impl Default for ZIndexStrategy {
65 fn default() -> Self {
66 ZIndexStrategy::Fixed {
67 base: 1000,
68 increment: 1,
69 }
70 }
71}
72
73#[derive(Debug)]
75pub struct SharedElementTransition {
76 pub id: String,
78 pub source_element: Element,
80 pub target_element: Element,
82 pub config: SharedElementConfig,
84 pub progress: f64,
86 pub active: bool,
88 pub start_time: f64,
90}
91
92pub struct SharedElementManager {
94 active_transitions: HashMap<String, SharedElementTransition>,
96 transition_queue: VecDeque<QueuedTransition>,
98 #[allow(dead_code)]
100 z_index_strategy: ZIndexStrategy,
101 performance_metrics: SharedElementMetrics,
103}
104
105#[derive(Debug)]
107pub struct QueuedTransition {
108 pub source_element: Element,
110 pub target_element: Element,
112 pub config: SharedElementConfig,
114 pub priority: TransitionPriority,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
120pub enum TransitionPriority {
121 Low = 0,
123 Normal = 1,
125 High = 2,
127 Critical = 3,
129}
130
131#[derive(Debug, Clone)]
133pub struct TransitionRecord {
134 pub id: String,
136 pub start_time: f64,
138 pub end_time: Option<f64>,
140 pub duration: f64,
142 pub success: bool,
144 pub performance: SharedElementMetrics,
146}
147
148#[derive(Debug, Clone)]
150pub struct SharedElementMetrics {
151 pub total_transitions: usize,
153 pub successful_transitions: usize,
155 pub average_duration: f64,
157 pub frame_rate: f64,
159}
160
161impl SharedElementManager {
162 pub fn new(z_index_strategy: ZIndexStrategy) -> Self {
164 Self {
165 active_transitions: HashMap::new(),
166 transition_queue: VecDeque::new(),
167 z_index_strategy,
168 performance_metrics: SharedElementMetrics::default(),
169 }
170 }
171
172 pub fn register_element(
174 &mut self,
175 _element: &Element,
176 config: &SharedElementConfig,
177 ) -> Result<(), String> {
178 log::info!("Registering shared element with config: {:?}", config);
181
182 Ok(())
183 }
184
185 pub fn unregister_element(&mut self, _element: &Element) -> Result<(), String> {
187 log::info!("Unregistering shared element");
190
191 Ok(())
192 }
193
194 pub fn create_transition(
196 &mut self,
197 source_element: &Element,
198 target_element: &Element,
199 config: &SharedElementConfig,
200 ) -> Result<String, String> {
201 let id = self.generate_transition_id();
203
204 let queued = QueuedTransition {
206 source_element: source_element.clone(),
207 target_element: target_element.clone(),
208 config: config.clone(),
209 priority: TransitionPriority::Normal,
210 };
211
212 self.transition_queue.push_back(queued);
214
215 self.process_transition_queue()?;
217
218 Ok(id)
219 }
220
221 pub fn start_transition(&mut self, transition_id: &str) -> Result<(), String> {
223 if let Some(transition) = self.active_transitions.get_mut(transition_id) {
225 transition.active = true;
226 transition.start_time = js_sys::Date::now();
227
228 let transition_clone = SharedElementTransition {
230 id: transition.id.clone(),
231 source_element: transition.source_element.clone(),
232 target_element: transition.target_element.clone(),
233 config: transition.config.clone(),
234 progress: transition.progress,
235 active: transition.active,
236 start_time: transition.start_time,
237 };
238
239 self.apply_initial_transforms(&transition_clone)?;
241
242 Ok(())
243 } else {
244 Err("Transition not found".to_string())
245 }
246 }
247
248 pub fn update_transition(&mut self, transition_id: &str, progress: f64) -> Result<(), String> {
250 if let Some(transition) = self.active_transitions.get_mut(transition_id) {
251 transition.progress = progress.clamp(0.0, 1.0);
252
253 let transition_clone = SharedElementTransition {
255 id: transition.id.clone(),
256 source_element: transition.source_element.clone(),
257 target_element: transition.target_element.clone(),
258 config: transition.config.clone(),
259 progress: transition.progress,
260 active: transition.active,
261 start_time: transition.start_time,
262 };
263
264 self.apply_transition_transforms(&transition_clone)?;
266
267 if progress >= 1.0 {
269 self.complete_transition(transition_id)?;
270 }
271
272 Ok(())
273 } else {
274 Err("Transition not found".to_string())
275 }
276 }
277
278 pub fn cancel_transition(&mut self, transition_id: &str) -> Result<(), String> {
280 if let Some(transition) = self.active_transitions.remove(transition_id) {
281 self.reset_element_transforms(&transition.source_element)?;
283 self.reset_element_transforms(&transition.target_element)?;
284
285 self.performance_metrics.total_transitions += 1;
287
288 Ok(())
289 } else {
290 Err("Transition not found".to_string())
291 }
292 }
293
294 pub fn cancel_all_transitions(&mut self) {
296 self.active_transitions.clear();
297 self.transition_queue.clear();
298 }
299
300 fn process_transition_queue(&mut self) -> Result<(), String> {
302 while let Some(queued) = self.transition_queue.pop_front() {
303 let transition = SharedElementTransition {
305 id: self.generate_transition_id(),
306 source_element: queued.source_element.clone(),
307 target_element: queued.target_element.clone(),
308 config: queued.config.clone(),
309 progress: 0.0,
310 active: false,
311 start_time: 0.0,
312 };
313
314 let id = transition.id.clone();
316 self.active_transitions.insert(id.clone(), transition);
317
318 self.start_transition(&id)?;
320 }
321
322 Ok(())
323 }
324
325 fn apply_initial_transforms(&self, transition: &SharedElementTransition) -> Result<(), String> {
327 self.setup_transition_element(&transition.source_element, &transition.config)?;
329 self.setup_transition_element(&transition.target_element, &transition.config)?;
330
331 log::info!(
332 "Applied initial transforms for transition {}",
333 transition.id
334 );
335
336 Ok(())
337 }
338
339 fn apply_transition_transforms(
341 &self,
342 transition: &SharedElementTransition,
343 ) -> Result<(), String> {
344 let progress = transition.progress;
346 let inverse_progress = 1.0 - progress;
347
348 if let Some(source_html) = transition.source_element.dyn_ref::<web_sys::HtmlElement>() {
350 let style = source_html.style();
351 let opacity = inverse_progress;
352 style
353 .set_property("opacity", &opacity.to_string())
354 .map_err(|_| "Failed to set source opacity")?;
355 }
356
357 if let Some(target_html) = transition.target_element.dyn_ref::<web_sys::HtmlElement>() {
358 let style = target_html.style();
359 let opacity = progress;
360 style
361 .set_property("opacity", &opacity.to_string())
362 .map_err(|_| "Failed to set target opacity")?;
363 }
364
365 log::info!(
366 "Applied transition transforms for {} at {}%",
367 transition.id,
368 (progress * 100.0) as i32
369 );
370
371 Ok(())
372 }
373
374 fn setup_transition_element(
376 &self,
377 element: &Element,
378 _config: &SharedElementConfig,
379 ) -> Result<(), String> {
380 if let Some(html_element) = element.dyn_ref::<web_sys::HtmlElement>() {
381 let style = html_element.style();
382 style
383 .set_property("will-change", "transform, opacity")
384 .map_err(|_| "Failed to set will-change")?;
385
386 style
388 .set_property("z-index", "9999")
389 .map_err(|_| "Failed to set elevated z-index")?;
390 } else {
391 return Err("Element is not an HtmlElement".to_string());
392 }
393
394 Ok(())
395 }
396
397 fn complete_transition(&mut self, transition_id: &str) -> Result<(), String> {
399 if let Some(transition) = self.active_transitions.remove(transition_id) {
400 self.reset_element_transforms(&transition.source_element)?;
402 self.reset_element_transforms(&transition.target_element)?;
403
404 let duration = js_sys::Date::now() - transition.start_time;
406 self.update_performance_metrics(duration);
407
408 Ok(())
409 } else {
410 Err("Transition not found".to_string())
411 }
412 }
413
414 fn reset_element_transforms(&self, element: &Element) -> Result<(), String> {
416 if let Some(html_element) = element.dyn_ref::<web_sys::HtmlElement>() {
418 let style = html_element.style();
419 style
420 .remove_property("transform")
421 .map_err(|_| "Failed to remove transform")?;
422 style
423 .remove_property("will-change")
424 .map_err(|_| "Failed to remove will-change")?;
425 style
426 .remove_property("z-index")
427 .map_err(|_| "Failed to remove z-index")?;
428 } else {
429 return Err("Element is not an HtmlElement".to_string());
430 }
431
432 log::info!("Reset element transforms");
433 Ok(())
434 }
435
436 fn generate_transition_id(&self) -> String {
438 format!("shared_trans_{}", js_sys::Date::now())
439 }
440
441 fn update_performance_metrics(&mut self, duration: f64) {
443 self.performance_metrics.total_transitions += 1;
444 self.performance_metrics.successful_transitions += 1;
445
446 let total = self.performance_metrics.total_transitions as f64;
448 let current_avg = self.performance_metrics.average_duration;
449 self.performance_metrics.average_duration =
450 (current_avg * (total - 1.0) + duration) / total;
451 }
452
453 pub fn get_performance_metrics(&self) -> &SharedElementMetrics {
455 &self.performance_metrics
456 }
457
458 pub fn get_active_transition_count(&self) -> usize {
460 self.active_transitions.len()
461 }
462
463 pub fn get_queued_transition_count(&self) -> usize {
465 self.transition_queue.len()
466 }
467}
468
469impl Default for SharedElementManager {
470 fn default() -> Self {
471 Self::new(ZIndexStrategy::default())
472 }
473}
474
475impl Default for SharedElementMetrics {
476 fn default() -> Self {
477 Self {
478 total_transitions: 0,
479 successful_transitions: 0,
480 average_duration: 0.0,
481 frame_rate: 60.0,
482 }
483 }
484}
485
486impl Default for TransitionPriority {
487 fn default() -> Self {
488 Self::Normal
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495 use wasm_bindgen_test::*;
496
497 wasm_bindgen_test_configure!(run_in_browser);
498
499 #[test]
500 fn test_shared_element_config_default() {
501 let config = SharedElementConfig::default();
502 assert_eq!(config.duration, 0.3);
503 assert!(matches!(
504 config.easing,
505 crate::flip::EasingFunction::EaseOut
506 ));
507 assert!(!config.maintain_aspect_ratio);
508 assert!(config.hardware_accelerated);
509 }
510
511 #[test]
512 fn test_z_index_strategy_fixed() {
513 let strategy = ZIndexStrategy::Fixed {
514 base: 1000,
515 increment: 10,
516 };
517 match strategy {
518 ZIndexStrategy::Fixed { base, increment } => {
519 assert_eq!(base, 1000);
520 assert_eq!(increment, 10);
521 }
522 _ => panic!("Expected Fixed strategy"),
523 }
524 }
525
526 #[test]
527 fn test_z_index_strategy_dynamic() {
528 let strategy = ZIndexStrategy::Dynamic {
529 base: 500,
530 max: 2000,
531 };
532 match strategy {
533 ZIndexStrategy::Dynamic { base, max } => {
534 assert_eq!(base, 500);
535 assert_eq!(max, 2000);
536 }
537 _ => panic!("Expected Dynamic strategy"),
538 }
539 }
540
541 #[test]
542 fn test_z_index_strategy_custom_property() {
543 let strategy = ZIndexStrategy::CustomProperty {
544 property: "z-index".to_string(),
545 };
546 match strategy {
547 ZIndexStrategy::CustomProperty { property } => {
548 assert_eq!(property, "z-index");
549 }
550 _ => panic!("Expected CustomProperty strategy"),
551 }
552 }
553
554 #[test]
555 fn test_z_index_strategy_elevate() {
556 let strategy = ZIndexStrategy::Elevate;
557 match strategy {
558 ZIndexStrategy::Elevate => {}
559 _ => panic!("Expected Elevate strategy"),
560 }
561 }
562
563 #[test]
564 fn test_z_index_strategy_custom() {
565 let strategy = ZIndexStrategy::Custom(5000);
566 match strategy {
567 ZIndexStrategy::Custom(value) => assert_eq!(value, 5000),
568 _ => panic!("Expected Custom strategy"),
569 }
570 }
571
572 #[test]
573 fn test_z_index_strategy_maintain() {
574 let strategy = ZIndexStrategy::Maintain;
575 match strategy {
576 ZIndexStrategy::Maintain => {}
577 _ => panic!("Expected Maintain strategy"),
578 }
579 }
580
581 #[test]
582 fn test_z_index_strategy_default() {
583 let strategy = ZIndexStrategy::default();
584 match strategy {
585 ZIndexStrategy::Fixed { base, increment } => {
586 assert_eq!(base, 1000);
587 assert_eq!(increment, 1);
588 }
589 _ => panic!("Expected default Fixed strategy"),
590 }
591 }
592
593 #[test]
594 fn test_transition_priority_low() {
595 let priority = TransitionPriority::Low;
596 assert_eq!(priority as i32, 0);
597 }
598
599 #[test]
600 fn test_transition_priority_normal() {
601 let priority = TransitionPriority::Normal;
602 assert_eq!(priority as i32, 1);
603 }
604
605 #[test]
606 fn test_transition_priority_high() {
607 let priority = TransitionPriority::High;
608 assert_eq!(priority as i32, 2);
609 }
610
611 #[test]
612 fn test_transition_priority_critical() {
613 let priority = TransitionPriority::Critical;
614 assert_eq!(priority as i32, 3);
615 }
616
617 #[test]
618 fn test_transition_priority_default() {
619 let priority = TransitionPriority::default();
620 assert_eq!(priority, TransitionPriority::Normal);
621 }
622
623 #[test]
624 fn test_shared_element_metrics_default() {
625 let metrics = SharedElementMetrics::default();
626 assert_eq!(metrics.total_transitions, 0);
627 assert_eq!(metrics.successful_transitions, 0);
628 assert_eq!(metrics.average_duration, 0.0);
629 assert_eq!(metrics.frame_rate, 60.0);
630 }
631
632 #[test]
633 fn test_transition_record_creation() {
634 let record = TransitionRecord {
635 id: "test_trans_123".to_string(),
636 start_time: 1000.0,
637 end_time: Some(1300.0),
638 duration: 0.3,
639 success: true,
640 performance: SharedElementMetrics::default(),
641 };
642
643 assert_eq!(record.id, "test_trans_123");
644 assert_eq!(record.start_time, 1000.0);
645 assert_eq!(record.end_time, Some(1300.0));
646 assert_eq!(record.duration, 0.3);
647 assert!(record.success);
648 }
649
650 #[wasm_bindgen_test]
651 fn test_shared_element_manager_creation() {
652 let manager = SharedElementManager::new(ZIndexStrategy::default());
653 assert_eq!(manager.get_active_transition_count(), 0);
654 assert_eq!(manager.get_queued_transition_count(), 0);
655 }
656
657 #[wasm_bindgen_test]
658 fn test_shared_element_manager_with_fixed_strategy() {
659 let strategy = ZIndexStrategy::Fixed {
660 base: 1000,
661 increment: 10,
662 };
663 let manager = SharedElementManager::new(strategy);
664 assert_eq!(manager.get_active_transition_count(), 0);
665 }
666
667 #[wasm_bindgen_test]
668 fn test_shared_element_manager_with_dynamic_strategy() {
669 let strategy = ZIndexStrategy::Dynamic {
670 base: 500,
671 max: 2000,
672 };
673 let manager = SharedElementManager::new(strategy);
674 assert_eq!(manager.get_active_transition_count(), 0);
675 }
676
677 #[wasm_bindgen_test]
678 fn test_transition_priority_ordering() {
679 assert!(TransitionPriority::Low < TransitionPriority::Normal);
680 assert!(TransitionPriority::Normal < TransitionPriority::High);
681 assert!(TransitionPriority::High < TransitionPriority::Critical);
682 }
683
684 #[wasm_bindgen_test]
685 fn test_transition_id_generation() {
686 let manager = SharedElementManager::new(ZIndexStrategy::default());
687 let id1 = manager.generate_transition_id();
688 let id2 = manager.generate_transition_id();
689
690 assert_ne!(id1, id2);
691 assert!(id1.starts_with("shared_trans_"));
692 assert!(id2.starts_with("shared_trans_"));
693 }
694
695 #[wasm_bindgen_test]
696 fn test_manager_cancel_all_transitions() {
697 let mut manager = SharedElementManager::new(ZIndexStrategy::default());
698 manager.cancel_all_transitions();
699 assert_eq!(manager.get_active_transition_count(), 0);
700 assert_eq!(manager.get_queued_transition_count(), 0);
701 }
702}