armas_basic/animation/
mod.rs1pub mod easing;
8pub mod interpolate;
10pub mod momentum;
11pub mod staggered;
12pub mod velocity_drag;
13
14pub use easing::EasingFunction;
15pub use interpolate::Interpolate;
16pub use momentum::{
17 ContinuousWithMomentum, MomentumBehavior, MomentumPosition, SnapToPageBoundaries,
18};
19pub use staggered::{AnimationSequence, LoopMode, LoopingAnimation, StaggeredAnimation};
20pub use velocity_drag::{DoubleClickReset, DragMode, VelocityDrag, VelocityDragConfig};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum AnimationState {
25 NotStarted,
27 Running,
29 Paused,
31 Completed,
33}
34
35#[derive(Debug, Clone)]
49pub struct Animation<T: Interpolate> {
50 pub start: T,
52 pub end: T,
54 pub duration: f32,
56 pub elapsed: f32,
58 pub easing: EasingFunction,
60 pub state: AnimationState,
62}
63
64impl<T: Interpolate> Animation<T> {
65 pub const fn new(start: T, end: T, duration: f32) -> Self {
67 Self {
68 start,
69 end,
70 duration,
71 elapsed: 0.0,
72 easing: EasingFunction::EaseInOut,
73 state: AnimationState::NotStarted,
74 }
75 }
76
77 #[must_use]
79 pub const fn easing(mut self, easing: EasingFunction) -> Self {
80 self.easing = easing;
81 self
82 }
83
84 pub const fn start(&mut self) {
86 self.state = AnimationState::Running;
87 self.elapsed = 0.0;
88 }
89
90 pub fn pause(&mut self) {
92 if self.state == AnimationState::Running {
93 self.state = AnimationState::Paused;
94 }
95 }
96
97 pub fn resume(&mut self) {
99 if self.state == AnimationState::Paused {
100 self.state = AnimationState::Running;
101 }
102 }
103
104 pub const fn reset(&mut self) {
106 self.elapsed = 0.0;
107 self.state = AnimationState::NotStarted;
108 }
109
110 pub fn update(&mut self, dt: f32) {
112 if self.state != AnimationState::Running {
113 return;
114 }
115
116 self.elapsed += dt;
117 if self.elapsed >= self.duration {
118 self.elapsed = self.duration;
119 self.state = AnimationState::Completed;
120 }
121 }
122
123 pub fn value(&self) -> T {
125 let t = if self.duration <= 0.0 {
126 1.0
127 } else {
128 (self.elapsed / self.duration).clamp(0.0, 1.0)
129 };
130
131 let eased_t = self.easing.apply(t);
132 self.start.interpolate(&self.end, eased_t)
133 }
134
135 pub fn progress(&self) -> f32 {
137 if self.duration <= 0.0 {
138 1.0
139 } else {
140 (self.elapsed / self.duration).clamp(0.0, 1.0)
141 }
142 }
143
144 pub fn is_complete(&self) -> bool {
146 self.state == AnimationState::Completed
147 }
148
149 pub fn is_running(&self) -> bool {
151 self.state == AnimationState::Running
152 }
153}
154
155#[derive(Debug, Clone)]
171pub struct SpringAnimation {
172 pub value: f32,
174 pub velocity: f32,
176 pub target: f32,
178 pub stiffness: f32,
180 pub damping: f32,
182}
183
184impl SpringAnimation {
185 #[must_use]
187 pub const fn new(initial: f32, target: f32) -> Self {
188 Self {
189 value: initial,
190 velocity: 0.0,
191 target,
192 stiffness: 200.0,
193 damping: 20.0,
194 }
195 }
196
197 #[must_use]
199 pub const fn params(mut self, stiffness: f32, damping: f32) -> Self {
200 self.stiffness = stiffness;
201 self.damping = damping;
202 self
203 }
204
205 pub fn update(&mut self, dt: f32) {
207 let spring_force = -self.stiffness * (self.value - self.target);
209
210 let damping_force = -self.damping * self.velocity;
212
213 let acceleration = spring_force + damping_force;
215
216 self.velocity += acceleration * dt;
218 self.value += self.velocity * dt;
219 }
220
221 pub const fn set_target(&mut self, target: f32) {
223 self.target = target;
224 }
225
226 #[must_use]
228 pub fn is_settled(&self, position_threshold: f32, velocity_threshold: f32) -> bool {
229 let position_error = (self.value - self.target).abs();
230 let velocity_mag = self.velocity.abs();
231
232 position_error < position_threshold && velocity_mag < velocity_threshold
233 }
234
235 pub const fn reset(&mut self, value: f32, target: f32) {
237 self.value = value;
238 self.target = target;
239 self.velocity = 0.0;
240 }
241}