use crate::inertia::{InertiaConfig, InertiaN};
#[derive(Debug, Clone, Default)]
pub struct PointerData {
pub x: f32,
pub y: f32,
pub pressure: f32,
pub pointer_id: i32,
}
#[derive(Debug, Clone, Default)]
pub struct DragConstraints {
pub bounds: Option<[f32; 4]>,
pub axis_lock: Option<DragAxis>,
pub snap_to_grid: Option<[f32; 2]>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DragAxis {
X,
Y,
}
#[derive(Debug, Clone)]
pub struct DragState {
position: [f32; 2],
velocity: [f32; 2],
dragging: bool,
start_pointer: [f32; 2],
start_position: [f32; 2],
last_pointer: [f32; 2],
constraints: DragConstraints,
}
impl DragState {
pub fn new() -> Self {
Self {
position: [0.0, 0.0],
velocity: [0.0, 0.0],
dragging: false,
start_pointer: [0.0, 0.0],
start_position: [0.0, 0.0],
last_pointer: [0.0, 0.0],
constraints: DragConstraints::default(),
}
}
pub fn with_constraints(mut self, constraints: DragConstraints) -> Self {
self.constraints = constraints;
self
}
pub fn with_position(mut self, position: [f32; 2]) -> Self {
self.position = position;
self
}
pub fn on_pointer_down(&mut self, x: f32, y: f32) {
self.dragging = true;
self.start_pointer = [x, y];
self.start_position = self.position;
self.last_pointer = [x, y];
self.velocity = [0.0, 0.0];
}
pub fn on_pointer_move(&mut self, x: f32, y: f32, dt: f32) {
if !self.dragging {
return;
}
let dx = x - self.start_pointer[0];
let dy = y - self.start_pointer[1];
let mut new_pos = [
self.start_position[0] + dx,
self.start_position[1] + dy,
];
new_pos = self.apply_constraints(new_pos);
if dt > 1e-6 {
let inst_vx = (x - self.last_pointer[0]) / dt;
let inst_vy = (y - self.last_pointer[1]) / dt;
self.velocity[0] = 0.8 * inst_vx + 0.2 * self.velocity[0];
self.velocity[1] = 0.8 * inst_vy + 0.2 * self.velocity[1];
}
self.position = new_pos;
self.last_pointer = [x, y];
}
pub fn on_pointer_up(&mut self) -> InertiaN<[f32; 2]> {
self.dragging = false;
InertiaN::new(InertiaConfig::default_flick(), self.position)
.with_velocity(self.velocity)
}
pub fn position(&self) -> [f32; 2] {
self.position
}
pub fn velocity(&self) -> [f32; 2] {
self.velocity
}
pub fn is_dragging(&self) -> bool {
self.dragging
}
fn apply_constraints(&self, mut pos: [f32; 2]) -> [f32; 2] {
if let Some(axis) = &self.constraints.axis_lock {
match axis {
DragAxis::X => pos[1] = self.start_position[1],
DragAxis::Y => pos[0] = self.start_position[0],
}
}
if let Some(bounds) = &self.constraints.bounds {
pos[0] = pos[0].clamp(bounds[0], bounds[2]);
pos[1] = pos[1].clamp(bounds[1], bounds[3]);
}
if let Some(grid) = &self.constraints.snap_to_grid {
if grid[0] > 0.0 {
pos[0] = (pos[0] / grid[0]).round() * grid[0];
}
if grid[1] > 0.0 {
pos[1] = (pos[1] / grid[1]).round() * grid[1];
}
}
pos
}
}
impl Default for DragState {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::Update;
#[test]
fn drag_basic_movement() {
let mut drag = DragState::new().with_position([100.0, 100.0]);
drag.on_pointer_down(50.0, 50.0);
drag.on_pointer_move(70.0, 60.0, 1.0 / 60.0);
assert_eq!(drag.position(), [120.0, 110.0]);
}
#[test]
fn drag_axis_lock_x() {
let mut drag = DragState::new()
.with_constraints(DragConstraints {
axis_lock: Some(DragAxis::X),
..Default::default()
});
drag.on_pointer_down(0.0, 0.0);
drag.on_pointer_move(50.0, 30.0, 1.0 / 60.0);
let pos = drag.position();
assert!((pos[0] - 50.0).abs() < 1e-6);
assert!((pos[1]).abs() < 1e-6, "Y should be locked: {}", pos[1]);
}
#[test]
fn drag_axis_lock_y() {
let mut drag = DragState::new()
.with_constraints(DragConstraints {
axis_lock: Some(DragAxis::Y),
..Default::default()
});
drag.on_pointer_down(0.0, 0.0);
drag.on_pointer_move(50.0, 30.0, 1.0 / 60.0);
let pos = drag.position();
assert!((pos[0]).abs() < 1e-6, "X should be locked: {}", pos[0]);
assert!((pos[1] - 30.0).abs() < 1e-6);
}
#[test]
fn drag_bounds_clamping() {
let mut drag = DragState::new()
.with_constraints(DragConstraints {
bounds: Some([0.0, 0.0, 100.0, 100.0]),
..Default::default()
});
drag.on_pointer_down(50.0, 50.0);
drag.on_pointer_move(200.0, 200.0, 1.0 / 60.0);
let pos = drag.position();
assert!(pos[0] <= 100.0, "X should be clamped: {}", pos[0]);
assert!(pos[1] <= 100.0, "Y should be clamped: {}", pos[1]);
}
#[test]
fn drag_grid_snapping() {
let mut drag = DragState::new()
.with_constraints(DragConstraints {
snap_to_grid: Some([10.0, 10.0]),
..Default::default()
});
drag.on_pointer_down(0.0, 0.0);
drag.on_pointer_move(17.0, 23.0, 1.0 / 60.0);
let pos = drag.position();
assert!((pos[0] - 20.0).abs() < 1e-6, "X should snap to 20: {}", pos[0]);
assert!((pos[1] - 20.0).abs() < 1e-6, "Y should snap to 20: {}", pos[1]);
}
#[test]
fn drag_velocity_tracking() {
let mut drag = DragState::new();
drag.on_pointer_down(0.0, 0.0);
drag.on_pointer_move(100.0, 0.0, 1.0 / 60.0);
let vel = drag.velocity();
assert!(vel[0] > 1000.0, "Expected large X velocity: {}", vel[0]);
}
#[test]
fn drag_pointer_up_returns_inertia() {
let mut drag = DragState::new();
drag.on_pointer_down(0.0, 0.0);
drag.on_pointer_move(50.0, 0.0, 1.0 / 60.0);
let mut inertia = drag.on_pointer_up();
assert!(!drag.is_dragging());
let pos_before = inertia.position();
inertia.update(1.0 / 60.0);
let pos_after = inertia.position();
assert!(pos_after[0] > pos_before[0], "Inertia should continue moving");
}
#[test]
fn drag_not_dragging_ignores_moves() {
let mut drag = DragState::new();
drag.on_pointer_move(100.0, 100.0, 1.0 / 60.0);
assert_eq!(drag.position(), [0.0, 0.0]);
}
}