use std::time::Duration;
use crate::{HoverButton, WindowState};
#[must_use]
pub fn ease_out_cubic(t: f32) -> f32 {
let clamped = t.clamp(0.0, 1.0);
let inverse = 1.0 - clamped;
1.0 - (inverse * inverse * inverse)
}
#[derive(Debug, Clone, Copy)]
struct Track {
start: f32,
progress: f32,
elapsed: Duration,
running: bool,
}
impl Track {
const fn settled(at: f32) -> Self {
Self {
start: at,
progress: at,
elapsed: Duration::ZERO,
running: false,
}
}
const fn retarget(&mut self, from: f32) {
self.start = from;
self.elapsed = Duration::ZERO;
self.running = true;
}
fn advance(&mut self, dt: Duration, duration: Duration, target: f32) {
if !self.running {
return;
}
self.elapsed = self.elapsed.saturating_add(dt);
let t = (self.elapsed.as_secs_f32() / duration.as_secs_f32()).clamp(0.0, 1.0);
self.progress = self.start + (target - self.start) * ease_out_cubic(t);
if t >= 1.0 {
self.progress = target;
self.running = false;
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct WindowAnim {
focus_target: bool,
focus: Track,
hover_target: HoverButton,
hover_painted: HoverButton,
hover: Track,
}
impl Default for WindowAnim {
fn default() -> Self {
Self::new(false)
}
}
impl WindowAnim {
#[must_use]
pub const fn new(focused: bool) -> Self {
Self {
focus_target: focused,
focus: Track::settled(if focused { 1.0 } else { 0.0 }),
hover_target: HoverButton::None,
hover_painted: HoverButton::None,
hover: Track::settled(0.0),
}
}
pub const fn retarget_focus(&mut self, focused: bool) {
if self.focus_target == focused {
return;
}
self.focus_target = focused;
let from = self.focus.progress;
self.focus.retarget(from);
}
pub const fn retarget_hover(&mut self, hover: HoverButton) {
if self.hover_target as u8 == hover as u8 {
return;
}
self.hover_target = hover;
let from = if matches!(hover, HoverButton::None) {
self.hover.progress
} else {
self.hover_painted = hover;
self.hover.progress = 0.0;
0.0
};
self.hover.retarget(from);
}
pub const fn snap(&mut self) {
self.focus = Track::settled(if self.focus_target { 1.0 } else { 0.0 });
self.hover_painted = self.hover_target;
self.hover = Track::settled(if matches!(self.hover_target, HoverButton::None) {
0.0
} else {
1.0
});
}
pub fn tick(&mut self, dt: Duration, duration: Duration) -> bool {
if duration.is_zero() {
self.snap();
return false;
}
self.focus
.advance(dt, duration, if self.focus_target { 1.0 } else { 0.0 });
let hover_target = if !matches!(self.hover_target, HoverButton::None)
&& self.hover_target as u8 == self.hover_painted as u8
{
1.0
} else {
0.0
};
let was_running = self.hover.running;
self.hover.advance(dt, duration, hover_target);
if was_running && !self.hover.running && hover_target == 0.0 {
self.hover_painted = HoverButton::None;
}
self.is_animating()
}
#[must_use]
pub const fn is_animating(&self) -> bool {
self.focus.running || self.hover.running
}
#[must_use]
pub const fn focus_progress(&self) -> f32 {
self.focus.progress
}
#[must_use]
pub const fn hover_progress(&self) -> f32 {
self.hover.progress
}
#[must_use]
pub const fn hover_painted(&self) -> HoverButton {
self.hover_painted
}
pub const fn apply_to(&self, state: &mut WindowState) {
state.focus_progress = self.focus.progress;
state.hover = self.hover_painted;
state.hover_progress = self.hover.progress;
}
}