use crate::{LayoutDirection, MindMapError, Placement, Point};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HitRect {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
impl HitRect {
pub fn validate(self) -> Result<(), MindMapError> {
if [self.x, self.y, self.width, self.height]
.iter()
.all(|v| v.is_finite())
&& self.width > 0.0
&& self.height > 0.0
{
Ok(())
} else {
Err(MindMapError::InvalidGeometry)
}
}
pub fn contains(self, point: Point) -> bool {
point.x >= self.x
&& point.x <= self.x + self.width
&& point.y >= self.y
&& point.y <= self.y + self.height
}
pub fn intersects(self, start: Point, end: Point) -> bool {
self.x <= start.x.max(end.x)
&& self.x + self.width >= start.x.min(end.x)
&& self.y <= start.y.max(end.y)
&& self.y + self.height >= start.y.min(end.y)
}
}
pub fn is_drag(start: Point, current: Point) -> bool {
(current.x - start.x).hypot(current.y - start.y) >= 5.0
}
pub fn drop_placement(
id: &str,
rect: HitRect,
at: Point,
layout: LayoutDirection,
scale: f64,
) -> Result<Placement, MindMapError> {
rect.validate()?;
if !scale.is_finite() || scale <= 0.0 || !at.x.is_finite() || !at.y.is_finite() {
return Err(MindMapError::InvalidGeometry);
}
let (offset, length) = if layout == LayoutDirection::Down {
(at.x - rect.x, rect.width)
} else {
(at.y - rect.y, rect.height)
};
let band = (12.0 * scale).min(length / 3.0);
Ok(if offset < band {
Placement::Before(id.into())
} else if offset > length - band {
Placement::After(id.into())
} else {
Placement::In(id.into())
})
}
pub fn edge_pan(bounds: HitRect, at: Point) -> Result<Point, MindMapError> {
bounds.validate()?;
if !at.x.is_finite() || !at.y.is_finite() {
return Err(MindMapError::InvalidGeometry);
}
let axis = |v: f64, low: f64, length: f64| {
if v < low + 50.0 {
((low + 50.0 - v) / 50.0).clamp(0.0, 1.0) * 10.0
} else if v > low + length - 50.0 {
-((v - (low + length - 50.0)) / 50.0).clamp(0.0, 1.0) * 10.0
} else {
0.0
}
};
Ok(Point {
x: axis(at.x, bounds.x, bounds.width),
y: axis(at.y, bounds.y, bounds.height),
})
}