armas_basic/animation/
momentum.rs1pub trait MomentumBehavior {
10 fn released_with_velocity(&mut self, position: f64, velocity: f64);
12
13 fn next_position(&mut self, current_position: f64, elapsed_seconds: f64) -> f64;
16
17 fn is_stopped(&self, position: f64) -> bool;
19}
20
21#[derive(Debug, Clone)]
32pub struct ContinuousWithMomentum {
33 velocity: f64,
34 damping: f64,
35 minimum_velocity: f64,
36}
37
38impl Default for ContinuousWithMomentum {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl ContinuousWithMomentum {
45 #[must_use]
47 pub const fn new() -> Self {
48 Self {
49 velocity: 0.0,
50 damping: 0.92,
51 minimum_velocity: 0.05,
52 }
53 }
54
55 #[must_use]
59 pub fn friction(mut self, friction: f64) -> Self {
60 self.damping = 1.0 - friction.clamp(0.0, 0.99);
61 self
62 }
63
64 #[must_use]
68 pub const fn minimum_velocity(mut self, min_vel: f64) -> Self {
69 self.minimum_velocity = min_vel.abs();
70 self
71 }
72
73 #[must_use]
75 pub const fn velocity(&self) -> f64 {
76 self.velocity
77 }
78}
79
80impl MomentumBehavior for ContinuousWithMomentum {
81 fn released_with_velocity(&mut self, _position: f64, velocity: f64) {
82 self.velocity = velocity;
83 }
84
85 fn next_position(&mut self, current_position: f64, elapsed_seconds: f64) -> f64 {
86 let new_pos = current_position + self.velocity * elapsed_seconds;
87 self.velocity *= self.damping;
89 if self.velocity.abs() < self.minimum_velocity {
90 self.velocity = 0.0;
91 }
92 new_pos
93 }
94
95 fn is_stopped(&self, _position: f64) -> bool {
96 self.velocity.abs() < self.minimum_velocity
97 }
98}
99
100#[derive(Debug, Clone)]
111pub struct SnapToPageBoundaries {
112 target_position: f64,
113 snap_speed: f64,
114}
115
116impl Default for SnapToPageBoundaries {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl SnapToPageBoundaries {
123 #[must_use]
125 pub const fn new() -> Self {
126 Self {
127 target_position: 0.0,
128 snap_speed: 10.0,
129 }
130 }
131
132 #[must_use]
136 pub const fn snap_speed(mut self, speed: f64) -> Self {
137 self.snap_speed = speed.max(1.0);
138 self
139 }
140
141 #[must_use]
143 pub const fn target(&self) -> f64 {
144 self.target_position
145 }
146}
147
148impl MomentumBehavior for SnapToPageBoundaries {
149 fn released_with_velocity(&mut self, position: f64, velocity: f64) {
150 self.target_position = position.round();
152
153 if velocity > 1.0 && self.target_position < position {
155 self.target_position += 1.0;
156 }
157 if velocity < -1.0 && self.target_position > position {
158 self.target_position -= 1.0;
159 }
160 }
161
162 fn next_position(&mut self, current_position: f64, elapsed_seconds: f64) -> f64 {
163 if self.is_stopped(current_position) {
164 return self.target_position;
165 }
166
167 let velocity = (self.target_position - current_position) * self.snap_speed;
168 let new_pos = current_position + velocity * elapsed_seconds;
169
170 if (current_position < self.target_position && new_pos > self.target_position)
172 || (current_position > self.target_position && new_pos < self.target_position)
173 {
174 self.target_position
175 } else {
176 new_pos
177 }
178 }
179
180 fn is_stopped(&self, position: f64) -> bool {
181 (self.target_position - position).abs() < 0.001
182 }
183}
184
185#[derive(Debug, Clone)]
207pub struct MomentumPosition<B: MomentumBehavior> {
208 position: f64,
209 grabbed_position: f64,
210 release_velocity: f64,
211 limits: (f64, f64),
212 last_drag_time: f64,
213 last_drag_position: f64,
214 is_dragging: bool,
215 is_animating: bool,
216 pub behavior: B,
218}
219
220impl<B: MomentumBehavior> MomentumPosition<B> {
221 pub const fn new(behavior: B) -> Self {
223 Self {
224 position: 0.0,
225 grabbed_position: 0.0,
226 release_velocity: 0.0,
227 limits: (f64::MIN, f64::MAX),
228 last_drag_time: 0.0,
229 last_drag_position: 0.0,
230 is_dragging: false,
231 is_animating: false,
232 behavior,
233 }
234 }
235
236 pub const fn set_limits(&mut self, min: f64, max: f64) {
238 self.limits = (min, max);
239 self.position = self.position.clamp(min, max);
240 }
241
242 pub const fn position(&self) -> f64 {
244 self.position
245 }
246
247 pub const fn set_position(&mut self, position: f64) {
249 self.position = position.clamp(self.limits.0, self.limits.1);
250 self.is_animating = false;
251 self.is_dragging = false;
252 }
253
254 pub const fn is_dragging(&self) -> bool {
256 self.is_dragging
257 }
258
259 pub const fn is_animating(&self) -> bool {
261 self.is_animating
262 }
263
264 pub const fn begin_drag(&mut self) {
266 self.grabbed_position = self.position;
267 self.release_velocity = 0.0;
268 self.last_drag_time = 0.0;
269 self.last_drag_position = self.position;
270 self.is_dragging = true;
271 self.is_animating = false;
272 }
273
274 pub fn drag(&mut self, delta: f64, elapsed_since_last: f64) {
279 let new_position = (self.grabbed_position + delta).clamp(self.limits.0, self.limits.1);
280
281 if elapsed_since_last > 0.005 {
283 let velocity = (new_position - self.last_drag_position) / elapsed_since_last;
284 if velocity.abs() > 0.2 {
286 self.release_velocity = velocity;
287 }
288 self.last_drag_position = new_position;
289 self.last_drag_time = elapsed_since_last;
290 }
291
292 self.position = new_position;
293 }
294
295 pub fn end_drag(&mut self) {
297 if self.is_dragging {
298 self.is_dragging = false;
299 self.behavior
300 .released_with_velocity(self.position, self.release_velocity);
301 self.is_animating = true;
302 }
303 }
304
305 pub fn nudge(&mut self, delta: f64) {
307 self.position = (self.position + delta).clamp(self.limits.0, self.limits.1);
308 self.behavior.released_with_velocity(self.position, 0.0);
309 self.is_animating = true;
310 }
311
312 pub fn update(&mut self, dt: f64) -> bool {
316 if self.is_dragging || !self.is_animating {
317 return false;
318 }
319
320 let new_position = self.behavior.next_position(self.position, dt);
321 let clamped = new_position.clamp(self.limits.0, self.limits.1);
322
323 if self.behavior.is_stopped(clamped) {
324 self.is_animating = false;
325 if (self.position - clamped).abs() > 0.0001 {
326 self.position = clamped;
327 return true;
328 }
329 return false;
330 }
331
332 if (self.position - clamped).abs() > 0.0001 {
333 self.position = clamped;
334 true
335 } else {
336 false
337 }
338 }
339}