Skip to main content

presentar_core/
runtime.rs

1//! Command runtime for executing side effects.
2//!
3//! This module provides the infrastructure to execute `Command` values
4//! produced by state updates.
5
6use crate::state::Command;
7use std::collections::HashMap;
8use std::sync::{Arc, Mutex};
9
10/// Router trait for navigation commands.
11pub trait Router: Send + Sync {
12    /// Navigate to a route.
13    fn navigate(&self, route: &str);
14
15    /// Get the current route.
16    fn current_route(&self) -> String;
17}
18
19/// Storage trait for state persistence commands.
20pub trait Storage: Send + Sync {
21    /// Save data to storage.
22    fn save(&self, key: &str, data: &[u8]);
23
24    /// Load data from storage.
25    fn load(&self, key: &str) -> Option<Vec<u8>>;
26
27    /// Remove data from storage.
28    fn remove(&self, key: &str);
29
30    /// Check if a key exists.
31    fn contains(&self, key: &str) -> bool;
32}
33
34/// In-memory storage for testing.
35#[derive(Debug, Default)]
36pub struct MemoryStorage {
37    data: Mutex<HashMap<String, Vec<u8>>>,
38}
39
40impl MemoryStorage {
41    /// Create a new empty memory storage.
42    #[must_use]
43    pub fn new() -> Self {
44        Self::default()
45    }
46
47    /// Get the number of stored items.
48    #[must_use]
49    pub fn len(&self) -> usize {
50        self.data
51            .lock()
52            .expect("MemoryStorage mutex poisoned")
53            .len()
54    }
55
56    /// Check if storage is empty.
57    #[must_use]
58    pub fn is_empty(&self) -> bool {
59        self.data
60            .lock()
61            .expect("MemoryStorage mutex poisoned")
62            .is_empty()
63    }
64
65    /// Clear all stored data.
66    pub fn clear(&self) {
67        self.data
68            .lock()
69            .expect("MemoryStorage mutex poisoned")
70            .clear();
71    }
72}
73
74impl Storage for MemoryStorage {
75    fn save(&self, key: &str, data: &[u8]) {
76        self.data
77            .lock()
78            .expect("MemoryStorage mutex poisoned")
79            .insert(key.to_string(), data.to_vec());
80    }
81
82    fn load(&self, key: &str) -> Option<Vec<u8>> {
83        self.data
84            .lock()
85            .expect("MemoryStorage mutex poisoned")
86            .get(key)
87            .cloned()
88    }
89
90    fn remove(&self, key: &str) {
91        self.data
92            .lock()
93            .expect("MemoryStorage mutex poisoned")
94            .remove(key);
95    }
96
97    fn contains(&self, key: &str) -> bool {
98        self.data
99            .lock()
100            .expect("MemoryStorage mutex poisoned")
101            .contains_key(key)
102    }
103}
104
105/// In-memory router for testing.
106#[derive(Debug)]
107pub struct MemoryRouter {
108    route: Mutex<String>,
109    history: Mutex<Vec<String>>,
110}
111
112impl Default for MemoryRouter {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118impl MemoryRouter {
119    /// Create a new memory router.
120    #[must_use]
121    pub fn new() -> Self {
122        Self {
123            route: Mutex::new("/".to_string()),
124            history: Mutex::new(vec!["/".to_string()]),
125        }
126    }
127
128    /// Get navigation history.
129    #[must_use]
130    pub fn history(&self) -> Vec<String> {
131        self.history
132            .lock()
133            .expect("MemoryRouter mutex poisoned")
134            .clone()
135    }
136
137    /// Get history length.
138    #[must_use]
139    pub fn history_len(&self) -> usize {
140        self.history
141            .lock()
142            .expect("MemoryRouter mutex poisoned")
143            .len()
144    }
145}
146
147impl Router for MemoryRouter {
148    fn navigate(&self, route: &str) {
149        let mut current = self.route.lock().expect("MemoryRouter mutex poisoned");
150        *current = route.to_string();
151        self.history
152            .lock()
153            .expect("MemoryRouter mutex poisoned")
154            .push(route.to_string());
155    }
156
157    fn current_route(&self) -> String {
158        self.route
159            .lock()
160            .expect("MemoryRouter mutex poisoned")
161            .clone()
162    }
163}
164
165/// Result of command execution.
166#[derive(Debug)]
167pub enum ExecutionResult<M> {
168    /// No result (`Command::None` or non-message-producing commands)
169    None,
170    /// A single message was produced
171    Message(M),
172    /// Multiple messages were produced
173    Messages(Vec<M>),
174    /// Command is pending (async)
175    Pending,
176}
177
178impl<M> ExecutionResult<M> {
179    /// Check if the result has no messages.
180    #[must_use]
181    pub const fn is_none(&self) -> bool {
182        matches!(self, Self::None)
183    }
184
185    /// Check if there are messages.
186    #[must_use]
187    pub const fn has_messages(&self) -> bool {
188        matches!(self, Self::Message(_) | Self::Messages(_))
189    }
190
191    /// Get messages as a vector.
192    pub fn into_messages(self) -> Vec<M> {
193        match self {
194            Self::None | Self::Pending => vec![],
195            Self::Message(m) => vec![m],
196            Self::Messages(ms) => ms,
197        }
198    }
199}
200
201/// Command executor configuration.
202pub struct ExecutorConfig<R, S> {
203    /// Router for navigation commands
204    pub router: Arc<R>,
205    /// Storage for persistence commands
206    pub storage: Arc<S>,
207}
208
209impl<R, S> std::fmt::Debug for ExecutorConfig<R, S> {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        f.debug_struct("ExecutorConfig")
212            .field("router", &std::any::type_name::<R>())
213            .field("storage", &std::any::type_name::<S>())
214            .finish()
215    }
216}
217
218impl<R, S> std::fmt::Debug for CommandExecutor<R, S> {
219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220        f.debug_struct("CommandExecutor")
221            .field("config", &self.config)
222            .finish()
223    }
224}
225
226impl<R: Router, S: Storage> ExecutorConfig<R, S> {
227    /// Create a new executor config.
228    pub fn new(router: R, storage: S) -> Self {
229        Self {
230            router: Arc::new(router),
231            storage: Arc::new(storage),
232        }
233    }
234}
235
236/// Command executor for synchronous commands.
237///
238/// Note: Task commands require async execution and return `ExecutionResult::Pending`.
239pub struct CommandExecutor<R, S> {
240    config: ExecutorConfig<R, S>,
241}
242
243impl<R: Router, S: Storage> CommandExecutor<R, S> {
244    /// Create a new command executor.
245    pub const fn new(config: ExecutorConfig<R, S>) -> Self {
246        Self { config }
247    }
248
249    /// Execute a command synchronously.
250    ///
251    /// For async Task commands, this returns `ExecutionResult::Pending`.
252    /// Use `execute_blocking` to block on async tasks.
253    pub fn execute<M: Send>(&self, command: Command<M>) -> ExecutionResult<M> {
254        match command {
255            Command::None => ExecutionResult::None,
256            Command::Batch(commands) => {
257                let mut messages = Vec::new();
258                for cmd in commands {
259                    match self.execute(cmd) {
260                        ExecutionResult::None | ExecutionResult::Pending => {}
261                        ExecutionResult::Message(m) => messages.push(m),
262                        ExecutionResult::Messages(ms) => messages.extend(ms),
263                    }
264                }
265                if messages.is_empty() {
266                    ExecutionResult::None
267                } else {
268                    ExecutionResult::Messages(messages)
269                }
270            }
271            Command::Task(_) => {
272                // Async tasks can't be executed synchronously
273                ExecutionResult::Pending
274            }
275            Command::Navigate { route } => {
276                self.config.router.navigate(&route);
277                ExecutionResult::None
278            }
279            Command::SaveState { key } => {
280                // SaveState requires the actual state to be passed
281                // This is a limitation - we'd need state access
282                // For now, just record that we tried to save
283                // In practice, the runtime would have state access
284                let _ = key;
285                ExecutionResult::None
286            }
287            Command::LoadState { key, on_load } => {
288                let data = self.config.storage.load(&key);
289                let message = on_load(data);
290                ExecutionResult::Message(message)
291            }
292        }
293    }
294
295    /// Get the router.
296    pub fn router(&self) -> &R {
297        &self.config.router
298    }
299
300    /// Get the storage.
301    pub fn storage(&self) -> &S {
302        &self.config.storage
303    }
304}
305
306/// Create a default executor with memory-based backends.
307#[must_use]
308pub fn default_executor() -> CommandExecutor<MemoryRouter, MemoryStorage> {
309    CommandExecutor::new(ExecutorConfig::new(
310        MemoryRouter::new(),
311        MemoryStorage::new(),
312    ))
313}
314
315// =============================================================================
316// Focus Management
317// =============================================================================
318
319/// Focus direction for keyboard navigation.
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
321pub enum FocusDirection {
322    /// Move focus forward (Tab)
323    Forward,
324    /// Move focus backward (Shift+Tab)
325    Backward,
326    /// Move focus up (Arrow Up)
327    Up,
328    /// Move focus down (Arrow Down)
329    Down,
330    /// Move focus left (Arrow Left)
331    Left,
332    /// Move focus right (Arrow Right)
333    Right,
334}
335
336/// Manages keyboard focus for widgets.
337#[derive(Debug, Default)]
338pub struct FocusManager {
339    /// Currently focused widget ID
340    focused: Option<u64>,
341    /// Focus ring (ordered list of focusable widget IDs)
342    focus_ring: Vec<u64>,
343    /// Focus trap stack (for modals/dialogs)
344    traps: Vec<FocusTrap>,
345}
346
347/// A focus trap that restricts focus to a subset of widgets.
348#[derive(Debug)]
349pub struct FocusTrap {
350    /// Widget IDs in this trap
351    pub widget_ids: Vec<u64>,
352    /// Initial focused widget when trap was created
353    pub initial_focus: Option<u64>,
354}
355
356impl FocusManager {
357    /// Create a new focus manager.
358    #[must_use]
359    pub fn new() -> Self {
360        Self::default()
361    }
362
363    /// Set the focus ring (ordered list of focusable widgets).
364    pub fn set_focus_ring(&mut self, widget_ids: Vec<u64>) {
365        self.focus_ring = widget_ids;
366    }
367
368    /// Get the currently focused widget ID.
369    #[must_use]
370    pub const fn focused(&self) -> Option<u64> {
371        self.focused
372    }
373
374    /// Set focus to a specific widget.
375    pub fn focus(&mut self, widget_id: u64) -> bool {
376        let available = self.available_focus_ring();
377        if available.contains(&widget_id) {
378            self.focused = Some(widget_id);
379            true
380        } else {
381            false
382        }
383    }
384
385    /// Clear focus.
386    pub fn blur(&mut self) {
387        self.focused = None;
388    }
389
390    /// Move focus in a direction.
391    pub fn move_focus(&mut self, direction: FocusDirection) -> Option<u64> {
392        let ring = self.available_focus_ring();
393        if ring.is_empty() {
394            return None;
395        }
396
397        let current_idx = self
398            .focused
399            .and_then(|f| ring.iter().position(|&id| id == f));
400
401        let next_idx = match direction {
402            FocusDirection::Forward | FocusDirection::Down | FocusDirection::Right => {
403                match current_idx {
404                    Some(idx) => (idx + 1) % ring.len(),
405                    None => 0,
406                }
407            }
408            FocusDirection::Backward | FocusDirection::Up | FocusDirection::Left => {
409                match current_idx {
410                    Some(0) | None => ring.len() - 1,
411                    Some(idx) => idx - 1,
412                }
413            }
414        };
415
416        let next_id = ring[next_idx];
417        self.focused = Some(next_id);
418        Some(next_id)
419    }
420
421    /// Push a focus trap (for modals/dialogs).
422    pub fn push_trap(&mut self, widget_ids: Vec<u64>) {
423        let initial = self.focused;
424        self.traps.push(FocusTrap {
425            widget_ids,
426            initial_focus: initial,
427        });
428        // Focus first item in trap
429        if let Some(first) = self.available_focus_ring().first().copied() {
430            self.focused = Some(first);
431        }
432    }
433
434    /// Pop the current focus trap.
435    pub fn pop_trap(&mut self) -> Option<FocusTrap> {
436        let trap = self.traps.pop();
437        // Restore previous focus
438        if let Some(ref t) = trap {
439            self.focused = t.initial_focus;
440        }
441        trap
442    }
443
444    /// Check if focus is currently trapped.
445    #[must_use]
446    pub fn is_trapped(&self) -> bool {
447        !self.traps.is_empty()
448    }
449
450    /// Get the available focus ring (respecting traps).
451    fn available_focus_ring(&self) -> Vec<u64> {
452        if let Some(trap) = self.traps.last() {
453            trap.widget_ids.clone()
454        } else {
455            self.focus_ring.clone()
456        }
457    }
458
459    /// Check if a widget is focusable.
460    #[must_use]
461    pub fn is_focusable(&self, widget_id: u64) -> bool {
462        self.available_focus_ring().contains(&widget_id)
463    }
464}
465
466// =============================================================================
467// Animation & Timer System
468// =============================================================================
469
470/// Easing functions for smooth animations.
471#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
472pub enum EasingFunction {
473    /// Linear interpolation (no easing)
474    #[default]
475    Linear,
476    /// Quadratic ease in
477    EaseInQuad,
478    /// Quadratic ease out
479    EaseOutQuad,
480    /// Quadratic ease in-out
481    EaseInOutQuad,
482    /// Cubic ease in
483    EaseInCubic,
484    /// Cubic ease out
485    EaseOutCubic,
486    /// Cubic ease in-out
487    EaseInOutCubic,
488    /// Elastic ease out (spring-like)
489    EaseOutElastic,
490    /// Bounce ease out
491    EaseOutBounce,
492}
493
494impl EasingFunction {
495    /// Apply the easing function to a normalized time value (0.0 to 1.0).
496    #[must_use]
497    #[allow(clippy::suboptimal_flops)]
498    pub fn apply(self, t: f32) -> f32 {
499        let t = t.clamp(0.0, 1.0);
500        match self {
501            Self::Linear => t,
502            Self::EaseInQuad => t * t,
503            Self::EaseOutQuad => 1.0 - (1.0 - t) * (1.0 - t),
504            Self::EaseInOutQuad => {
505                if t < 0.5 {
506                    2.0 * t * t
507                } else {
508                    1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
509                }
510            }
511            Self::EaseInCubic => t * t * t,
512            Self::EaseOutCubic => 1.0 - (1.0 - t).powi(3),
513            Self::EaseInOutCubic => {
514                if t < 0.5 {
515                    4.0 * t * t * t
516                } else {
517                    1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
518                }
519            }
520            Self::EaseOutElastic => {
521                if t == 0.0 || t == 1.0 {
522                    t
523                } else {
524                    let c4 = (2.0 * std::f32::consts::PI) / 3.0;
525                    2.0_f32.powf(-10.0 * t) * ((t * 10.0 - 0.75) * c4).sin() + 1.0
526                }
527            }
528            Self::EaseOutBounce => {
529                let n1 = 7.5625;
530                let d1 = 2.75;
531                if t < 1.0 / d1 {
532                    n1 * t * t
533                } else if t < 2.0 / d1 {
534                    let t = t - 1.5 / d1;
535                    n1 * t * t + 0.75
536                } else if t < 2.5 / d1 {
537                    let t = t - 2.25 / d1;
538                    n1 * t * t + 0.9375
539                } else {
540                    let t = t - 2.625 / d1;
541                    n1 * t * t + 0.984_375
542                }
543            }
544        }
545    }
546}
547
548/// A tween that interpolates between two values over time.
549#[derive(Debug, Clone)]
550pub struct Tween<T> {
551    /// Starting value
552    pub from: T,
553    /// Ending value
554    pub to: T,
555    /// Duration in milliseconds
556    pub duration_ms: u32,
557    /// Easing function
558    pub easing: EasingFunction,
559    /// Current elapsed time in milliseconds
560    elapsed_ms: u32,
561}
562
563impl<T: Clone> Tween<T> {
564    /// Create a new tween.
565    pub fn new(from: T, to: T, duration_ms: u32) -> Self {
566        Self {
567            from,
568            to,
569            duration_ms,
570            easing: EasingFunction::default(),
571            elapsed_ms: 0,
572        }
573    }
574
575    /// Set the easing function.
576    #[must_use]
577    pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
578        self.easing = easing;
579        self
580    }
581
582    /// Get the normalized progress (0.0 to 1.0).
583    #[must_use]
584    pub fn progress(&self) -> f32 {
585        if self.duration_ms == 0 {
586            1.0
587        } else {
588            (self.elapsed_ms as f32 / self.duration_ms as f32).min(1.0)
589        }
590    }
591
592    /// Get the eased progress value.
593    #[must_use]
594    pub fn eased_progress(&self) -> f32 {
595        self.easing.apply(self.progress())
596    }
597
598    /// Check if the tween is complete.
599    #[must_use]
600    pub const fn is_complete(&self) -> bool {
601        self.elapsed_ms >= self.duration_ms
602    }
603
604    /// Advance the tween by delta milliseconds.
605    pub fn advance(&mut self, delta_ms: u32) {
606        self.elapsed_ms = self
607            .elapsed_ms
608            .saturating_add(delta_ms)
609            .min(self.duration_ms);
610    }
611
612    /// Reset the tween to the beginning.
613    pub fn reset(&mut self) {
614        self.elapsed_ms = 0;
615    }
616}
617
618impl Tween<f32> {
619    /// Get the current interpolated value.
620    #[must_use]
621    #[allow(clippy::suboptimal_flops)]
622    pub fn value(&self) -> f32 {
623        let t = self.eased_progress();
624        self.from + (self.to - self.from) * t
625    }
626}
627
628impl Tween<f64> {
629    /// Get the current interpolated value.
630    #[must_use]
631    #[allow(clippy::suboptimal_flops)]
632    pub fn value(&self) -> f64 {
633        let t = f64::from(self.eased_progress());
634        self.from + (self.to - self.from) * t
635    }
636}
637
638/// Animation state for tracking animation lifecycle.
639#[derive(Debug, Clone, Copy, PartialEq, Eq)]
640pub enum AnimationState {
641    /// Animation is idle/not started
642    Idle,
643    /// Animation is running
644    Running,
645    /// Animation is paused
646    Paused,
647    /// Animation has completed
648    Completed,
649}
650
651/// Unique identifier for an animation.
652pub type AnimationId = u64;
653
654/// An animation instance that can be managed by an Animator.
655#[derive(Debug)]
656pub struct AnimationInstance {
657    /// Unique ID
658    pub id: AnimationId,
659    /// Tween for the animation
660    pub tween: Tween<f32>,
661    /// Current state
662    pub state: AnimationState,
663    /// Loop count (0 = infinite, 1 = once, N = N times)
664    pub loop_count: u32,
665    /// Current loop iteration
666    pub current_loop: u32,
667    /// Whether to reverse on alternate loops (ping-pong)
668    pub alternate: bool,
669    /// Direction (true = forward, false = reverse)
670    forward: bool,
671}
672
673impl AnimationInstance {
674    /// Create a new animation instance.
675    pub fn new(id: AnimationId, from: f32, to: f32, duration_ms: u32) -> Self {
676        Self {
677            id,
678            tween: Tween::new(from, to, duration_ms),
679            state: AnimationState::Idle,
680            loop_count: 1,
681            current_loop: 0,
682            alternate: false,
683            forward: true,
684        }
685    }
686
687    /// Set easing function.
688    #[must_use]
689    pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
690        self.tween = self.tween.with_easing(easing);
691        self
692    }
693
694    /// Set loop count (0 = infinite).
695    #[must_use]
696    pub const fn with_loop_count(mut self, count: u32) -> Self {
697        self.loop_count = count;
698        self
699    }
700
701    /// Enable ping-pong alternating.
702    #[must_use]
703    pub const fn with_alternate(mut self, alternate: bool) -> Self {
704        self.alternate = alternate;
705        self
706    }
707
708    /// Start the animation.
709    pub fn start(&mut self) {
710        self.state = AnimationState::Running;
711        self.current_loop = 0;
712        self.forward = true;
713        self.tween.reset();
714    }
715
716    /// Pause the animation.
717    pub fn pause(&mut self) {
718        if self.state == AnimationState::Running {
719            self.state = AnimationState::Paused;
720        }
721    }
722
723    /// Resume the animation.
724    pub fn resume(&mut self) {
725        if self.state == AnimationState::Paused {
726            self.state = AnimationState::Running;
727        }
728    }
729
730    /// Stop the animation.
731    pub fn stop(&mut self) {
732        self.state = AnimationState::Idle;
733        self.tween.reset();
734    }
735
736    /// Get the current value.
737    #[must_use]
738    #[allow(clippy::suboptimal_flops)]
739    pub fn value(&self) -> f32 {
740        if self.forward {
741            self.tween.value()
742        } else {
743            self.tween.from
744                + (self.tween.to - self.tween.from) * (1.0 - self.tween.eased_progress())
745        }
746    }
747
748    /// Advance the animation by delta milliseconds.
749    pub fn advance(&mut self, delta_ms: u32) {
750        if self.state != AnimationState::Running {
751            return;
752        }
753
754        self.tween.advance(delta_ms);
755
756        if self.tween.is_complete() {
757            // Handle looping
758            if self.loop_count == 0 || self.current_loop + 1 < self.loop_count {
759                self.current_loop += 1;
760                self.tween.reset();
761
762                if self.alternate {
763                    self.forward = !self.forward;
764                }
765            } else {
766                self.state = AnimationState::Completed;
767            }
768        }
769    }
770}
771
772/// Manages multiple animations.
773#[derive(Debug, Default)]
774pub struct Animator {
775    animations: Vec<AnimationInstance>,
776    next_id: AnimationId,
777}
778
779impl Animator {
780    /// Create a new animator.
781    #[must_use]
782    pub fn new() -> Self {
783        Self::default()
784    }
785
786    /// Create a new animation and return its ID.
787    pub fn create(&mut self, from: f32, to: f32, duration_ms: u32) -> AnimationId {
788        let id = self.next_id;
789        self.next_id += 1;
790        self.animations
791            .push(AnimationInstance::new(id, from, to, duration_ms));
792        id
793    }
794
795    /// Get an animation by ID.
796    #[must_use]
797    pub fn get(&self, id: AnimationId) -> Option<&AnimationInstance> {
798        self.animations.iter().find(|a| a.id == id)
799    }
800
801    /// Get a mutable animation by ID.
802    pub fn get_mut(&mut self, id: AnimationId) -> Option<&mut AnimationInstance> {
803        self.animations.iter_mut().find(|a| a.id == id)
804    }
805
806    /// Start an animation.
807    pub fn start(&mut self, id: AnimationId) {
808        if let Some(anim) = self.get_mut(id) {
809            anim.start();
810        }
811    }
812
813    /// Pause an animation.
814    pub fn pause(&mut self, id: AnimationId) {
815        if let Some(anim) = self.get_mut(id) {
816            anim.pause();
817        }
818    }
819
820    /// Resume an animation.
821    pub fn resume(&mut self, id: AnimationId) {
822        if let Some(anim) = self.get_mut(id) {
823            anim.resume();
824        }
825    }
826
827    /// Stop an animation.
828    pub fn stop(&mut self, id: AnimationId) {
829        if let Some(anim) = self.get_mut(id) {
830            anim.stop();
831        }
832    }
833
834    /// Remove an animation.
835    pub fn remove(&mut self, id: AnimationId) {
836        self.animations.retain(|a| a.id != id);
837    }
838
839    /// Advance all animations by delta milliseconds.
840    pub fn advance(&mut self, delta_ms: u32) {
841        for anim in &mut self.animations {
842            anim.advance(delta_ms);
843        }
844    }
845
846    /// Get the value of an animation.
847    #[must_use]
848    pub fn value(&self, id: AnimationId) -> Option<f32> {
849        self.get(id).map(AnimationInstance::value)
850    }
851
852    /// Get the number of animations.
853    #[must_use]
854    pub fn len(&self) -> usize {
855        self.animations.len()
856    }
857
858    /// Check if there are no animations.
859    #[must_use]
860    pub fn is_empty(&self) -> bool {
861        self.animations.is_empty()
862    }
863
864    /// Remove all completed animations.
865    pub fn cleanup_completed(&mut self) {
866        self.animations
867            .retain(|a| a.state != AnimationState::Completed);
868    }
869
870    /// Check if any animations are running.
871    #[must_use]
872    pub fn has_running(&self) -> bool {
873        self.animations
874            .iter()
875            .any(|a| a.state == AnimationState::Running)
876    }
877}
878
879/// A timer that fires at regular intervals.
880#[derive(Debug)]
881pub struct Timer {
882    /// Interval in milliseconds
883    pub interval_ms: u32,
884    /// Elapsed time since last tick
885    elapsed_ms: u32,
886    /// Whether the timer is running
887    running: bool,
888    /// Number of times the timer has fired
889    tick_count: u64,
890    /// Optional limit on tick count (0 = unlimited)
891    max_ticks: u64,
892}
893
894impl Timer {
895    /// Create a new timer with the given interval.
896    #[must_use]
897    pub const fn new(interval_ms: u32) -> Self {
898        Self {
899            interval_ms,
900            elapsed_ms: 0,
901            running: false,
902            tick_count: 0,
903            max_ticks: 0,
904        }
905    }
906
907    /// Set maximum tick count (0 = unlimited).
908    #[must_use]
909    pub const fn with_max_ticks(mut self, max: u64) -> Self {
910        self.max_ticks = max;
911        self
912    }
913
914    /// Start the timer.
915    pub fn start(&mut self) {
916        self.running = true;
917    }
918
919    /// Stop the timer.
920    pub fn stop(&mut self) {
921        self.running = false;
922    }
923
924    /// Reset the timer.
925    pub fn reset(&mut self) {
926        self.elapsed_ms = 0;
927        self.tick_count = 0;
928    }
929
930    /// Check if the timer is running.
931    #[must_use]
932    pub const fn is_running(&self) -> bool {
933        self.running
934    }
935
936    /// Get the tick count.
937    #[must_use]
938    pub const fn tick_count(&self) -> u64 {
939        self.tick_count
940    }
941
942    /// Advance the timer and return the number of ticks that occurred.
943    pub fn advance(&mut self, delta_ms: u32) -> u32 {
944        if !self.running || self.interval_ms == 0 {
945            return 0;
946        }
947
948        self.elapsed_ms += delta_ms;
949        let ticks = self.elapsed_ms / self.interval_ms;
950        self.elapsed_ms %= self.interval_ms;
951
952        // Apply ticks with limit check
953        let mut actual_ticks = 0;
954        for _ in 0..ticks {
955            if self.max_ticks > 0 && self.tick_count >= self.max_ticks {
956                self.running = false;
957                break;
958            }
959            self.tick_count += 1;
960            actual_ticks += 1;
961        }
962
963        actual_ticks
964    }
965
966    /// Get progress to next tick (0.0 to 1.0).
967    #[must_use]
968    pub fn progress(&self) -> f32 {
969        if self.interval_ms == 0 {
970            0.0
971        } else {
972            self.elapsed_ms as f32 / self.interval_ms as f32
973        }
974    }
975}
976
977/// Frame timer for 60fps animations.
978#[derive(Debug)]
979pub struct FrameTimer {
980    /// Target frame duration in microseconds (16667 for 60fps)
981    target_frame_us: u64,
982    /// Last frame timestamp in microseconds
983    last_frame_us: Option<u64>,
984    /// Accumulated frame time for averaging
985    frame_times: [u64; 60],
986    /// Current frame index
987    frame_index: usize,
988    /// Number of recorded frame deltas
989    delta_count: usize,
990    /// Total frames rendered
991    total_frames: u64,
992}
993
994impl Default for FrameTimer {
995    fn default() -> Self {
996        Self::new(60)
997    }
998}
999
1000impl FrameTimer {
1001    /// Create a new frame timer with target FPS.
1002    #[must_use]
1003    pub fn new(target_fps: u32) -> Self {
1004        let target_frame_us = if target_fps > 0 {
1005            1_000_000 / u64::from(target_fps)
1006        } else {
1007            16667
1008        };
1009        Self {
1010            target_frame_us,
1011            last_frame_us: None,
1012            frame_times: [0; 60],
1013            frame_index: 0,
1014            delta_count: 0,
1015            total_frames: 0,
1016        }
1017    }
1018
1019    /// Record a frame with the current timestamp in microseconds.
1020    pub fn frame(&mut self, now_us: u64) {
1021        if let Some(last) = self.last_frame_us {
1022            let delta = now_us.saturating_sub(last);
1023            self.frame_times[self.frame_index] = delta;
1024            self.frame_index = (self.frame_index + 1) % 60;
1025            self.delta_count = (self.delta_count + 1).min(60);
1026        }
1027        self.last_frame_us = Some(now_us);
1028        self.total_frames += 1;
1029    }
1030
1031    /// Get the average frame time in microseconds.
1032    #[must_use]
1033    pub fn average_frame_time_us(&self) -> u64 {
1034        if self.delta_count == 0 {
1035            return self.target_frame_us;
1036        }
1037        let sum: u64 = self.frame_times[..self.delta_count].iter().sum();
1038        sum / self.delta_count as u64
1039    }
1040
1041    /// Get the current FPS.
1042    #[must_use]
1043    pub fn fps(&self) -> f32 {
1044        let avg = self.average_frame_time_us();
1045        if avg == 0 {
1046            0.0
1047        } else {
1048            1_000_000.0 / avg as f32
1049        }
1050    }
1051
1052    /// Check if we're hitting target FPS (within 10% tolerance).
1053    #[must_use]
1054    pub fn is_on_target(&self) -> bool {
1055        let avg = self.average_frame_time_us();
1056        let target = self.target_frame_us;
1057        // Within 10% of target
1058        avg <= target + target / 10
1059    }
1060
1061    /// Get the target frame time in milliseconds.
1062    #[must_use]
1063    pub fn target_frame_ms(&self) -> f32 {
1064        self.target_frame_us as f32 / 1000.0
1065    }
1066
1067    /// Get total frames rendered.
1068    #[must_use]
1069    pub const fn total_frames(&self) -> u64 {
1070        self.total_frames
1071    }
1072}
1073
1074// =============================================================================
1075// Data Refresh Manager
1076// =============================================================================
1077
1078/// Manages periodic data refresh for data sources.
1079#[derive(Debug)]
1080pub struct DataRefreshManager {
1081    /// Registered refresh tasks
1082    tasks: Vec<RefreshTask>,
1083    /// Current timestamp in milliseconds
1084    current_time_ms: u64,
1085}
1086
1087/// A scheduled data refresh task.
1088#[derive(Debug, Clone)]
1089pub struct RefreshTask {
1090    /// Data source key
1091    pub key: String,
1092    /// Refresh interval in milliseconds
1093    pub interval_ms: u64,
1094    /// Last refresh timestamp
1095    pub last_refresh_ms: u64,
1096    /// Whether task is active
1097    pub active: bool,
1098}
1099
1100impl DataRefreshManager {
1101    /// Create a new refresh manager.
1102    #[must_use]
1103    pub const fn new() -> Self {
1104        Self {
1105            tasks: Vec::new(),
1106            current_time_ms: 0,
1107        }
1108    }
1109
1110    /// Register a data source for periodic refresh.
1111    ///
1112    /// # Arguments
1113    ///
1114    /// * `key` - Data source identifier
1115    /// * `interval_ms` - Refresh interval in milliseconds
1116    pub fn register(&mut self, key: impl Into<String>, interval_ms: u64) {
1117        let key = key.into();
1118
1119        // Check if already registered
1120        if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1121            task.interval_ms = interval_ms;
1122            task.active = true;
1123            return;
1124        }
1125
1126        self.tasks.push(RefreshTask {
1127            key,
1128            interval_ms,
1129            last_refresh_ms: 0,
1130            active: true,
1131        });
1132    }
1133
1134    /// Unregister a data source.
1135    pub fn unregister(&mut self, key: &str) {
1136        self.tasks.retain(|t| t.key != key);
1137    }
1138
1139    /// Pause refresh for a data source.
1140    pub fn pause(&mut self, key: &str) {
1141        if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1142            task.active = false;
1143        }
1144    }
1145
1146    /// Resume refresh for a data source.
1147    pub fn resume(&mut self, key: &str) {
1148        if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1149            task.active = true;
1150        }
1151    }
1152
1153    /// Update the manager with the current timestamp.
1154    ///
1155    /// Returns keys of data sources that need to be refreshed.
1156    pub fn update(&mut self, current_time_ms: u64) -> Vec<String> {
1157        self.current_time_ms = current_time_ms;
1158
1159        let mut to_refresh = Vec::new();
1160
1161        for task in &mut self.tasks {
1162            if !task.active {
1163                continue;
1164            }
1165
1166            let elapsed = current_time_ms.saturating_sub(task.last_refresh_ms);
1167            if elapsed >= task.interval_ms {
1168                to_refresh.push(task.key.clone());
1169                task.last_refresh_ms = current_time_ms;
1170            }
1171        }
1172
1173        to_refresh
1174    }
1175
1176    /// Force immediate refresh of a data source.
1177    pub fn force_refresh(&mut self, key: &str) -> bool {
1178        if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1179            task.last_refresh_ms = 0;
1180            true
1181        } else {
1182            false
1183        }
1184    }
1185
1186    /// Get all registered tasks.
1187    #[must_use]
1188    pub fn tasks(&self) -> &[RefreshTask] {
1189        &self.tasks
1190    }
1191
1192    /// Get task by key.
1193    #[must_use]
1194    pub fn get_task(&self, key: &str) -> Option<&RefreshTask> {
1195        self.tasks.iter().find(|t| t.key == key)
1196    }
1197
1198    /// Check if a data source is due for refresh.
1199    #[must_use]
1200    pub fn is_due(&self, key: &str) -> bool {
1201        if let Some(task) = self.tasks.iter().find(|t| t.key == key) {
1202            if !task.active {
1203                return false;
1204            }
1205            let elapsed = self.current_time_ms.saturating_sub(task.last_refresh_ms);
1206            elapsed >= task.interval_ms
1207        } else {
1208            false
1209        }
1210    }
1211
1212    /// Get time until next refresh for a data source (in ms).
1213    #[must_use]
1214    pub fn time_until_refresh(&self, key: &str) -> Option<u64> {
1215        self.tasks.iter().find(|t| t.key == key).map(|task| {
1216            if !task.active {
1217                return u64::MAX;
1218            }
1219            let elapsed = self.current_time_ms.saturating_sub(task.last_refresh_ms);
1220            task.interval_ms.saturating_sub(elapsed)
1221        })
1222    }
1223}
1224
1225impl Default for DataRefreshManager {
1226    fn default() -> Self {
1227        Self::new()
1228    }
1229}
1230
1231// =============================================================================
1232// Widget Animation API
1233// =============================================================================
1234
1235/// Configuration for property transitions.
1236#[derive(Debug, Clone)]
1237pub struct TransitionConfig {
1238    /// Duration in milliseconds
1239    pub duration_ms: u32,
1240    /// Easing function
1241    pub easing: EasingFunction,
1242    /// Delay before starting in milliseconds
1243    pub delay_ms: u32,
1244}
1245
1246impl Default for TransitionConfig {
1247    fn default() -> Self {
1248        Self {
1249            duration_ms: 300,
1250            easing: EasingFunction::EaseInOutCubic,
1251            delay_ms: 0,
1252        }
1253    }
1254}
1255
1256impl TransitionConfig {
1257    /// Create a new transition configuration.
1258    #[must_use]
1259    pub const fn new(duration_ms: u32) -> Self {
1260        Self {
1261            duration_ms,
1262            easing: EasingFunction::EaseInOutCubic,
1263            delay_ms: 0,
1264        }
1265    }
1266
1267    /// Set the easing function.
1268    #[must_use]
1269    pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
1270        self.easing = easing;
1271        self
1272    }
1273
1274    /// Set the delay.
1275    #[must_use]
1276    pub const fn with_delay(mut self, delay_ms: u32) -> Self {
1277        self.delay_ms = delay_ms;
1278        self
1279    }
1280
1281    /// Quick preset (150ms)
1282    #[must_use]
1283    pub const fn quick() -> Self {
1284        Self::new(150)
1285    }
1286
1287    /// Normal preset (300ms)
1288    #[must_use]
1289    pub const fn normal() -> Self {
1290        Self::new(300)
1291    }
1292
1293    /// Slow preset (500ms)
1294    #[must_use]
1295    pub const fn slow() -> Self {
1296        Self::new(500)
1297    }
1298}
1299
1300/// An animated property that smoothly transitions between values.
1301///
1302/// Use this in widget state to animate property changes automatically.
1303#[derive(Debug, Clone)]
1304pub struct AnimatedProperty<T> {
1305    /// Current visual value (what's rendered)
1306    current: T,
1307    /// Target value we're animating towards
1308    target: T,
1309    /// Starting value of current animation
1310    start: T,
1311    /// Transition configuration
1312    config: TransitionConfig,
1313    /// Elapsed time in milliseconds
1314    elapsed_ms: u32,
1315    /// Whether an animation is in progress
1316    animating: bool,
1317}
1318
1319impl<T: Clone + Default> Default for AnimatedProperty<T> {
1320    fn default() -> Self {
1321        Self::new(T::default())
1322    }
1323}
1324
1325impl<T: Clone> AnimatedProperty<T> {
1326    /// Create a new animated property with an initial value.
1327    pub fn new(value: T) -> Self {
1328        Self {
1329            current: value.clone(),
1330            target: value.clone(),
1331            start: value,
1332            config: TransitionConfig::default(),
1333            elapsed_ms: 0,
1334            animating: false,
1335        }
1336    }
1337
1338    /// Create with a custom transition config.
1339    pub fn with_config(value: T, config: TransitionConfig) -> Self {
1340        Self {
1341            current: value.clone(),
1342            target: value.clone(),
1343            start: value,
1344            config,
1345            elapsed_ms: 0,
1346            animating: false,
1347        }
1348    }
1349
1350    /// Get the current visual value.
1351    pub const fn get(&self) -> &T {
1352        &self.current
1353    }
1354
1355    /// Get the target value.
1356    pub const fn target(&self) -> &T {
1357        &self.target
1358    }
1359
1360    /// Check if currently animating.
1361    #[must_use]
1362    pub const fn is_animating(&self) -> bool {
1363        self.animating
1364    }
1365
1366    /// Set a new target value, starting an animation.
1367    pub fn set(&mut self, value: T) {
1368        self.start = self.current.clone();
1369        self.target = value;
1370        self.elapsed_ms = 0;
1371        self.animating = true;
1372    }
1373
1374    /// Set value immediately without animation.
1375    pub fn set_immediate(&mut self, value: T) {
1376        self.current = value.clone();
1377        self.target = value.clone();
1378        self.start = value;
1379        self.animating = false;
1380        self.elapsed_ms = 0;
1381    }
1382
1383    /// Get animation progress (0.0 to 1.0).
1384    #[must_use]
1385    pub fn progress(&self) -> f32 {
1386        if !self.animating {
1387            return 1.0;
1388        }
1389
1390        let total = self.config.duration_ms + self.config.delay_ms;
1391        if total == 0 {
1392            return 1.0;
1393        }
1394
1395        if self.elapsed_ms < self.config.delay_ms {
1396            return 0.0;
1397        }
1398
1399        let elapsed_after_delay = self.elapsed_ms - self.config.delay_ms;
1400        (elapsed_after_delay as f32 / self.config.duration_ms as f32).min(1.0)
1401    }
1402
1403    /// Get eased progress.
1404    #[must_use]
1405    pub fn eased_progress(&self) -> f32 {
1406        self.config.easing.apply(self.progress())
1407    }
1408}
1409
1410impl AnimatedProperty<f32> {
1411    /// Advance the animation by delta milliseconds.
1412    pub fn advance(&mut self, delta_ms: u32) {
1413        if !self.animating {
1414            return;
1415        }
1416
1417        self.elapsed_ms += delta_ms;
1418
1419        let t = self.eased_progress();
1420        self.current = (self.target - self.start).mul_add(t, self.start);
1421
1422        if self.progress() >= 1.0 {
1423            self.current = self.target;
1424            self.animating = false;
1425        }
1426    }
1427}
1428
1429impl AnimatedProperty<f64> {
1430    /// Advance the animation by delta milliseconds.
1431    pub fn advance(&mut self, delta_ms: u32) {
1432        if !self.animating {
1433            return;
1434        }
1435
1436        self.elapsed_ms += delta_ms;
1437
1438        let t = f64::from(self.eased_progress());
1439        self.current = (self.target - self.start).mul_add(t, self.start);
1440
1441        if self.progress() >= 1.0 {
1442            self.current = self.target;
1443            self.animating = false;
1444        }
1445    }
1446}
1447
1448impl AnimatedProperty<crate::Color> {
1449    /// Advance the animation by delta milliseconds.
1450    pub fn advance(&mut self, delta_ms: u32) {
1451        if !self.animating {
1452            return;
1453        }
1454
1455        self.elapsed_ms += delta_ms;
1456
1457        let t = self.eased_progress();
1458        self.current = crate::Color {
1459            r: (self.target.r - self.start.r).mul_add(t, self.start.r),
1460            g: (self.target.g - self.start.g).mul_add(t, self.start.g),
1461            b: (self.target.b - self.start.b).mul_add(t, self.start.b),
1462            a: (self.target.a - self.start.a).mul_add(t, self.start.a),
1463        };
1464
1465        if self.progress() >= 1.0 {
1466            self.current = self.target;
1467            self.animating = false;
1468        }
1469    }
1470}
1471
1472impl AnimatedProperty<crate::Point> {
1473    /// Advance the animation by delta milliseconds.
1474    pub fn advance(&mut self, delta_ms: u32) {
1475        if !self.animating {
1476            return;
1477        }
1478
1479        self.elapsed_ms += delta_ms;
1480
1481        let t = self.eased_progress();
1482        self.current = crate::Point {
1483            x: (self.target.x - self.start.x).mul_add(t, self.start.x),
1484            y: (self.target.y - self.start.y).mul_add(t, self.start.y),
1485        };
1486
1487        if self.progress() >= 1.0 {
1488            self.current = self.target;
1489            self.animating = false;
1490        }
1491    }
1492}
1493
1494impl AnimatedProperty<crate::Size> {
1495    /// Advance the animation by delta milliseconds.
1496    pub fn advance(&mut self, delta_ms: u32) {
1497        if !self.animating {
1498            return;
1499        }
1500
1501        self.elapsed_ms += delta_ms;
1502
1503        let t = self.eased_progress();
1504        self.current = crate::Size {
1505            width: (self.target.width - self.start.width).mul_add(t, self.start.width),
1506            height: (self.target.height - self.start.height).mul_add(t, self.start.height),
1507        };
1508
1509        if self.progress() >= 1.0 {
1510            self.current = self.target;
1511            self.animating = false;
1512        }
1513    }
1514}
1515
1516/// Spring animation configuration.
1517#[derive(Debug, Clone, Copy)]
1518pub struct SpringConfig {
1519    /// Spring stiffness (higher = faster oscillation)
1520    pub stiffness: f32,
1521    /// Damping (higher = less oscillation)
1522    pub damping: f32,
1523    /// Mass of the object
1524    pub mass: f32,
1525}
1526
1527impl Default for SpringConfig {
1528    fn default() -> Self {
1529        Self {
1530            stiffness: 100.0,
1531            damping: 10.0,
1532            mass: 1.0,
1533        }
1534    }
1535}
1536
1537impl SpringConfig {
1538    /// Create a new spring configuration.
1539    #[must_use]
1540    pub const fn new(stiffness: f32, damping: f32, mass: f32) -> Self {
1541        Self {
1542            stiffness,
1543            damping,
1544            mass,
1545        }
1546    }
1547
1548    /// Gentle spring preset.
1549    #[must_use]
1550    pub const fn gentle() -> Self {
1551        Self::new(100.0, 15.0, 1.0)
1552    }
1553
1554    /// Bouncy spring preset.
1555    #[must_use]
1556    pub const fn bouncy() -> Self {
1557        Self::new(300.0, 10.0, 1.0)
1558    }
1559
1560    /// Stiff spring preset.
1561    #[must_use]
1562    pub const fn stiff() -> Self {
1563        Self::new(500.0, 30.0, 1.0)
1564    }
1565}
1566
1567/// Spring-based animation for physics-like motion.
1568#[derive(Debug, Clone)]
1569pub struct SpringAnimation {
1570    /// Current position
1571    position: f32,
1572    /// Current velocity
1573    velocity: f32,
1574    /// Target position
1575    target: f32,
1576    /// Spring configuration
1577    config: SpringConfig,
1578    /// Velocity threshold for considering animation complete
1579    velocity_threshold: f32,
1580    /// Position threshold for considering animation complete
1581    position_threshold: f32,
1582}
1583
1584impl SpringAnimation {
1585    /// Create a new spring animation.
1586    #[must_use]
1587    pub fn new(initial: f32) -> Self {
1588        Self {
1589            position: initial,
1590            velocity: 0.0,
1591            target: initial,
1592            config: SpringConfig::default(),
1593            velocity_threshold: 0.01,
1594            position_threshold: 0.001,
1595        }
1596    }
1597
1598    /// Create with custom spring config.
1599    #[must_use]
1600    pub const fn with_config(initial: f32, config: SpringConfig) -> Self {
1601        Self {
1602            position: initial,
1603            velocity: 0.0,
1604            target: initial,
1605            config,
1606            velocity_threshold: 0.01,
1607            position_threshold: 0.001,
1608        }
1609    }
1610
1611    /// Get the current position.
1612    #[must_use]
1613    pub const fn position(&self) -> f32 {
1614        self.position
1615    }
1616
1617    /// Get the current velocity.
1618    #[must_use]
1619    pub const fn velocity(&self) -> f32 {
1620        self.velocity
1621    }
1622
1623    /// Get the target.
1624    #[must_use]
1625    pub const fn target(&self) -> f32 {
1626        self.target
1627    }
1628
1629    /// Set the target position.
1630    pub fn set_target(&mut self, target: f32) {
1631        self.target = target;
1632    }
1633
1634    /// Set position immediately without animation.
1635    pub fn set_immediate(&mut self, position: f32) {
1636        self.position = position;
1637        self.target = position;
1638        self.velocity = 0.0;
1639    }
1640
1641    /// Check if the animation is at rest.
1642    #[must_use]
1643    pub fn is_at_rest(&self) -> bool {
1644        let position_diff = (self.position - self.target).abs();
1645        let velocity_abs = self.velocity.abs();
1646        position_diff < self.position_threshold && velocity_abs < self.velocity_threshold
1647    }
1648
1649    /// Advance the spring animation by delta seconds.
1650    pub fn advance(&mut self, delta_s: f32) {
1651        if self.is_at_rest() {
1652            self.position = self.target;
1653            self.velocity = 0.0;
1654            return;
1655        }
1656
1657        // Spring physics: F = -kx - cv
1658        // a = F/m = (-kx - cv) / m
1659        let displacement = self.position - self.target;
1660        let spring_force = -self.config.stiffness * displacement;
1661        let damping_force = -self.config.damping * self.velocity;
1662        let acceleration = (spring_force + damping_force) / self.config.mass;
1663
1664        // Semi-implicit Euler integration
1665        self.velocity += acceleration * delta_s;
1666        self.position += self.velocity * delta_s;
1667    }
1668
1669    /// Advance by delta milliseconds.
1670    pub fn advance_ms(&mut self, delta_ms: u32) {
1671        self.advance(delta_ms as f32 / 1000.0);
1672    }
1673}