x11-overlay 0.1.0

A library for creating overlay interfaces on X11 systems using Cairo for rendering
Documentation
#![allow(dead_code)]

use std::time::{Duration, Instant};

/// Timer for tracking animation frame timing
#[derive(Debug)]
pub struct Timer {
    last_update: Instant,
    start_time: Instant,
}

impl Timer {
    pub fn new() -> Self {
        let now = Instant::now();
        Self {
            last_update: now,
            start_time: now,
        }
    }

    /// Get the time elapsed since the last update call and update the timer
    pub fn delta(&mut self) -> f64 {
        let now = Instant::now();
        let delta = now.duration_since(self.last_update);
        self.last_update = now;
        delta.as_secs_f64()
    }

    /// Get the total elapsed time since timer creation
    pub fn elapsed(&self) -> f64 {
        Instant::now().duration_since(self.start_time).as_secs_f64()
    }

    /// Reset the timer
    pub fn reset(&mut self) {
        let now = Instant::now();
        self.last_update = now;
        self.start_time = now;
    }
}

impl Default for Timer {
    fn default() -> Self {
        Self::new()
    }
}

/// Animation timeline for tracking animation progress
#[derive(Debug, Clone)]
pub struct Timeline {
    duration: f64,
    elapsed: f64,
    looping: bool,
}

impl Timeline {
    pub fn new(duration: f64) -> Self {
        Self {
            duration,
            elapsed: 0.0,
            looping: false,
        }
    }

    pub fn looping(mut self, looping: bool) -> Self {
        self.looping = looping;
        self
    }

    /// Update the timeline with delta time
    /// Returns true if still active, false if completed
    pub fn update(&mut self, delta_time: f64) -> bool {
        self.elapsed += delta_time;

        if self.elapsed >= self.duration {
            if self.looping {
                self.elapsed %= self.duration;
                true
            } else {
                self.elapsed = self.duration;
                false
            }
        } else {
            true
        }
    }

    /// Get the current progress as a value between 0.0 and 1.0
    pub fn progress(&self) -> f32 {
        if self.duration <= 0.0 {
            1.0
        } else {
            (self.elapsed / self.duration).min(1.0) as f32
        }
    }

    /// Check if the timeline has completed
    pub fn is_complete(&self) -> bool {
        !self.looping && self.elapsed >= self.duration
    }

    /// Reset the timeline to the beginning
    pub fn reset(&mut self) {
        self.elapsed = 0.0;
    }

    /// Get the total duration
    pub fn duration(&self) -> f64 {
        self.duration
    }

    /// Get the elapsed time
    pub fn elapsed(&self) -> f64 {
        self.elapsed
    }
}

/// Frame rate limiter for consistent animation timing
#[derive(Debug)]
pub struct FrameLimiter {
    target_fps: u32,
    frame_duration: Duration,
    last_frame: Instant,
}

impl FrameLimiter {
    pub fn new(target_fps: u32) -> Self {
        let frame_duration = Duration::from_nanos(1_000_000_000 / target_fps as u64);
        Self {
            target_fps,
            frame_duration,
            last_frame: Instant::now(),
        }
    }

    /// Wait if necessary to maintain target frame rate
    /// Returns true if a wait occurred
    pub fn limit(&mut self) -> bool {
        let now = Instant::now();
        let elapsed = now.duration_since(self.last_frame);

        if elapsed < self.frame_duration {
            let sleep_duration = self.frame_duration - elapsed;
            std::thread::sleep(sleep_duration);
            self.last_frame = Instant::now();
            true
        } else {
            self.last_frame = now;
            false
        }
    }

    pub fn target_fps(&self) -> u32 {
        self.target_fps
    }

    pub fn set_target_fps(&mut self, fps: u32) {
        self.target_fps = fps;
        self.frame_duration = Duration::from_nanos(1_000_000_000 / fps as u64);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread;
    use std::time::Duration;

    #[test]
    fn test_timer_creation() {
        let timer = Timer::new();
        assert!(timer.elapsed() >= 0.0);
    }

    #[test]
    fn test_timer_delta() {
        let mut timer = Timer::new();
        thread::sleep(Duration::from_millis(10));

        let delta = timer.delta();
        assert!(delta > 0.0);
        assert!(delta < 1.0); // Should be less than 1 second
    }

    #[test]
    fn test_timer_reset() {
        let mut timer = Timer::new();
        thread::sleep(Duration::from_millis(10));

        timer.reset();
        let elapsed = timer.elapsed();
        assert!(elapsed < 0.01); // Should be very close to 0 after reset
    }

    #[test]
    fn test_timeline_creation() {
        let timeline = Timeline::new(2.0);
        assert_eq!(timeline.duration(), 2.0);
        assert_eq!(timeline.elapsed(), 0.0);
        assert_eq!(timeline.progress(), 0.0);
        assert!(!timeline.is_complete());
    }

    #[test]
    fn test_timeline_progress() {
        let mut timeline = Timeline::new(2.0);

        // Update with 1 second - should be 50% complete
        timeline.update(1.0);
        assert_eq!(timeline.progress(), 0.5);
        assert!(!timeline.is_complete());

        // Update with another second - should be complete
        timeline.update(1.0);
        assert_eq!(timeline.progress(), 1.0);
        assert!(timeline.is_complete());
    }

    #[test]
    fn test_timeline_looping() {
        let mut timeline = Timeline::new(2.0).looping(true);

        // Update beyond duration
        let still_active = timeline.update(3.0);
        assert!(still_active);
        assert!(!timeline.is_complete());

        // Progress should wrap around
        assert_eq!(timeline.progress(), 0.5);
    }

    #[test]
    fn test_timeline_non_looping() {
        let mut timeline = Timeline::new(2.0);

        // Update beyond duration
        let still_active = timeline.update(3.0);
        assert!(!still_active);
        assert!(timeline.is_complete());
        assert_eq!(timeline.progress(), 1.0);
    }

    #[test]
    fn test_timeline_reset() {
        let mut timeline = Timeline::new(2.0);
        timeline.update(1.5);

        timeline.reset();
        assert_eq!(timeline.elapsed(), 0.0);
        assert_eq!(timeline.progress(), 0.0);
        assert!(!timeline.is_complete());
    }

    #[test]
    fn test_timeline_zero_duration() {
        let timeline = Timeline::new(0.0);
        assert_eq!(timeline.progress(), 1.0);
    }

    #[test]
    fn test_frame_limiter_creation() {
        let limiter = FrameLimiter::new(60);
        assert_eq!(limiter.target_fps(), 60);
    }

    #[test]
    fn test_frame_limiter_set_fps() {
        let mut limiter = FrameLimiter::new(60);
        limiter.set_target_fps(30);
        assert_eq!(limiter.target_fps(), 30);
    }

    #[test]
    fn test_frame_limiter_limit() {
        let mut limiter = FrameLimiter::new(1000); // Very high FPS to test no-sleep case

        // First call shouldn't need to wait since we just created it
        let _did_wait = limiter.limit();

        // Behavior may vary based on timing, so just test that it returns a boolean
        // No assertion needed - the function call itself validates it returns a bool
    }

    mod property_tests {
        use super::*;
        use proptest::prelude::*;

        proptest! {
            #[test]
            fn test_timeline_progress_bounds(duration in 0.1f64..100.0, elapsed_time in 0.0f64..200.0) {
                let mut timeline = Timeline::new(duration);
                timeline.update(elapsed_time);

                let progress = timeline.progress();
                assert!((0.0..=1.0).contains(&progress));
            }

            #[test]
            fn test_timeline_looping_never_completes(
                duration in 0.1f64..10.0,
                update_times in prop::collection::vec(0.0f64..5.0, 1..10)
            ) {
                let mut timeline = Timeline::new(duration).looping(true);

                for update_time in update_times {
                    timeline.update(update_time);
                    assert!(!timeline.is_complete());
                }
            }

            #[test]
            fn test_frame_limiter_fps_range(fps in 1u32..1000) {
                let limiter = FrameLimiter::new(fps);
                assert_eq!(limiter.target_fps(), fps);
            }
        }
    }
}