1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.ui.graphics.vector
18 
19 /** [PathBuilder] provides a fluent API to creates a list of [PathNode], used to describe a path. */
20 class PathBuilder {
21     // 88% of Material icons use 32 or fewer path nodes
22     private val _nodes = ArrayList<PathNode>(32)
23 
24     /** Returns the list of [PathNode] currently held in this builder. */
25     val nodes: List<PathNode>
26         get() = _nodes
27 
28     /** Closes the current contour by adding a [PathNode.Close] to [nodes]. */
closenull29     fun close(): PathBuilder {
30         _nodes.add(PathNode.Close)
31         return this
32     }
33 
34     /**
35      * Start a new contour at position ([x], [y]) by adding a [PathNode.MoveTo] to [nodes].
36      *
37      * @param x The x coordinate of the start of the new contour
38      * @param y The y coordinate of the start of the new contour
39      */
moveTonull40     fun moveTo(x: Float, y: Float): PathBuilder {
41         _nodes.add(PathNode.MoveTo(x, y))
42         return this
43     }
44 
45     /**
46      * Start a new contour at the offset ([dx], [dy]) relative to the last path position by adding a
47      * [PathNode.RelativeMoveTo] to [nodes].
48      *
49      * @param dx The x offset of the start of the new contour, relative to the last path position
50      * @param dy The y offset of the start of the new contour, relative to the last path position
51      */
moveToRelativenull52     fun moveToRelative(dx: Float, dy: Float): PathBuilder {
53         _nodes.add(PathNode.RelativeMoveTo(dx, dy))
54         return this
55     }
56 
57     /**
58      * Add a line from the last point to the position ([x], [y]) by adding a [PathNode.LineTo] to
59      * [nodes]. If no contour has been created by calling [moveTo] first, the origin of the line is
60      * set to (0, 0).
61      *
62      * @param x The x coordinate of the end of the line
63      * @param y The y coordinate of the end of the line
64      */
lineTonull65     fun lineTo(x: Float, y: Float): PathBuilder {
66         _nodes.add(PathNode.LineTo(x, y))
67         return this
68     }
69 
70     /**
71      * Add a line from the last point to the offset ([dx], [dy]) relative to the last point by
72      * adding a [PathNode.RelativeLineTo] to [nodes]. If no contour has been created by calling
73      * [moveTo] first, the origin of the line is set to (0, 0).
74      *
75      * @param dx The x offset of the end of the line, relative to the last path position
76      * @param dy The y offset of the end of the line, relative to the last path position
77      */
lineToRelativenull78     fun lineToRelative(dx: Float, dy: Float): PathBuilder {
79         _nodes.add(PathNode.RelativeLineTo(dx, dy))
80         return this
81     }
82 
83     /**
84      * Add a line from the last point to the position ([x], `oy`), where `oy` is the y coordinate of
85      * the last point, by adding a [PathNode.HorizontalTo] to [nodes]. If no contour has been
86      * created by calling [moveTo] first, the origin of the line is set to (0, 0).
87      *
88      * @param x The x coordinate of the end of the line
89      */
horizontalLineTonull90     fun horizontalLineTo(x: Float): PathBuilder {
91         _nodes.add(PathNode.HorizontalTo(x))
92         return this
93     }
94 
95     /**
96      * Add a line from the last point to the position ([dx] `+ ox`, `oy`), where `ox` and `oy` are
97      * the x and y coordinates of the last point, by adding a [PathNode.RelativeHorizontalTo] to
98      * [nodes]. If no contour has been created by calling [moveTo] first, the origin of the line is
99      * set to (0, 0).
100      *
101      * @param dx The x offset of the end of the line, relative to the last path position
102      */
horizontalLineToRelativenull103     fun horizontalLineToRelative(dx: Float): PathBuilder {
104         _nodes.add(PathNode.RelativeHorizontalTo(dx))
105         return this
106     }
107 
108     /**
109      * Add a line from the last point to the position (`ox`, [y]), where `ox` is the x coordinate of
110      * the last point, by adding a [PathNode.VerticalTo] to [nodes]. If no contour has been created
111      * by calling [moveTo] first, the origin of the line is set to (0, 0).
112      *
113      * @param y The y coordinate of the end of the line
114      */
verticalLineTonull115     fun verticalLineTo(y: Float): PathBuilder {
116         _nodes.add(PathNode.VerticalTo(y))
117         return this
118     }
119 
120     /**
121      * Add a line from the last point to the position (`ox`, [dy] `+ oy`), where `ox` and `oy` are
122      * the x and y coordinates of the last point, by adding a [PathNode.RelativeVerticalTo] to
123      * [nodes]. If no contour has been created by calling [moveTo] first, the origin of the line is
124      * set to (0, 0).
125      *
126      * @param dy The y offset of the end of the line, relative to the last path position
127      */
verticalLineToRelativenull128     fun verticalLineToRelative(dy: Float): PathBuilder {
129         _nodes.add(PathNode.RelativeVerticalTo(dy))
130         return this
131     }
132 
133     /**
134      * Add a cubic Bézier from the last point to the position ([x3], [y3]), approaching the control
135      * points ([x1], [y1]) and ([x2], [y2]), by adding a [PathNode.CurveTo] to [nodes]. If no
136      * contour has been created by calling [moveTo] first, the origin of the curve is set to (0, 0).
137      *
138      * @param x1 The x coordinate of the first control point of the cubic curve
139      * @param y1 The y coordinate of the first control point of the cubic curve
140      * @param x2 The x coordinate of the second control point of the cubic curve
141      * @param y2 The y coordinate of the second control point of the cubic curve
142      * @param x3 The x coordinate of the end point of the cubic curve
143      * @param y3 The y coordinate of the end point of the cubic curve
144      */
curveTonull145     fun curveTo(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float): PathBuilder {
146         _nodes.add(PathNode.CurveTo(x1, y1, x2, y2, x3, y3))
147         return this
148     }
149 
150     /**
151      * Add a cubic Bézier by adding a [PathNode.CurveTo] to [nodes]. If no contour has been created
152      * by calling [moveTo] first, the origin of the curve is set to (0, 0). The cubic Bézier control
153      * and end points are defined by offsets relative to the last point.
154      *
155      * @param dx1 The x offset of the first control point of the cubic curve, relative to the last
156      *   path position
157      * @param dy1 The y offset of the first control point of the cubic curve, relative to the last
158      *   path position
159      * @param dx2 The x offset of the second control point of the cubic curve, relative to the last
160      *   path position
161      * @param dy2 The y offset of the second control point of the cubic curve, relative to the last
162      *   path position
163      * @param dx3 The x offset of the end point of the cubic curve, relative to the last path
164      *   position
165      * @param dy3 The y offset of the end point of the cubic curve, relative to the last path
166      *   position
167      */
curveToRelativenull168     fun curveToRelative(
169         dx1: Float,
170         dy1: Float,
171         dx2: Float,
172         dy2: Float,
173         dx3: Float,
174         dy3: Float
175     ): PathBuilder {
176         _nodes.add(PathNode.RelativeCurveTo(dx1, dy1, dx2, dy2, dx3, dy3))
177         return this
178     }
179 
180     /**
181      * Add a cubic Bézier from the last point to the position ([x2], [y2]). The first control point
182      * is the reflection of the second control point of the previous command. If there is no
183      * previous command or the previous command is not a cubic Bézier, the first control point is
184      * set to the last path position. The second control point is defined by ([x1], [y1]). Calling
185      * this method adds a [PathNode.ReflectiveCurveTo] to [nodes]. If no contour has been created by
186      * calling [moveTo] first, the origin of the curve is set to (0, 0).
187      *
188      * @param x1 The x coordinate of the second control point of the cubic curve
189      * @param y1 The y coordinate of the second control point of the cubic curve
190      * @param x2 The x coordinate of the end point of the cubic curve
191      * @param y2 The y coordinate of the end point of the cubic curve
192      */
reflectiveCurveTonull193     fun reflectiveCurveTo(x1: Float, y1: Float, x2: Float, y2: Float): PathBuilder {
194         _nodes.add(PathNode.ReflectiveCurveTo(x1, y1, x2, y2))
195         return this
196     }
197 
198     /**
199      * Add a cubic Bézier by adding a [PathNode.RelativeReflectiveCurveTo] to [nodes]. If no contour
200      * has been created by calling [moveTo] first, the origin of the curve is set to (0, 0). The
201      * cubic Bézier second control point and end points are defined by offsets relative to the last
202      * point. The reflective nature of the curve is described in [reflectiveCurveTo].
203      *
204      * @param dx1 The x offset of the second control point of the cubic curve, relative to the last
205      *   path position
206      * @param dy1 The y offset of the second control point of the cubic curve, relative to the last
207      *   path position
208      * @param dx2 The x offset of the end point of the cubic curve, relative to the last path
209      *   position
210      * @param dy2 The y offset of the end point of the cubic curve, relative to the last path
211      *   position
212      */
reflectiveCurveToRelativenull213     fun reflectiveCurveToRelative(dx1: Float, dy1: Float, dx2: Float, dy2: Float): PathBuilder {
214         _nodes.add(PathNode.RelativeReflectiveCurveTo(dx1, dy1, dx2, dy2))
215         return this
216     }
217 
218     /**
219      * Add a quadratic Bézier from the last point to the position ([x2], [y2]), approaching the
220      * control point ([x1], [y1]), by adding a [PathNode.QuadTo] to [nodes]. If no contour has been
221      * created by calling [moveTo] first, the origin of the curve is set to (0, 0).
222      *
223      * @param x1 The x coordinate of the control point of the quadratic curve
224      * @param y1 The y coordinate of the control point of the quadratic curve
225      * @param x2 The x coordinate of the end point of the quadratic curve
226      * @param y2 The y coordinate of the end point of the quadratic curve
227      */
quadTonull228     fun quadTo(x1: Float, y1: Float, x2: Float, y2: Float): PathBuilder {
229         _nodes.add(PathNode.QuadTo(x1, y1, x2, y2))
230         return this
231     }
232 
233     /**
234      * Add a quadratic Bézier by adding a [PathNode.RelativeQuadTo] to [nodes]. If no contour has
235      * been created by calling [moveTo] first, the origin of the curve is set to (0, 0). The control
236      * point and end point of the curve are defined by offsets relative to the last point.
237      *
238      * @param dx1 The x offset of the control point of the quadratic curve, relative to the last
239      *   path position
240      * @param dy1 The y offset of the control point of the quadratic curve, relative to the last
241      *   path position
242      * @param dx2 The x offset of the end point of the quadratic curve, relative to the last path
243      *   position
244      * @param dy2 The y offset of the end point of the quadratic curve, relative to the last path
245      *   position
246      */
quadToRelativenull247     fun quadToRelative(dx1: Float, dy1: Float, dx2: Float, dy2: Float): PathBuilder {
248         _nodes.add(PathNode.RelativeQuadTo(dx1, dy1, dx2, dy2))
249         return this
250     }
251 
252     /**
253      * Add a quadratic Bézier from the last point to the position ([x1], [y1]). The control point is
254      * the reflection of the control point of the previous command. If there is no previous command
255      * or the previous command is not a quadratic Bézier, the control point is set to the last path
256      * position. Calling this method adds a [PathNode.ReflectiveQuadTo] to [nodes]. If no contour
257      * has been created by calling [moveTo] first, the origin of the curve is set to (0, 0).
258      *
259      * @param x1 The x coordinate of the end point of the quadratic curve
260      * @param y1 The y coordinate of the end point of the quadratic curve
261      */
reflectiveQuadTonull262     fun reflectiveQuadTo(x1: Float, y1: Float): PathBuilder {
263         _nodes.add(PathNode.ReflectiveQuadTo(x1, y1))
264         return this
265     }
266 
267     /**
268      * Add a quadratic Bézier by adding a [PathNode.RelativeReflectiveQuadTo] to [nodes]. If no
269      * contour has been created by calling [moveTo] first, the origin of the curve is set to (0, 0).
270      * The quadratic Bézier end point is defined by an offset relative to the last point. The
271      * reflective nature of the curve is described in [reflectiveQuadTo].
272      *
273      * @param dx1 The x offset of the end point of the quadratic curve, relative to the last path
274      *   position
275      * @param dy1 The y offset of the end point of the quadratic curve, relative to the last path
276      *   position
277      */
reflectiveQuadToRelativenull278     fun reflectiveQuadToRelative(dx1: Float, dy1: Float): PathBuilder {
279         _nodes.add(PathNode.RelativeReflectiveQuadTo(dx1, dy1))
280         return this
281     }
282 
283     /**
284      * Add an elliptical arc from the last point to the position ([x1], [y1]) by adding
285      * [PathNode.ArcTo] to [nodes]. If no contour has been created by calling [moveTo] first, the
286      * origin of the arc is set to (0, 0).
287      *
288      * The ellipse is defined by 3 parameters:
289      * - [horizontalEllipseRadius] and [verticalEllipseRadius] to define the size of the ellipse
290      * - [theta] to define the orientation (as an X-axis rotation) of the ellipse
291      *
292      * In most situations, there are four arc candidates that can be drawn from the origin to ([x1],
293      * [y1]). Which of the arcs is used is influenced by [isMoreThanHalf] and [isPositiveArc].
294      *
295      * When [isMoreThanHalf] is set to `true`, the added arc will be chosen amongst the two
296      * candidates that represent an arc sweep greater than or equal to 180 degrees.
297      *
298      * When [isPositiveArc] is set to `true`, the added arc will be chosen amongst the two
299      * candidates with a positive-angle direction (counter-clockwise)
300      *
301      * @param horizontalEllipseRadius The horizontal radius of the ellipse
302      * @param verticalEllipseRadius The vertical radius of the ellipse
303      * @param theta The rotation of the ellipse around the X-axis, in degrees
304      * @param isMoreThanHalf Defines whether to use an arc candidate with a sweep greater than or
305      *   equal to 180 degrees
306      * @param isPositiveArc Defines whether to use an arc candidate that's counter-clockwise or not
307      * @param x1 The x coordinate of the end point of the arc
308      * @param y1 The y coordinate of the end point of the arc
309      */
arcTonull310     fun arcTo(
311         horizontalEllipseRadius: Float,
312         verticalEllipseRadius: Float,
313         theta: Float,
314         isMoreThanHalf: Boolean,
315         isPositiveArc: Boolean,
316         x1: Float,
317         y1: Float
318     ): PathBuilder {
319         _nodes.add(
320             PathNode.ArcTo(
321                 horizontalEllipseRadius,
322                 verticalEllipseRadius,
323                 theta,
324                 isMoreThanHalf,
325                 isPositiveArc,
326                 x1,
327                 y1
328             )
329         )
330         return this
331     }
332 
333     /**
334      * Add an elliptical arc by adding [PathNode.RelativeArcTo] to [nodes]. If no contour has been
335      * created by calling [moveTo] first, the origin of the arc is set to (0, 0). The arc Bézier end
336      * point is defined by an offset relative to the last point.
337      *
338      * The ellipse is defined by 3 parameters:
339      * - [a] and [b] to define the size of the ellipse
340      * - [theta] to define the orientation (as an X-axis rotation) of the ellipse
341      *
342      * In most situations, there are four arc candidates that can be drawn from the origin to the
343      * end point. Which of the arcs is used is influenced by [isMoreThanHalf] and [isPositiveArc].
344      *
345      * When [isMoreThanHalf] is set to `true`, the added arc will be chosen amongst the two
346      * candidates that represent an arc sweep greater than or equal to 180 degrees.
347      *
348      * When [isPositiveArc] is set to `true`, the added arc will be chosen amongst the two
349      * candidates with a positive-angle direction (counter-clockwise)
350      *
351      * @param a The horizontal radius of the ellipse
352      * @param b The vertical radius of the ellipse
353      * @param theta The rotation of the ellipse around the X-axis, in degrees
354      * @param isMoreThanHalf Defines whether to use an arc candidate with a sweep greater than or
355      *   equal to 180 degrees
356      * @param isPositiveArc Defines whether to use an arc candidate that's counter-clockwise or not
357      * @param dx1 The x offset of the end point of the arc, relative to the last path position
358      * @param dy1 The y offset of the end point of the arc, relative to the last path position
359      */
arcToRelativenull360     fun arcToRelative(
361         a: Float,
362         b: Float,
363         theta: Float,
364         isMoreThanHalf: Boolean,
365         isPositiveArc: Boolean,
366         dx1: Float,
367         dy1: Float
368     ): PathBuilder {
369         _nodes.add(PathNode.RelativeArcTo(a, b, theta, isMoreThanHalf, isPositiveArc, dx1, dy1))
370         return this
371     }
372 }
373