#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MagnetState {
pub offset_x: f32,
pub offset_y: f32,
pub is_active: bool,
}
impl Default for MagnetState {
fn default() -> Self {
Self {
offset_x: 0.0,
offset_y: 0.0,
is_active: false,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Rect {
pub left: f32,
pub top: f32,
pub width: f32,
pub height: f32,
}
impl Rect {
pub fn center(&self) -> (f32, f32) {
(self.left + self.width / 2.0, self.top + self.height / 2.0)
}
}
pub struct Magnet {
pub padding: f32,
pub strength: f32,
pub disabled: bool,
}
impl Default for Magnet {
fn default() -> Self {
Self {
padding: 100.0,
strength: 2.0,
disabled: false,
}
}
}
impl Magnet {
pub fn new() -> Self {
Self::default()
}
pub fn with_padding(mut self, padding: f32) -> Self {
self.padding = padding;
self
}
pub fn with_strength(mut self, strength: f32) -> Self {
self.strength = strength;
self
}
pub fn with_disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn update(&self, cursor_x: f32, cursor_y: f32, element_rect: Rect) -> MagnetState {
if self.disabled {
return MagnetState::default();
}
let (center_x, center_y) = element_rect.center();
let half_width = element_rect.width / 2.0;
let half_height = element_rect.height / 2.0;
let dist_x = (center_x - cursor_x).abs();
let dist_y = (center_y - cursor_y).abs();
let is_active = dist_x < half_width + self.padding && dist_y < half_height + self.padding;
if is_active {
let offset_x = (cursor_x - center_x) / self.strength;
let offset_y = (cursor_y - center_y) / self.strength;
MagnetState {
offset_x,
offset_y,
is_active: true,
}
} else {
MagnetState {
offset_x: 0.0,
offset_y: 0.0,
is_active: false,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_magnet_within_bounds() {
let magnet = Magnet::new().with_strength(2.0).with_padding(100.0);
let rect = Rect {
left: 100.0,
top: 100.0,
width: 200.0,
height: 200.0,
};
let state = magnet.update(200.0, 200.0, rect);
assert!(state.is_active);
assert_eq!(state.offset_x, 0.0);
assert_eq!(state.offset_y, 0.0);
let state = magnet.update(250.0, 250.0, rect);
assert!(state.is_active);
assert_eq!(state.offset_x, 25.0);
assert_eq!(state.offset_y, 25.0);
let state = magnet.update(150.0, 150.0, rect);
assert!(state.is_active);
assert_eq!(state.offset_x, -25.0);
assert_eq!(state.offset_y, -25.0);
}
#[test]
fn test_magnet_outside_bounds() {
let magnet = Magnet::new().with_padding(50.0);
let rect = Rect {
left: 100.0,
top: 100.0,
width: 100.0,
height: 100.0,
};
let state = magnet.update(500.0, 500.0, rect);
assert!(!state.is_active);
assert_eq!(state.offset_x, 0.0);
assert_eq!(state.offset_y, 0.0);
}
#[test]
fn test_magnet_disabled() {
let magnet = Magnet::new().with_disabled(true);
let rect = Rect {
left: 0.0,
top: 0.0,
width: 100.0,
height: 100.0,
};
let state = magnet.update(50.0, 50.0, rect);
assert!(!state.is_active);
assert_eq!(state.offset_x, 0.0);
assert_eq!(state.offset_y, 0.0);
}
#[test]
fn test_magnet_strength() {
let magnet_weak = Magnet::new().with_strength(4.0);
let magnet_strong = Magnet::new().with_strength(1.0);
let rect = Rect {
left: 0.0,
top: 0.0,
width: 100.0,
height: 100.0,
};
let state_weak = magnet_weak.update(60.0, 50.0, rect);
let state_strong = magnet_strong.update(60.0, 50.0, rect);
assert_eq!(state_weak.offset_x, 2.5);
assert_eq!(state_strong.offset_x, 10.0);
}
}