Skip to main content

dioxus_mind_elixir/plugins/
gesture.rs

1//! Platform-independent pointer hit testing; coordinates are CSS client pixels.
2use crate::{LayoutDirection, MindMapError, Placement, Point};
3
4/// Rectangle measured in client coordinates.
5#[derive(Clone, Copy, Debug, PartialEq)]
6pub struct HitRect {
7    /// Left edge.
8    pub x: f64,
9    /// Top edge.
10    pub y: f64,
11    /// Width.
12    pub width: f64,
13    /// Height.
14    pub height: f64,
15}
16impl HitRect {
17    /// Reject invalid measurements before hit testing.
18    pub fn validate(self) -> Result<(), MindMapError> {
19        if [self.x, self.y, self.width, self.height]
20            .iter()
21            .all(|v| v.is_finite())
22            && self.width > 0.0
23            && self.height > 0.0
24        {
25            Ok(())
26        } else {
27            Err(MindMapError::InvalidGeometry)
28        }
29    }
30    /// Whether the point is inside the rectangle.
31    pub fn contains(self, point: Point) -> bool {
32        point.x >= self.x
33            && point.x <= self.x + self.width
34            && point.y >= self.y
35            && point.y <= self.y + self.height
36    }
37    /// Whether the rectangle intersects a selection area, regardless of direction.
38    pub fn intersects(self, start: Point, end: Point) -> bool {
39        self.x <= start.x.max(end.x)
40            && self.x + self.width >= start.x.min(end.x)
41            && self.y <= start.y.max(end.y)
42            && self.y + self.height >= start.y.min(end.y)
43    }
44}
45/// The upstream five client-pixel threshold avoids moving nodes on clicks.
46pub fn is_drag(start: Point, current: Point) -> bool {
47    (current.x - start.x).hypot(current.y - start.y) >= 5.0
48}
49/// Resolve before/after bands (12 × zoom) and the central child target.
50/// LEFT/RIGHT/SIDE siblings are vertical; DOWN siblings are horizontal.
51pub fn drop_placement(
52    id: &str,
53    rect: HitRect,
54    at: Point,
55    layout: LayoutDirection,
56    scale: f64,
57) -> Result<Placement, MindMapError> {
58    rect.validate()?;
59    if !scale.is_finite() || scale <= 0.0 || !at.x.is_finite() || !at.y.is_finite() {
60        return Err(MindMapError::InvalidGeometry);
61    }
62    let (offset, length) = if layout == LayoutDirection::Down {
63        (at.x - rect.x, rect.width)
64    } else {
65        (at.y - rect.y, rect.height)
66    };
67    let band = (12.0 * scale).min(length / 3.0);
68    Ok(if offset < band {
69        Placement::Before(id.into())
70    } else if offset > length - band {
71        Placement::After(id.into())
72    } else {
73        Placement::In(id.into())
74    })
75}
76/// Per-pointer-step edge pan within a 50px band, independent of document history.
77pub fn edge_pan(bounds: HitRect, at: Point) -> Result<Point, MindMapError> {
78    bounds.validate()?;
79    if !at.x.is_finite() || !at.y.is_finite() {
80        return Err(MindMapError::InvalidGeometry);
81    }
82    let axis = |v: f64, low: f64, length: f64| {
83        if v < low + 50.0 {
84            ((low + 50.0 - v) / 50.0).clamp(0.0, 1.0) * 10.0
85        } else if v > low + length - 50.0 {
86            -((v - (low + length - 50.0)) / 50.0).clamp(0.0, 1.0) * 10.0
87        } else {
88            0.0
89        }
90    };
91    Ok(Point {
92        x: axis(at.x, bounds.x, bounds.width),
93        y: axis(at.y, bounds.y, bounds.height),
94    })
95}