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}