kael_ui 0.2.0

Professional shadcn-inspired UI component library for Kael. 100+ accessible components for building beautiful, performant desktop applications.
use kael::*;
use std::time::Duration;

use crate::animations::easings;

#[derive(Clone)]
pub struct AnimationPreset {
    duration: Duration,
    easing: fn(f32) -> f32,
    delay: Duration,
}

impl AnimationPreset {
    pub fn new(duration: Duration, easing: fn(f32) -> f32) -> Self {
        Self {
            duration,
            easing,
            delay: Duration::ZERO,
        }
    }

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

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

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

    pub fn with_duration(mut self, duration: Duration) -> Self {
        self.duration = duration;
        self
    }

    pub fn with_easing(mut self, easing: fn(f32) -> f32) -> Self {
        self.easing = easing;
        self
    }

    pub fn with_delay(mut self, delay: Duration) -> Self {
        self.delay = delay;
        self
    }

    pub fn get_duration(&self) -> Duration {
        self.duration
    }

    pub fn get_easing(&self) -> fn(f32) -> f32 {
        self.easing
    }

    pub fn get_delay(&self) -> Duration {
        self.delay
    }

    pub fn to_animation(&self) -> Animation {
        Animation::new(self.duration).with_easing(self.easing)
    }
}

pub fn fade_in() -> AnimationPreset {
    AnimationPreset::new(Duration::from_millis(200), easings::ease_out_cubic)
}

pub fn fade_out() -> AnimationPreset {
    AnimationPreset::new(Duration::from_millis(200), easings::ease_in_cubic)
}

pub fn slide_up() -> AnimationPreset {
    AnimationPreset::new(Duration::from_millis(250), easings::ease_out_cubic)
}

pub fn slide_down() -> AnimationPreset {
    AnimationPreset::new(Duration::from_millis(250), easings::ease_out_cubic)
}

pub fn scale_in() -> AnimationPreset {
    AnimationPreset::new(Duration::from_millis(200), easings::ease_out_back)
}

pub fn bounce_in() -> AnimationPreset {
    AnimationPreset::new(Duration::from_millis(400), easings::elastic)
}

pub fn slide_in_left() -> AnimationPreset {
    AnimationPreset::new(Duration::from_millis(250), easings::ease_out_cubic)
}

pub fn slide_in_right() -> AnimationPreset {
    AnimationPreset::new(Duration::from_millis(250), easings::ease_out_cubic)
}

#[derive(Clone, Debug, PartialEq)]
pub enum AnimationRepeat {
    Once,
    Count(u32),
    Infinite,
}

pub struct KeyframeAnimation {
    id: SharedString,
    keyframes: Vec<(f32, f32)>,
    duration: Duration,
    repeat: AnimationRepeat,
    easing: fn(f32) -> f32,
}

impl KeyframeAnimation {
    pub fn new(id: impl Into<SharedString>) -> Self {
        Self {
            id: id.into(),
            keyframes: vec![(0.0, 0.0), (1.0, 1.0)],
            duration: Duration::from_millis(300),
            repeat: AnimationRepeat::Once,
            easing: easings::linear,
        }
    }

    pub fn at(mut self, pct: f32, value: f32) -> Self {
        let pct = pct.clamp(0.0, 1.0);
        if let Some(pos) = self
            .keyframes
            .iter()
            .position(|(p, _)| (*p - pct).abs() < f32::EPSILON)
        {
            self.keyframes[pos] = (pct, value);
        } else {
            self.keyframes.push((pct, value));
            self.keyframes
                .sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
        }
        self
    }

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

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

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

    pub fn id(&self) -> &SharedString {
        &self.id
    }

    pub fn get_duration(&self) -> Duration {
        self.duration
    }

    pub fn get_repeat(&self) -> &AnimationRepeat {
        &self.repeat
    }

    pub fn interpolate(&self, progress: f32) -> f32 {
        let progress = progress.clamp(0.0, 1.0);

        if self.keyframes.len() < 2 {
            return if self.keyframes.is_empty() {
                0.0
            } else {
                self.keyframes[0].1
            };
        }

        let eased = (self.easing)(progress);

        let mut prev = &self.keyframes[0];
        for kf in &self.keyframes[1..] {
            if eased <= kf.0 {
                let range = kf.0 - prev.0;
                if range <= f32::EPSILON {
                    return kf.1;
                }
                let local_t = (eased - prev.0) / range;
                return prev.1 + (kf.1 - prev.1) * local_t;
            }
            prev = kf;
        }

        self.keyframes.last().map(|kf| kf.1).unwrap_or(1.0)
    }

    pub fn to_animation(&self) -> Animation {
        let anim = Animation::new(self.duration);
        match &self.repeat {
            AnimationRepeat::Once => anim,
            AnimationRepeat::Infinite => anim.repeat_forever(),
            AnimationRepeat::Count(_) => anim.repeat_forever(),
        }
    }
}

#[derive(Clone)]
pub struct StaggerConfig {
    delay_per_child: Duration,
    preset: AnimationPreset,
}

impl StaggerConfig {
    pub fn new() -> Self {
        Self {
            delay_per_child: Duration::from_millis(50),
            preset: fade_in(),
        }
    }

    pub fn delay_per_child(mut self, delay: Duration) -> Self {
        self.delay_per_child = delay;
        self
    }

    pub fn animation(mut self, preset: AnimationPreset) -> Self {
        self.preset = preset;
        self
    }

    pub fn delay_for_index(&self, index: usize) -> Duration {
        self.preset.delay + self.delay_per_child * index as u32
    }

    pub fn preset_for_index(&self, index: usize) -> AnimationPreset {
        let mut preset = self.preset.clone();
        preset.delay = self.delay_for_index(index);
        preset
    }

    pub fn get_preset(&self) -> &AnimationPreset {
        &self.preset
    }
}

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

#[derive(Clone)]
pub struct Transition {
    property: SharedString,
    duration: Duration,
    easing: fn(f32) -> f32,
    delay: Duration,
}

impl Transition {
    pub fn new(property: impl Into<SharedString>) -> Self {
        Self {
            property: property.into(),
            duration: Duration::from_millis(200),
            easing: easings::ease_out_cubic,
            delay: Duration::ZERO,
        }
    }

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

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

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

    pub fn get_property(&self) -> &SharedString {
        &self.property
    }

    pub fn get_duration(&self) -> Duration {
        self.duration
    }

    pub fn get_easing(&self) -> fn(f32) -> f32 {
        self.easing
    }

    pub fn get_delay(&self) -> Duration {
        self.delay
    }

    pub fn to_animation(&self) -> Animation {
        Animation::new(self.duration).with_easing(self.easing)
    }
}