1use crate::{CubicBezier, QuadraticBezier};
6use glam::Vec2;
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum PathCommand {
11 MoveTo(Vec2),
13 LineTo(Vec2),
15 QuadTo {
17 control: Vec2,
19 to: Vec2,
21 },
22 CubicTo {
24 control1: Vec2,
26 control2: Vec2,
28 to: Vec2,
30 },
31 ArcTo {
33 radii: Vec2,
35 x_rotation: f32,
37 large_arc: bool,
39 sweep: bool,
41 to: Vec2,
43 },
44 Close,
46}
47
48#[derive(Debug, Clone, Default, PartialEq)]
50pub struct Path {
51 commands: Vec<PathCommand>,
52}
53
54impl Path {
55 pub fn new() -> Self {
57 Self::default()
58 }
59
60 pub fn from_commands(commands: Vec<PathCommand>) -> Self {
62 Self { commands }
63 }
64
65 pub fn commands(&self) -> &[PathCommand] {
67 &self.commands
68 }
69
70 pub fn is_empty(&self) -> bool {
72 self.commands.is_empty()
73 }
74
75 pub fn len(&self) -> usize {
77 self.commands.len()
78 }
79
80 pub fn bounds(&self) -> Option<(Vec2, Vec2)> {
84 if self.commands.is_empty() {
85 return None;
86 }
87
88 let mut min = Vec2::splat(f32::INFINITY);
89 let mut max = Vec2::splat(f32::NEG_INFINITY);
90 let mut current = Vec2::ZERO;
91
92 for cmd in &self.commands {
93 match cmd {
94 PathCommand::MoveTo(to) | PathCommand::LineTo(to) => {
95 min = min.min(*to);
96 max = max.max(*to);
97 current = *to;
98 }
99 PathCommand::QuadTo { control, to } => {
100 min = min.min(*control).min(*to);
102 max = max.max(*control).max(*to);
103 current = *to;
104 }
105 PathCommand::CubicTo {
106 control1,
107 control2,
108 to,
109 } => {
110 min = min.min(*control1).min(*control2).min(*to);
112 max = max.max(*control1).max(*control2).max(*to);
113 current = *to;
114 }
115 PathCommand::ArcTo { to, radii, .. } => {
116 min = min.min(*to).min(current - *radii);
118 max = max.max(*to).max(current + *radii);
119 current = *to;
120 }
121 PathCommand::Close => {}
122 }
123 }
124
125 if min.x.is_finite() && min.y.is_finite() && max.x.is_finite() && max.y.is_finite() {
126 Some((min, max))
127 } else {
128 None
129 }
130 }
131
132 pub fn reverse(&self) -> Self {
134 let mut reversed = Vec::new();
135 let mut subpath_start = Vec2::ZERO;
136 let mut current = Vec2::ZERO;
137 let mut subpath_commands = Vec::new();
138
139 for cmd in &self.commands {
140 match cmd {
141 PathCommand::MoveTo(to) => {
142 if !subpath_commands.is_empty() {
144 reversed.push(PathCommand::MoveTo(current));
145 for rcmd in subpath_commands.drain(..).rev() {
146 reversed.push(rcmd);
147 }
148 }
149 subpath_start = *to;
150 current = *to;
151 }
152 PathCommand::LineTo(to) => {
153 subpath_commands.push(PathCommand::LineTo(current));
154 current = *to;
155 }
156 PathCommand::QuadTo { control, to } => {
157 subpath_commands.push(PathCommand::QuadTo {
158 control: *control,
159 to: current,
160 });
161 current = *to;
162 }
163 PathCommand::CubicTo {
164 control1,
165 control2,
166 to,
167 } => {
168 subpath_commands.push(PathCommand::CubicTo {
169 control1: *control2,
170 control2: *control1,
171 to: current,
172 });
173 current = *to;
174 }
175 PathCommand::ArcTo {
176 radii,
177 x_rotation,
178 large_arc,
179 sweep,
180 to,
181 } => {
182 subpath_commands.push(PathCommand::ArcTo {
183 radii: *radii,
184 x_rotation: *x_rotation,
185 large_arc: *large_arc,
186 sweep: !sweep,
187 to: current,
188 });
189 current = *to;
190 }
191 PathCommand::Close => {
192 subpath_commands.push(PathCommand::LineTo(current));
193 current = subpath_start;
194 }
195 }
196 }
197
198 if !subpath_commands.is_empty() {
200 reversed.push(PathCommand::MoveTo(current));
201 for rcmd in subpath_commands.drain(..).rev() {
202 reversed.push(rcmd);
203 }
204 }
205
206 Self { commands: reversed }
207 }
208}
209
210#[derive(Debug, Default)]
212pub struct PathBuilder {
213 commands: Vec<PathCommand>,
214 current_pos: Vec2,
215 subpath_start: Vec2,
216}
217
218impl PathBuilder {
219 pub fn new() -> Self {
221 Self::default()
222 }
223
224 pub fn move_to(&mut self, to: Vec2) -> &mut Self {
226 self.commands.push(PathCommand::MoveTo(to));
227 self.current_pos = to;
228 self.subpath_start = to;
229 self
230 }
231
232 pub fn line_to(&mut self, to: Vec2) -> &mut Self {
234 self.commands.push(PathCommand::LineTo(to));
235 self.current_pos = to;
236 self
237 }
238
239 pub fn horizontal_line_to(&mut self, x: f32) -> &mut Self {
241 let to = Vec2::new(x, self.current_pos.y);
242 self.line_to(to)
243 }
244
245 pub fn vertical_line_to(&mut self, y: f32) -> &mut Self {
247 let to = Vec2::new(self.current_pos.x, y);
248 self.line_to(to)
249 }
250
251 pub fn quad_to(&mut self, control: Vec2, to: Vec2) -> &mut Self {
253 self.commands.push(PathCommand::QuadTo { control, to });
254 self.current_pos = to;
255 self
256 }
257
258 pub fn smooth_quad_to(&mut self, to: Vec2) -> &mut Self {
260 let control = if let Some(PathCommand::QuadTo { control, to: prev }) =
262 self.commands.last().copied()
263 {
264 prev * 2.0 - control
265 } else {
266 self.current_pos
267 };
268 self.quad_to(control, to)
269 }
270
271 pub fn cubic_to(&mut self, control1: Vec2, control2: Vec2, to: Vec2) -> &mut Self {
273 self.commands.push(PathCommand::CubicTo {
274 control1,
275 control2,
276 to,
277 });
278 self.current_pos = to;
279 self
280 }
281
282 pub fn smooth_cubic_to(&mut self, control2: Vec2, to: Vec2) -> &mut Self {
284 let control1 = if let Some(PathCommand::CubicTo {
286 control2, to: prev, ..
287 }) = self.commands.last().copied()
288 {
289 prev * 2.0 - control2
290 } else {
291 self.current_pos
292 };
293 self.cubic_to(control1, control2, to)
294 }
295
296 pub fn arc_to(
298 &mut self,
299 radii: Vec2,
300 x_rotation: f32,
301 large_arc: bool,
302 sweep: bool,
303 to: Vec2,
304 ) -> &mut Self {
305 self.commands.push(PathCommand::ArcTo {
306 radii,
307 x_rotation,
308 large_arc,
309 sweep,
310 to,
311 });
312 self.current_pos = to;
313 self
314 }
315
316 pub fn close(&mut self) -> &mut Self {
318 self.commands.push(PathCommand::Close);
319 self.current_pos = self.subpath_start;
320 self
321 }
322
323 pub fn rect(&mut self, position: Vec2, size: Vec2) -> &mut Self {
325 self.move_to(position);
326 self.line_to(position + Vec2::new(size.x, 0.0));
327 self.line_to(position + size);
328 self.line_to(position + Vec2::new(0.0, size.y));
329 self.close()
330 }
331
332 pub fn rounded_rect(&mut self, position: Vec2, size: Vec2, radius: f32) -> &mut Self {
334 let r = radius.min(size.x / 2.0).min(size.y / 2.0);
335 let radii = Vec2::splat(r);
336
337 self.move_to(position + Vec2::new(r, 0.0));
339
340 self.line_to(position + Vec2::new(size.x - r, 0.0));
342 self.arc_to(radii, 0.0, false, true, position + Vec2::new(size.x, r));
344
345 self.line_to(position + Vec2::new(size.x, size.y - r));
347 self.arc_to(
349 radii,
350 0.0,
351 false,
352 true,
353 position + Vec2::new(size.x - r, size.y),
354 );
355
356 self.line_to(position + Vec2::new(r, size.y));
358 self.arc_to(
360 radii,
361 0.0,
362 false,
363 true,
364 position + Vec2::new(0.0, size.y - r),
365 );
366
367 self.line_to(position + Vec2::new(0.0, r));
369 self.arc_to(radii, 0.0, false, true, position + Vec2::new(r, 0.0));
371
372 self.close()
373 }
374
375 pub fn circle(&mut self, center: Vec2, radius: f32) -> &mut Self {
377 let r = Vec2::splat(radius);
378
379 self.move_to(center + Vec2::new(radius, 0.0));
381
382 self.arc_to(r, 0.0, false, true, center + Vec2::new(0.0, radius));
384 self.arc_to(r, 0.0, false, true, center + Vec2::new(-radius, 0.0));
385 self.arc_to(r, 0.0, false, true, center + Vec2::new(0.0, -radius));
386 self.arc_to(r, 0.0, false, true, center + Vec2::new(radius, 0.0));
387
388 self.close()
389 }
390
391 pub fn ellipse(&mut self, center: Vec2, radii: Vec2) -> &mut Self {
393 self.move_to(center + Vec2::new(radii.x, 0.0));
395
396 self.arc_to(radii, 0.0, false, true, center + Vec2::new(0.0, radii.y));
398 self.arc_to(radii, 0.0, false, true, center + Vec2::new(-radii.x, 0.0));
399 self.arc_to(radii, 0.0, false, true, center + Vec2::new(0.0, -radii.y));
400 self.arc_to(radii, 0.0, false, true, center + Vec2::new(radii.x, 0.0));
401
402 self.close()
403 }
404
405 pub fn polygon(&mut self, points: &[Vec2]) -> &mut Self {
407 if points.is_empty() {
408 return self;
409 }
410
411 self.move_to(points[0]);
412 for point in &points[1..] {
413 self.line_to(*point);
414 }
415 self.close()
416 }
417
418 pub fn current_pos(&self) -> Vec2 {
420 self.current_pos
421 }
422
423 pub fn build(self) -> Path {
425 Path {
426 commands: self.commands,
427 }
428 }
429}
430
431pub trait PathCurves {
433 fn quadratic_curves(&self) -> impl Iterator<Item = QuadraticBezier> + '_;
435 fn cubic_curves(&self) -> impl Iterator<Item = CubicBezier> + '_;
437}
438
439impl PathCurves for Path {
440 fn quadratic_curves(&self) -> impl Iterator<Item = QuadraticBezier> + '_ {
441 let mut current = Vec2::ZERO;
442 self.commands.iter().filter_map(move |cmd| match cmd {
443 PathCommand::MoveTo(to) | PathCommand::LineTo(to) => {
444 current = *to;
445 None
446 }
447 PathCommand::QuadTo { control, to } => {
448 let curve = QuadraticBezier::new(current, *control, *to);
449 current = *to;
450 Some(curve)
451 }
452 PathCommand::CubicTo { to, .. } => {
453 current = *to;
454 None
455 }
456 PathCommand::ArcTo { to, .. } => {
457 current = *to;
458 None
459 }
460 PathCommand::Close => None,
461 })
462 }
463
464 fn cubic_curves(&self) -> impl Iterator<Item = CubicBezier> + '_ {
465 let mut current = Vec2::ZERO;
466 self.commands.iter().filter_map(move |cmd| match cmd {
467 PathCommand::MoveTo(to) | PathCommand::LineTo(to) => {
468 current = *to;
469 None
470 }
471 PathCommand::QuadTo { to, .. } => {
472 current = *to;
473 None
474 }
475 PathCommand::CubicTo {
476 control1,
477 control2,
478 to,
479 } => {
480 let curve = CubicBezier::new(current, *control1, *control2, *to);
481 current = *to;
482 Some(curve)
483 }
484 PathCommand::ArcTo { to, .. } => {
485 current = *to;
486 None
487 }
488 PathCommand::Close => None,
489 })
490 }
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496
497 #[test]
498 fn test_path_builder_line() {
499 let mut builder = PathBuilder::new();
500 builder
501 .move_to(Vec2::new(0.0, 0.0))
502 .line_to(Vec2::new(100.0, 0.0))
503 .line_to(Vec2::new(100.0, 100.0))
504 .close();
505 let path = builder.build();
506
507 assert_eq!(path.len(), 4);
508 }
509
510 #[test]
511 fn test_path_bounds() {
512 let mut builder = PathBuilder::new();
513 builder
514 .move_to(Vec2::new(10.0, 20.0))
515 .line_to(Vec2::new(100.0, 50.0))
516 .line_to(Vec2::new(50.0, 100.0));
517 let path = builder.build();
518
519 let (min, max) = path.bounds().unwrap();
520 assert_eq!(min, Vec2::new(10.0, 20.0));
521 assert_eq!(max, Vec2::new(100.0, 100.0));
522 }
523
524 #[test]
525 fn test_circle_path() {
526 let mut builder = PathBuilder::new();
527 builder.circle(Vec2::new(50.0, 50.0), 25.0);
528 let path = builder.build();
529
530 assert!(!path.is_empty());
532 }
533
534 #[test]
535 fn test_rect_path() {
536 let mut builder = PathBuilder::new();
537 builder.rect(Vec2::new(10.0, 10.0), Vec2::new(80.0, 60.0));
538 let path = builder.build();
539
540 let (min, max) = path.bounds().unwrap();
541 assert_eq!(min, Vec2::new(10.0, 10.0));
542 assert_eq!(max, Vec2::new(90.0, 70.0));
543 }
544}