1#[cfg(any(feature = "std", feature = "alloc"))]
4use crate::inertia::{InertiaBounds, InertiaConfig, InertiaN};
5
6#[derive(Clone, Copy, Debug, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct PointerData {
10 pub x: f32,
12 pub y: f32,
14 pub pressure: f32,
16 pub pointer_id: u64,
18}
19
20impl PointerData {
21 pub fn new(x: f32, y: f32, pointer_id: u64) -> Self {
23 Self {
24 x,
25 y,
26 pressure: 1.0,
27 pointer_id,
28 }
29 }
30
31 pub fn position(&self) -> [f32; 2] {
33 [self.x, self.y]
34 }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub enum DragAxis {
41 Both,
43 X,
45 Y,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub struct DragConstraints {
53 pub min_x: Option<f32>,
55 pub max_x: Option<f32>,
57 pub min_y: Option<f32>,
59 pub max_y: Option<f32>,
61 pub grid_snap: Option<f32>,
63}
64
65impl Default for DragConstraints {
66 fn default() -> Self {
67 Self::unbounded()
68 }
69}
70
71impl DragConstraints {
72 pub fn unbounded() -> Self {
74 Self {
75 min_x: None,
76 max_x: None,
77 min_y: None,
78 max_y: None,
79 grid_snap: None,
80 }
81 }
82
83 pub fn bounded(min_x: f32, max_x: f32, min_y: f32, max_y: f32) -> Self {
85 Self {
86 min_x: Some(min_x),
87 max_x: Some(max_x),
88 min_y: Some(min_y),
89 max_y: Some(max_y),
90 grid_snap: None,
91 }
92 }
93
94 pub fn with_grid_snap(mut self, grid: f32) -> Self {
96 self.grid_snap = if grid.is_finite() && grid > 0.0 {
97 Some(grid)
98 } else {
99 None
100 };
101 self
102 }
103
104 pub fn constrain(
106 &self,
107 position: [f32; 2],
108 axis: DragAxis,
109 locked_origin: [f32; 2],
110 ) -> [f32; 2] {
111 let mut x = finite_or_zero(position[0]);
112 let mut y = finite_or_zero(position[1]);
113
114 match axis {
115 DragAxis::Both => {}
116 DragAxis::X => y = locked_origin[1],
117 DragAxis::Y => x = locked_origin[0],
118 }
119
120 x = clamp_optional(x, self.min_x, self.max_x);
121 y = clamp_optional(y, self.min_y, self.max_y);
122
123 if let Some(grid) = self.grid_snap {
124 x = snap(x, grid);
125 y = snap(y, grid);
126 x = clamp_optional(x, self.min_x, self.max_x);
127 y = clamp_optional(y, self.min_y, self.max_y);
128 }
129
130 [x, y]
131 }
132}
133
134#[cfg(any(feature = "std", feature = "alloc"))]
139#[derive(Clone, Debug)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
141pub struct DragState {
142 position: [f32; 2],
143 start_position: [f32; 2],
144 start_pointer: [f32; 2],
145 last_position: [f32; 2],
146 velocity: [f32; 2],
147 active_pointer_id: Option<u64>,
148 axis: DragAxis,
149 constraints: DragConstraints,
150 inertia_config: InertiaConfig<[f32; 2]>,
151 velocity_smoothing: f32,
152}
153
154#[cfg(any(feature = "std", feature = "alloc"))]
155impl DragState {
156 pub fn new(position: [f32; 2]) -> Self {
158 Self {
159 position,
160 start_position: position,
161 start_pointer: [0.0, 0.0],
162 last_position: position,
163 velocity: [0.0, 0.0],
164 active_pointer_id: None,
165 axis: DragAxis::Both,
166 constraints: DragConstraints::unbounded(),
167 inertia_config: InertiaConfig::new(1400.0, 2.0),
168 velocity_smoothing: 0.35,
169 }
170 }
171
172 pub fn axis(mut self, axis: DragAxis) -> Self {
174 self.axis = axis;
175 self
176 }
177
178 pub fn constraints(mut self, constraints: DragConstraints) -> Self {
180 self.constraints = constraints;
181 self
182 }
183
184 pub fn set_constraints(&mut self, constraints: DragConstraints) {
186 self.constraints = constraints;
187 self.position = self
188 .constraints
189 .constrain(self.position, self.axis, self.position);
190 self.last_position = self.position;
191 }
192
193 pub fn inertia_config(mut self, config: InertiaConfig<[f32; 2]>) -> Self {
195 self.inertia_config = config;
196 self
197 }
198
199 pub fn velocity_smoothing(mut self, smoothing: f32) -> Self {
201 self.velocity_smoothing = smoothing.clamp(0.0, 1.0);
202 self
203 }
204
205 pub fn position(&self) -> [f32; 2] {
207 self.position
208 }
209
210 pub fn velocity(&self) -> [f32; 2] {
212 self.velocity
213 }
214
215 pub fn snap_to(&mut self, position: [f32; 2]) {
217 self.active_pointer_id = None;
218 self.velocity = [0.0, 0.0];
219 self.position = self
220 .constraints
221 .constrain(position, self.axis, self.position);
222 self.start_position = self.position;
223 self.last_position = self.position;
224 }
225
226 pub fn is_dragging(&self) -> bool {
228 self.active_pointer_id.is_some()
229 }
230
231 pub fn active_pointer_id(&self) -> Option<u64> {
233 self.active_pointer_id
234 }
235
236 pub fn on_pointer_down(&mut self, data: PointerData) {
238 self.active_pointer_id = Some(data.pointer_id);
239 self.start_pointer = data.position();
240 self.start_position = self.position;
241 self.last_position = self.position;
242 self.velocity = [0.0, 0.0];
243 }
244
245 pub fn on_pointer_move(&mut self, data: PointerData, dt: f32) {
247 if self.active_pointer_id != Some(data.pointer_id) {
248 return;
249 }
250
251 let delta = [
252 data.x - self.start_pointer[0],
253 data.y - self.start_pointer[1],
254 ];
255 let raw_position = [
256 self.start_position[0] + delta[0],
257 self.start_position[1] + delta[1],
258 ];
259 let constrained = self
260 .constraints
261 .constrain(raw_position, self.axis, self.start_position);
262
263 let dt = dt.max(0.0);
264 if dt > 0.0 {
265 let instant = [
266 (constrained[0] - self.last_position[0]) / dt,
267 (constrained[1] - self.last_position[1]) / dt,
268 ];
269 let alpha = self.velocity_smoothing;
270 self.velocity = [
271 self.velocity[0] * (1.0 - alpha) + instant[0] * alpha,
272 self.velocity[1] * (1.0 - alpha) + instant[1] * alpha,
273 ];
274 }
275
276 self.position = constrained;
277 self.last_position = constrained;
278 }
279
280 pub fn on_pointer_up(&mut self, data: PointerData) -> Option<InertiaN<[f32; 2]>> {
282 if self.active_pointer_id != Some(data.pointer_id) {
283 return None;
284 }
285
286 self.active_pointer_id = None;
287 let velocity = match self.axis {
288 DragAxis::Both => self.velocity,
289 DragAxis::X => [self.velocity[0], 0.0],
290 DragAxis::Y => [0.0, self.velocity[1]],
291 };
292
293 if velocity[0].abs() <= self.inertia_config.min_velocity
294 && velocity[1].abs() <= self.inertia_config.min_velocity
295 {
296 self.velocity = [0.0, 0.0];
297 return None;
298 }
299
300 let mut config = self.inertia_config.clone();
301 config.bounds = self.bounds_for_inertia();
302 let mut inertia = InertiaN::new(config, self.position);
303 inertia.kick(velocity);
304 Some(inertia)
305 }
306
307 fn bounds_for_inertia(&self) -> Option<InertiaBounds<[f32; 2]>> {
308 match (
309 self.constraints.min_x,
310 self.constraints.max_x,
311 self.constraints.min_y,
312 self.constraints.max_y,
313 ) {
314 (Some(min_x), Some(max_x), Some(min_y), Some(max_y)) => {
315 Some(InertiaBounds::new([min_x, min_y], [max_x, max_y]))
316 }
317 _ => self.inertia_config.bounds.clone(),
318 }
319 }
320}
321
322#[inline]
323fn finite_or_zero(value: f32) -> f32 {
324 if value.is_finite() { value } else { 0.0 }
325}
326
327#[inline]
328fn clamp_optional(value: f32, min: Option<f32>, max: Option<f32>) -> f32 {
329 match (min, max) {
330 (Some(a), Some(b)) => value.clamp(a.min(b), a.max(b)),
331 (Some(min), None) => value.max(min),
332 (None, Some(max)) => value.min(max),
333 (None, None) => value,
334 }
335}
336
337#[inline]
338fn snap(value: f32, grid: f32) -> f32 {
339 if grid > 0.0 {
340 libm::roundf(value / grid) * grid
341 } else {
342 value
343 }
344}
345
346#[cfg(all(test, any(feature = "std", feature = "alloc")))]
347mod tests {
348 use super::*;
349
350 #[cfg(any(feature = "std", feature = "alloc"))]
351 #[test]
352 fn drag_respects_axis_and_constraints() {
353 let mut drag = DragState::new([0.0, 5.0])
354 .axis(DragAxis::X)
355 .constraints(DragConstraints::bounded(-10.0, 10.0, -10.0, 10.0));
356
357 drag.on_pointer_down(PointerData::new(0.0, 0.0, 1));
358 drag.on_pointer_move(PointerData::new(30.0, 40.0, 1), 0.016);
359
360 assert_eq!(drag.position(), [10.0, 5.0]);
361 }
362
363 #[cfg(any(feature = "std", feature = "alloc"))]
364 #[test]
365 fn drag_ignores_wrong_pointer_id() {
366 let mut drag = DragState::new([0.0, 0.0]);
367 drag.on_pointer_down(PointerData::new(0.0, 0.0, 7));
368 drag.on_pointer_move(PointerData::new(20.0, 0.0, 8), 0.016);
369 assert_eq!(drag.position(), [0.0, 0.0]);
370 }
371
372 #[cfg(any(feature = "std", feature = "alloc"))]
373 #[test]
374 fn drag_estimates_velocity_with_ema() {
375 let mut drag = DragState::new([0.0, 0.0]).velocity_smoothing(1.0);
376 drag.on_pointer_down(PointerData::new(0.0, 0.0, 1));
377 drag.on_pointer_move(PointerData::new(16.0, 0.0, 1), 0.016);
378 assert!((drag.velocity()[0] - 1000.0).abs() < 0.01);
379 assert_eq!(drag.velocity()[1], 0.0);
380 }
381
382 #[cfg(any(feature = "std", feature = "alloc"))]
383 #[test]
384 fn grid_snap_applies_to_position() {
385 let mut drag = DragState::new([0.0, 0.0])
386 .constraints(DragConstraints::unbounded().with_grid_snap(10.0));
387 drag.on_pointer_down(PointerData::new(0.0, 0.0, 1));
388 drag.on_pointer_move(PointerData::new(16.0, 24.0, 1), 0.016);
389 assert_eq!(drag.position(), [20.0, 20.0]);
390 }
391
392 #[cfg(any(feature = "std", feature = "alloc"))]
393 #[test]
394 fn snap_to_and_updated_constraints_clamp_position() {
395 let mut drag = DragState::new([0.0, 0.0])
396 .constraints(DragConstraints::bounded(-10.0, 10.0, -8.0, 8.0));
397
398 drag.snap_to([40.0, -30.0]);
399 assert_eq!(drag.position(), [10.0, -8.0]);
400
401 drag.set_constraints(DragConstraints::bounded(-4.0, 4.0, -3.0, 3.0));
402 assert_eq!(drag.position(), [4.0, -3.0]);
403 assert!(!drag.is_dragging());
404 assert_eq!(drag.velocity(), [0.0, 0.0]);
405 }
406}