Skip to main content

motion_core/path2d/
builder.rs

1//! Chainable path construction that reports its first error once.
2
3use super::Path2D;
4use crate::{Point, ValidationError, Vector2};
5
6/// Collects path verbs fluently and reports the first failure from [`Self::build`].
7///
8/// Once a step fails, later steps are ignored so the reported error is the first one.
9///
10/// ```
11/// use motion_core::{Path2D, Point};
12/// let path = Path2D::builder()
13///     .move_to(Point::ORIGIN)
14///     .line_to(Point::new(4.0, 0.0))
15///     .close()
16///     .build()?;
17/// assert_eq!(path.verbs().len(), 3);
18/// assert!(Path2D::builder().line_to(Point::ORIGIN).build().is_err());
19/// # Ok::<(), motion_core::ValidationError>(())
20/// ```
21#[derive(Clone, Debug, Default)]
22#[must_use = "a path builder does nothing until `build` is called"]
23pub struct PathBuilder {
24    path: Path2D,
25    error: Option<ValidationError>,
26}
27
28impl PathBuilder {
29    /// Creates an empty builder.
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Starts a new subpath.
35    pub fn move_to(self, point: Point) -> Self {
36        self.step(|path| path.try_move_to(point))
37    }
38
39    /// Adds a line; requires a preceding move.
40    pub fn line_to(self, point: Point) -> Self {
41        self.step(|path| path.try_line_to(point))
42    }
43
44    /// Adds a quadratic Bezier; requires a preceding move.
45    pub fn quadratic_to(self, control: Point, end: Point) -> Self {
46        self.step(|path| path.try_quadratic_to(control, end))
47    }
48
49    /// Adds a cubic Bezier; requires a preceding move.
50    pub fn cubic_to(self, first: Point, second: Point, end: Point) -> Self {
51        self.step(|path| path.try_cubic_to(first, second, end))
52    }
53
54    /// Appends a clockwise elliptical arc; see [`Path2D::try_arc_to`].
55    pub fn arc_to(self, center: Point, radii: Vector2, start_angle: f64, sweep_angle: f64) -> Self {
56        self.step(|path| path.try_arc_to(center, radii, start_angle, sweep_angle))
57    }
58
59    /// Closes the current subpath; requires a preceding move.
60    pub fn close(self) -> Self {
61        self.step(Path2D::try_close)
62    }
63
64    /// Returns the finished path or the first error recorded while building.
65    pub fn build(self) -> Result<Path2D, ValidationError> {
66        match self.error {
67            Some(error) => Err(error),
68            None => Ok(self.path),
69        }
70    }
71
72    fn step(mut self, apply: impl FnOnce(&mut Path2D) -> Result<(), ValidationError>) -> Self {
73        if self.error.is_none()
74            && let Err(error) = apply(&mut self.path)
75        {
76            self.error = Some(error);
77        }
78        self
79    }
80}