Skip to main content

dioxus_mind_elixir/
interact.rs

1//! Pure view coordinate transforms; pointer movement never clones the document.
2use crate::MindMapError;
3
4/// A point in CSS pixels.
5#[derive(Clone, Copy, Debug, Default, PartialEq)]
6pub struct Point {
7    /// Horizontal coordinate.
8    pub x: f64,
9    /// Vertical coordinate.
10    pub y: f64,
11}
12/// Per-view viewport, excluded from document serialization and history.
13#[derive(Clone, Copy, Debug, PartialEq)]
14pub struct Viewport {
15    /// Translation in screen CSS pixels.
16    pub translation: Point,
17    /// Positive magnification (bounded to 0.1–4).
18    pub scale: f64,
19}
20impl Default for Viewport {
21    fn default() -> Self {
22        Self {
23            translation: Point::default(),
24            scale: 1.0,
25        }
26    }
27}
28impl Viewport {
29    /// Convert view-local screen coordinates into unscaled canvas coordinates.
30    pub fn to_canvas(self, point: Point) -> Result<Point, MindMapError> {
31        self.validate()?;
32        if !point.x.is_finite() || !point.y.is_finite() {
33            return Err(MindMapError::InvalidGeometry);
34        }
35        let result = Point {
36            x: (point.x - self.translation.x) / self.scale,
37            y: (point.y - self.translation.y) / self.scale,
38        };
39        if !result.x.is_finite() || !result.y.is_finite() {
40            return Err(MindMapError::InvalidGeometry);
41        }
42        Ok(result)
43    }
44    /// Zoom around a view-local anchor, preserving its canvas point.
45    pub fn zoom_at(&mut self, scale: f64, anchor: Point) -> Result<(), MindMapError> {
46        let canvas = self.to_canvas(anchor)?;
47        if !scale.is_finite() {
48            return Err(MindMapError::InvalidGeometry);
49        }
50        let scale = scale.clamp(0.1, 4.0);
51        let translation = Point {
52            x: anchor.x - canvas.x * scale,
53            y: anchor.y - canvas.y * scale,
54        };
55        if !translation.x.is_finite() || !translation.y.is_finite() {
56            return Err(MindMapError::InvalidGeometry);
57        }
58        self.translation = translation;
59        self.scale = scale;
60        Ok(())
61    }
62    /// Translate by a finite screen-space delta.
63    pub fn pan(&mut self, delta: Point) -> Result<(), MindMapError> {
64        self.validate()?;
65        let translation = Point {
66            x: self.translation.x + delta.x,
67            y: self.translation.y + delta.y,
68        };
69        if !translation.x.is_finite() || !translation.y.is_finite() {
70            return Err(MindMapError::InvalidGeometry);
71        }
72        self.translation = translation;
73        Ok(())
74    }
75    /// Center a canvas-space point in the specified view dimensions.
76    pub fn center(&mut self, target: Point, size: Point) -> Result<(), MindMapError> {
77        self.validate()?;
78        if ![target.x, target.y, size.x, size.y]
79            .into_iter()
80            .all(f64::is_finite)
81            || size.x <= 0.0
82            || size.y <= 0.0
83        {
84            return Err(MindMapError::InvalidGeometry);
85        }
86        let translation = Point {
87            x: size.x / 2.0 - target.x * self.scale,
88            y: size.y / 2.0 - target.y * self.scale,
89        };
90        if !translation.x.is_finite() || !translation.y.is_finite() {
91            return Err(MindMapError::InvalidGeometry);
92        }
93        self.translation = translation;
94        Ok(())
95    }
96    /// Check public fields before applying a transform.
97    pub fn validate(self) -> Result<(), MindMapError> {
98        if [self.translation.x, self.translation.y, self.scale]
99            .into_iter()
100            .all(f64::is_finite)
101            && (0.1..=4.0).contains(&self.scale)
102        {
103            Ok(())
104        } else {
105            Err(MindMapError::InvalidGeometry)
106        }
107    }
108}