use super::types::{Point, Rect};
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct ModifierCtx {
pub container: Option<Rect>,
pub element: Option<Rect>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DragModifier {
LockAxis {
horizontal: bool,
vertical: bool,
},
Snap { x: f64, y: f64 },
KeepInside,
}
impl DragModifier {
pub fn apply(self, p: Point, ctx: &ModifierCtx) -> Point {
match self {
DragModifier::LockAxis {
horizontal,
vertical,
} => Point::new(
if horizontal { p.x } else { 0.0 },
if vertical { p.y } else { 0.0 },
),
DragModifier::Snap { x, y } => Point::new(snap(p.x, x), snap(p.y, y)),
DragModifier::KeepInside => {
let (Some(c), Some(e)) = (ctx.container, ctx.element) else {
return p;
};
Point::new(
clamp_axis(p.x, c.x, c.x + c.width - e.width),
clamp_axis(p.y, c.y, c.y + c.height - e.height),
)
}
}
}
}
pub fn apply_modifiers(chain: &[DragModifier], mut p: Point, ctx: &ModifierCtx) -> Point {
for m in chain {
p = m.apply(p, ctx);
}
p
}
fn snap(v: f64, step: f64) -> f64 {
if step > 0.0 {
(v / step).round() * step
} else {
v
}
}
fn clamp_axis(v: f64, min: f64, max: f64) -> f64 {
if min > max {
min } else {
v.clamp(min, max)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn modifiers_compose_in_order() {
let ctx = ModifierCtx {
container: Some(Rect::new(0.0, 0.0, 100.0, 100.0)),
element: Some(Rect::new(0.0, 0.0, 30.0, 30.0)),
};
let chain = [
DragModifier::LockAxis {
horizontal: false,
vertical: true,
},
DragModifier::Snap { x: 0.0, y: 20.0 },
DragModifier::KeepInside,
];
let p = apply_modifiers(&chain, Point::new(55.0, 91.0), &ctx);
assert_eq!((p.x, p.y), (0.0, 70.0)); }
#[test]
fn keep_inside_pins_oversized_elements() {
let ctx = ModifierCtx {
container: Some(Rect::new(10.0, 10.0, 50.0, 50.0)),
element: Some(Rect::new(0.0, 0.0, 200.0, 20.0)), };
let p = DragModifier::KeepInside.apply(Point::new(-40.0, 100.0), &ctx);
assert_eq!((p.x, p.y), (10.0, 40.0));
}
#[test]
fn keep_inside_without_rects_is_noop() {
let p = DragModifier::KeepInside.apply(Point::new(7.0, 8.0), &ModifierCtx::default());
assert_eq!((p.x, p.y), (7.0, 8.0));
}
}