#import "modules/std.sx"; #import "modules/math"; // --- Easing Functions --- ease_linear :: (t: f32) -> f32 { t; } ease_in_quad :: (t: f32) -> f32 { t * t; } ease_out_quad :: (t: f32) -> f32 { t * (2.0 - t); } ease_in_out_quad :: (t: f32) -> f32 { if t < 0.5 then 2.0 * t * t else -1.0 + (4.0 - 2.0 * t) * t; } ease_out_cubic :: (t: f32) -> f32 { u := t - 1.0; u * u * u + 1.0; } // --- AnimatedFloat — duration-based --- AnimatedFloat :: struct { current: f32; from: f32; to: f32; elapsed: f32; duration: f32; easing: ?Closure(f32) -> f32; active: bool; make :: (value: f32) -> AnimatedFloat { AnimatedFloat.{ current = value, from = value, to = value, elapsed = 0.0, duration = 0.0, easing = null, active = false }; } animate_to :: (self: *AnimatedFloat, target: f32, dur: f32, ease: Closure(f32) -> f32) { self.from = self.current; self.to = target; self.elapsed = 0.0; self.duration = dur; self.easing = ease; self.active = true; } tick :: (self: *AnimatedFloat, dt: f32) { if !self.active { return; } self.elapsed += dt; t := clamp(self.elapsed / self.duration, 0.0, 1.0); eased := if ease := self.easing { ease(t); } else { t; }; self.current = self.from + (self.to - self.from) * eased; if t >= 1.0 { self.current = self.to; self.active = false; } } } // --- SpringFloat — physics-based --- SpringFloat :: struct { current: f32; velocity: f32; target: f32; stiffness: f32; damping: f32; mass: f32; threshold: f32; make :: (value: f32) -> SpringFloat { SpringFloat.{ current = value, velocity = 0.0, target = value, stiffness = 200.0, damping = 20.0, mass = 1.0, threshold = 0.01 }; } snappy :: (value: f32) -> SpringFloat { SpringFloat.{ current = value, velocity = 0.0, target = value, stiffness = 300.0, damping = 25.0, mass = 1.0, threshold = 0.01 }; } tick :: (self: *SpringFloat, dt: f32) { if self.is_settled() { return; } force := 0.0 - self.stiffness * (self.current - self.target); damping_force := 0.0 - self.damping * self.velocity; accel := (force + damping_force) / self.mass; self.velocity += accel * dt; self.current += self.velocity * dt; } is_settled :: (self: *SpringFloat) -> bool { abs(self.current - self.target) < self.threshold and abs(self.velocity) < self.threshold; } }