use std::cell::Cell;
use std::time::Duration;
use web_time::Instant;
std::thread_local! {
static NEEDS_TICK: Cell<bool> = Cell::new(false);
static NEXT_REPAINT_AT: Cell<Option<Instant>> = Cell::new(None);
}
pub fn request_tick() {
NEEDS_TICK.with(|c| c.set(true));
}
pub fn wants_tick() -> bool {
NEEDS_TICK.with(|c| c.get())
}
pub fn clear_tick() {
NEEDS_TICK.with(|c| c.set(false));
NEXT_REPAINT_AT.with(|c| c.set(None));
}
pub fn request_repaint_after(delay: Duration) {
let when = Instant::now() + delay;
NEXT_REPAINT_AT.with(|c| {
match c.get() {
Some(existing) if existing <= when => {}
_ => c.set(Some(when)),
}
});
}
pub fn take_next_repaint() -> Option<Instant> {
NEXT_REPAINT_AT.with(|c| c.replace(None))
}
#[derive(Clone, Copy)]
pub struct Tween {
current: f64,
start_value: f64,
target: f64,
start_time: Option<Instant>,
duration: f64,
}
impl Tween {
pub const fn new(initial: f64, duration_secs: f64) -> Self {
Self {
current: initial,
start_value: initial,
target: initial,
start_time: None,
duration: duration_secs,
}
}
pub fn set_target(&mut self, new_target: f64) {
if (self.target - new_target).abs() > 1e-9 {
self.start_value = self.current;
self.target = new_target;
self.start_time = Some(Instant::now());
}
}
pub fn tick(&mut self) -> f64 {
if let Some(start) = self.start_time {
let elapsed = start.elapsed().as_secs_f64();
let p = (elapsed / self.duration).min(1.0);
let eased = 1.0 - (1.0 - p).powi(3);
self.current = self.start_value + (self.target - self.start_value) * eased;
if p >= 1.0 {
self.current = self.target;
self.start_time = None;
} else {
request_tick();
}
}
self.current
}
pub fn value(&self) -> f64 { self.current }
}
impl Default for Tween {
fn default() -> Self { Self::new(0.0, 0.12) }
}