use glam::{FloatExt, Vec2}; use crate::{ event::{CallbackDataCommon, EventAlterables}, layout::{LayoutState, WidgetID}, widget::{WidgetData, WidgetObj}, }; pub enum AnimationEasing { Linear, InQuad, // ^2 InCubic, // ^3 InQuint, // ^5 OutQuad, // ^2 OutCubic, // ^3 OutQuint, // ^5 OutBack, InBack, } impl AnimationEasing { fn interpolate(&self, x: f32) -> f32 { match self { Self::Linear => x, Self::InQuad => x.powi(2), Self::InCubic => x.powi(3), Self::InQuint => x.powi(5), Self::OutQuad => 1.0 - (1.0 - x).powi(2), Self::OutCubic => 1.0 - (1.0 - x).powi(3), Self::OutQuint => 1.0 - (1.0 - x).powi(5), Self::OutBack => { let a = 1.7; let b = a + 1.0; 1.0 + b * (x - 1.0).powi(3) + a * (x - 1.0).powi(2) } Self::InBack => { let a = 1.7; let b = a + 1.0; b * x.powi(3) - a * x.powi(2) } } } } pub struct CallbackData<'a> { pub obj: &'a mut dyn WidgetObj, pub data: &'a mut WidgetData, pub widget_id: WidgetID, pub widget_size: Vec2, pub pos: f32, // 0.0 (start of animation) - 1.0 (end of animation) } pub type AnimationCallback = Box; pub struct Animation { target_widget: WidgetID, id: u32, ticks_remaining: u32, ticks_duration: u32, easing: AnimationEasing, pos: f32, pos_prev: f32, last_tick: bool, callback: AnimationCallback, } impl Animation { pub fn new(target_widget: WidgetID, ticks: u32, easing: AnimationEasing, callback: AnimationCallback) -> Self { Self::new_ex(target_widget, 0, ticks, easing, callback) } pub fn new_ex( target_widget: WidgetID, animation_id: u32, ticks: u32, easing: AnimationEasing, callback: AnimationCallback, ) -> Self { Self { target_widget, id: animation_id, callback, easing, ticks_duration: ticks, ticks_remaining: ticks, last_tick: false, pos: 0.0, pos_prev: 0.0, } } fn call(&self, state: &LayoutState, alterables: &mut EventAlterables, pos: f32) { let Some(widget) = state.widgets.get(self.target_widget).cloned() else { return; // failed }; let widget_node = *state.nodes.get(self.target_widget).unwrap(); let layout = state.tree.layout(widget_node).unwrap(); // should always succeed let mut widget_state = widget.state(); let (data, obj) = widget_state.get_data_obj_mut(); let data = &mut CallbackData { widget_id: self.target_widget, widget_size: Vec2::new(layout.size.width, layout.size.height), obj, data, pos, }; let common = &mut CallbackDataCommon { state, alterables }; (self.callback)(common, data); } } #[derive(Default)] pub struct Animations { running_animations: Vec, } impl Animations { pub fn tick(&mut self, state: &LayoutState, alterables: &mut EventAlterables) { for anim in &mut self.running_animations { let x = 1.0 - (anim.ticks_remaining as f32 / anim.ticks_duration as f32); let pos = if anim.ticks_remaining > 0 { anim.easing.interpolate(x) } else { anim.last_tick = true; 1.0 }; anim.pos_prev = anim.pos; anim.pos = pos; anim.call(state, alterables, 1.0); if anim.last_tick { alterables.needs_redraw = true; } anim.ticks_remaining -= 1; } self.running_animations.retain(|anim| anim.ticks_remaining > 0); } pub fn process(&mut self, state: &LayoutState, alterables: &mut EventAlterables, alpha: f32) { for anim in &mut self.running_animations { let pos = anim.pos_prev.lerp(anim.pos, alpha); anim.call(state, alterables, pos); } } pub fn add(&mut self, anim: Animation) { // prevent running two animations at once self.stop_by_widget(anim.target_widget, Some(anim.id)); self.running_animations.push(anim); } pub fn stop_by_widget(&mut self, widget_id: WidgetID, opt_animation_id: Option) { self.running_animations.retain(|anim| { if let Some(animation_id) = &opt_animation_id { if anim.target_widget == widget_id { anim.id != *animation_id } else { true } } else { anim.target_widget != widget_id } }); } }