Skip to main content

open_gpui_canvas/
routing.rs

1use crate::document::{CanvasEdge, CanvasEdgeRouteKind};
2use open_gpui::{Bounds, Pixels, Point};
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Copy, Debug, PartialEq)]
6pub struct CanvasRouteRequest<'a> {
7    pub edge: &'a CanvasEdge,
8    pub source: Point<Pixels>,
9    pub target: Point<Pixels>,
10}
11
12#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
13pub struct CanvasRoutePath {
14    #[serde(default)]
15    pub segments: Vec<CanvasRouteSegment>,
16}
17
18impl CanvasRoutePath {
19    pub fn new(segments: impl IntoIterator<Item = CanvasRouteSegment>) -> Self {
20        Self {
21            segments: segments.into_iter().collect(),
22        }
23    }
24
25    pub fn polyline(points: impl IntoIterator<Item = Point<Pixels>>) -> Self {
26        let points = points.into_iter().collect::<Vec<_>>();
27        Self::new(points.windows(2).map(|points| CanvasRouteSegment::Line {
28            from: points[0],
29            to: points[1],
30        }))
31    }
32
33    pub fn orthogonal(points: impl IntoIterator<Item = Point<Pixels>>) -> Self {
34        let points = points.into_iter().collect::<Vec<_>>();
35        let mut orthogonal_points = Vec::new();
36
37        for points in points.windows(2) {
38            append_orthogonal_leg(&mut orthogonal_points, points[0], points[1]);
39        }
40
41        Self::polyline(orthogonal_points)
42    }
43
44    pub fn cubic_bezier(
45        from: Point<Pixels>,
46        control_1: Point<Pixels>,
47        control_2: Point<Pixels>,
48        to: Point<Pixels>,
49    ) -> Self {
50        Self::new([CanvasRouteSegment::CubicBezier {
51            from,
52            control_1,
53            control_2,
54            to,
55        }])
56    }
57
58    pub fn is_empty(&self) -> bool {
59        self.segments.is_empty()
60    }
61
62    pub fn document_points(&self) -> Vec<Point<Pixels>> {
63        let mut points = Vec::new();
64        for segment in &self.segments {
65            let start = segment.start();
66            if points.last().copied() != Some(start) {
67                points.push(start);
68            }
69            points.push(segment.end());
70        }
71        points
72    }
73
74    pub fn bounds(&self) -> Option<Bounds<Pixels>> {
75        let mut points = self
76            .segments
77            .iter()
78            .flat_map(CanvasRouteSegment::bounds_points);
79        let first = points.next()?;
80        let (min_x, min_y, max_x, max_y) = points.fold(
81            (first.x, first.y, first.x, first.y),
82            |(min_x, min_y, max_x, max_y), point| {
83                (
84                    min_x.min(point.x),
85                    min_y.min(point.y),
86                    max_x.max(point.x),
87                    max_y.max(point.y),
88                )
89            },
90        );
91        Some(Bounds::from_corners(
92            Point::new(min_x, min_y),
93            Point::new(max_x, max_y),
94        ))
95    }
96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
99pub enum CanvasRouteSegment {
100    Line {
101        from: Point<Pixels>,
102        to: Point<Pixels>,
103    },
104    CubicBezier {
105        from: Point<Pixels>,
106        control_1: Point<Pixels>,
107        control_2: Point<Pixels>,
108        to: Point<Pixels>,
109    },
110}
111
112impl CanvasRouteSegment {
113    pub fn start(&self) -> Point<Pixels> {
114        match self {
115            Self::Line { from, .. } | Self::CubicBezier { from, .. } => *from,
116        }
117    }
118
119    pub fn end(&self) -> Point<Pixels> {
120        match self {
121            Self::Line { to, .. } | Self::CubicBezier { to, .. } => *to,
122        }
123    }
124
125    pub fn bounds_points(&self) -> [Point<Pixels>; 4] {
126        match self {
127            Self::Line { from, to } => [*from, *to, *from, *to],
128            Self::CubicBezier {
129                from,
130                control_1,
131                control_2,
132                to,
133            } => [*from, *control_1, *control_2, *to],
134        }
135    }
136}
137
138pub trait CanvasEdgeRouter {
139    fn route_edge(&self, request: CanvasRouteRequest<'_>) -> CanvasRoutePath;
140}
141
142impl<T> CanvasEdgeRouter for &T
143where
144    T: CanvasEdgeRouter + ?Sized,
145{
146    fn route_edge(&self, request: CanvasRouteRequest<'_>) -> CanvasRoutePath {
147        (*self).route_edge(request)
148    }
149}
150
151#[derive(Clone, Copy, Debug, Default)]
152pub struct CanvasDefaultEdgeRouter;
153
154impl CanvasEdgeRouter for CanvasDefaultEdgeRouter {
155    fn route_edge(&self, request: CanvasRouteRequest<'_>) -> CanvasRoutePath {
156        let route = &request.edge.route;
157        if route.kind.as_str() == CanvasEdgeRouteKind::CUBIC_BEZIER
158            && route.control_points.len() >= 2
159        {
160            return CanvasRoutePath::cubic_bezier(
161                request.source,
162                route.control_points[0],
163                route.control_points[1],
164                request.target,
165            );
166        }
167
168        if route.kind.as_str() == CanvasEdgeRouteKind::CUBIC_BEZIER {
169            let (control_1, control_2) =
170                default_cubic_control_points(request.source, request.target);
171            return CanvasRoutePath::cubic_bezier(
172                request.source,
173                control_1,
174                control_2,
175                request.target,
176            );
177        }
178
179        if route.kind.as_str() == CanvasEdgeRouteKind::ORTHOGONAL {
180            return CanvasRoutePath::orthogonal(
181                [request.source]
182                    .into_iter()
183                    .chain(route.waypoints.iter().copied())
184                    .chain([request.target]),
185            );
186        }
187
188        CanvasRoutePath::polyline(
189            [request.source]
190                .into_iter()
191                .chain(route.waypoints.iter().copied())
192                .chain([request.target]),
193        )
194    }
195}
196
197fn default_cubic_control_points(
198    source: Point<Pixels>,
199    target: Point<Pixels>,
200) -> (Point<Pixels>, Point<Pixels>) {
201    let dx = target.x.as_f32() - source.x.as_f32();
202    let offset = (dx.abs() * 0.5)
203        .max(40.0)
204        .copysign(if dx == 0.0 { 1.0 } else { dx });
205    let offset = Pixels::from(offset);
206    (
207        Point::new(source.x + offset, source.y),
208        Point::new(target.x - offset, target.y),
209    )
210}
211
212fn append_orthogonal_leg(points: &mut Vec<Point<Pixels>>, from: Point<Pixels>, to: Point<Pixels>) {
213    push_unique_point(points, from);
214
215    if from.x != to.x && from.y != to.y {
216        let mid_x = (from.x + to.x) * 0.5;
217        push_unique_point(points, Point::new(mid_x, from.y));
218        push_unique_point(points, Point::new(mid_x, to.y));
219    }
220
221    push_unique_point(points, to);
222}
223
224fn push_unique_point(points: &mut Vec<Point<Pixels>>, point: Point<Pixels>) {
225    if points.last().copied() != Some(point) {
226        points.push(point);
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233    use crate::{CanvasEdge, CanvasEdgeRoute, CanvasEndpoint};
234    use open_gpui::{point, px};
235
236    #[test]
237    fn default_router_emits_polyline_segments_from_waypoints() {
238        let mut edge = CanvasEdge::new(
239            "edge",
240            CanvasEndpoint::new("a", None::<&str>),
241            CanvasEndpoint::new("b", None::<&str>),
242        );
243        edge.route = CanvasEdgeRoute::polyline([point(px(40.0), px(50.0))]);
244
245        let path = CanvasDefaultEdgeRouter.route_edge(CanvasRouteRequest {
246            edge: &edge,
247            source: point(px(0.0), px(0.0)),
248            target: point(px(100.0), px(0.0)),
249        });
250
251        assert_eq!(
252            path.segments,
253            vec![
254                CanvasRouteSegment::Line {
255                    from: point(px(0.0), px(0.0)),
256                    to: point(px(40.0), px(50.0)),
257                },
258                CanvasRouteSegment::Line {
259                    from: point(px(40.0), px(50.0)),
260                    to: point(px(100.0), px(0.0)),
261                },
262            ]
263        );
264        assert_eq!(
265            path.document_points(),
266            vec![
267                point(px(0.0), px(0.0)),
268                point(px(40.0), px(50.0)),
269                point(px(100.0), px(0.0)),
270            ]
271        );
272    }
273
274    #[test]
275    fn default_router_emits_cubic_segment_from_control_points() {
276        let mut edge = CanvasEdge::new(
277            "edge",
278            CanvasEndpoint::new("a", None::<&str>),
279            CanvasEndpoint::new("b", None::<&str>),
280        );
281        edge.route = CanvasEdgeRoute::new(CanvasEdgeRouteKind::CUBIC_BEZIER);
282        edge.route.control_points = vec![point(px(25.0), px(40.0)), point(px(75.0), px(-40.0))];
283
284        let path = CanvasDefaultEdgeRouter.route_edge(CanvasRouteRequest {
285            edge: &edge,
286            source: point(px(0.0), px(0.0)),
287            target: point(px(100.0), px(0.0)),
288        });
289
290        assert_eq!(
291            path.segments,
292            vec![CanvasRouteSegment::CubicBezier {
293                from: point(px(0.0), px(0.0)),
294                control_1: point(px(25.0), px(40.0)),
295                control_2: point(px(75.0), px(-40.0)),
296                to: point(px(100.0), px(0.0)),
297            }]
298        );
299        assert_eq!(
300            path.bounds().unwrap(),
301            Bounds::from_corners(point(px(0.0), px(-40.0)), point(px(100.0), px(40.0)))
302        );
303    }
304
305    #[test]
306    fn default_router_emits_cubic_segment_without_control_points() {
307        let mut edge = CanvasEdge::new(
308            "edge",
309            CanvasEndpoint::new("a", None::<&str>),
310            CanvasEndpoint::new("b", None::<&str>),
311        );
312        edge.route = CanvasEdgeRoute::new(CanvasEdgeRouteKind::CUBIC_BEZIER);
313
314        let path = CanvasDefaultEdgeRouter.route_edge(CanvasRouteRequest {
315            edge: &edge,
316            source: point(px(0.0), px(0.0)),
317            target: point(px(100.0), px(30.0)),
318        });
319
320        assert_eq!(path.segments.len(), 1);
321        assert!(matches!(
322            path.segments[0],
323            CanvasRouteSegment::CubicBezier { .. }
324        ));
325    }
326
327    #[test]
328    fn default_router_emits_orthogonal_segments() {
329        let mut edge = CanvasEdge::new(
330            "edge",
331            CanvasEndpoint::new("a", None::<&str>),
332            CanvasEndpoint::new("b", None::<&str>),
333        );
334        edge.route = CanvasEdgeRoute::new(CanvasEdgeRouteKind::ORTHOGONAL);
335
336        let path = CanvasDefaultEdgeRouter.route_edge(CanvasRouteRequest {
337            edge: &edge,
338            source: point(px(0.0), px(0.0)),
339            target: point(px(100.0), px(50.0)),
340        });
341
342        assert_eq!(
343            path.document_points(),
344            vec![
345                point(px(0.0), px(0.0)),
346                point(px(50.0), px(0.0)),
347                point(px(50.0), px(50.0)),
348                point(px(100.0), px(50.0)),
349            ]
350        );
351        assert_eq!(
352            path.segments,
353            vec![
354                CanvasRouteSegment::Line {
355                    from: point(px(0.0), px(0.0)),
356                    to: point(px(50.0), px(0.0)),
357                },
358                CanvasRouteSegment::Line {
359                    from: point(px(50.0), px(0.0)),
360                    to: point(px(50.0), px(50.0)),
361                },
362                CanvasRouteSegment::Line {
363                    from: point(px(50.0), px(50.0)),
364                    to: point(px(100.0), px(50.0)),
365                },
366            ]
367        );
368    }
369
370    #[test]
371    fn default_router_routes_orthogonal_waypoints_by_leg() {
372        let mut edge = CanvasEdge::new(
373            "edge",
374            CanvasEndpoint::new("a", None::<&str>),
375            CanvasEndpoint::new("b", None::<&str>),
376        );
377        edge.route = CanvasEdgeRoute::new(CanvasEdgeRouteKind::ORTHOGONAL);
378        edge.route.waypoints = vec![point(px(40.0), px(30.0))];
379
380        let path = CanvasDefaultEdgeRouter.route_edge(CanvasRouteRequest {
381            edge: &edge,
382            source: point(px(0.0), px(0.0)),
383            target: point(px(100.0), px(30.0)),
384        });
385
386        assert_eq!(
387            path.document_points(),
388            vec![
389                point(px(0.0), px(0.0)),
390                point(px(20.0), px(0.0)),
391                point(px(20.0), px(30.0)),
392                point(px(40.0), px(30.0)),
393                point(px(100.0), px(30.0)),
394            ]
395        );
396    }
397}