[go: nahoru, domu]

blob: 42cdbfb2b60b25a29d029886fab7cef8127086f4 [file] [log] [blame]
Andrey Kulikova7d11042018-08-09 14:41:54 +01001/*
2 * Copyright 2018 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
Nader Jawadad983532019-08-29 16:27:37 -070017package androidx.ui.graphics
Andrey Kulikova7d11042018-08-09 14:41:54 +010018
George Mount5de30a52019-01-29 13:55:54 -080019import androidx.ui.core.toFrameworkRect
George Mount842c8c12020-01-08 16:03:42 -080020import androidx.ui.geometry.Offset
21import androidx.ui.geometry.RRect
22import androidx.ui.geometry.Radius
23import androidx.ui.geometry.Rect
George Mountc525e362020-01-10 14:53:39 -080024import androidx.ui.graphics.vectormath.Matrix4
25import androidx.ui.graphics.vectormath.degrees
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070026
Qingqing Deng21d88d82019-01-28 15:37:37 -080027class Path(private val internalPath: android.graphics.Path = android.graphics.Path()) {
Andrey Kulikov01f9ac22018-12-05 16:00:07 +000028
29 // Temporary value holders to reuse an object (not part of a state):
Nader Jawadae649842018-09-05 14:43:04 -070030 private val rectF = android.graphics.RectF()
Nader Jawadae649842018-09-05 14:43:04 -070031 private val radii = FloatArray(8)
Nader Jawadae649842018-09-05 14:43:04 -070032 private val mMatrix = android.graphics.Matrix()
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070033
Nader Jawade341cd12018-08-29 14:16:57 -070034 fun toFrameworkPath(): android.graphics.Path = internalPath
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070035
George Mount8fd10882019-01-31 14:02:33 -080036 private fun clone() = Path().apply {
Andrey Kulikov01f9ac22018-12-05 16:00:07 +000037 internalPath.set(this@Path.internalPath)
38 }
39
Mihai Popad8e03d32018-09-24 12:29:50 +010040 /**
41 * Determines how the interior of this path is calculated.
42 *
43 * Defaults to the non-zero winding rule, [PathFillType.nonZero].
44 */
Nader Jawade341cd12018-08-29 14:16:57 -070045 fun getFillType(): PathFillType {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070046 if (internalPath.fillType == android.graphics.Path.FillType.EVEN_ODD) {
Nader Jawade341cd12018-08-29 14:16:57 -070047 return PathFillType.evenOdd
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070048 } else {
Nader Jawade341cd12018-08-29 14:16:57 -070049 return PathFillType.nonZero
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070050 }
51 }
52
Nader Jawade341cd12018-08-29 14:16:57 -070053 fun setFillType(value: PathFillType) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070054 internalPath.fillType =
55 if (value == PathFillType.evenOdd) {
56 android.graphics.Path.FillType.EVEN_ODD
57 } else {
Nader Jawade341cd12018-08-29 14:16:57 -070058 android.graphics.Path.FillType.WINDING
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070059 }
60 }
61
Mihai Popad8e03d32018-09-24 12:29:50 +010062 /** Starts a new subpath at the given coordinate. */
George Mountb1cdd862018-12-21 10:54:42 -080063 fun moveTo(dx: Float, dy: Float) {
64 internalPath.moveTo(dx, dy)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070065 }
66
Mihai Popad8e03d32018-09-24 12:29:50 +010067 /** Starts a new subpath at the given offset from the current point. */
George Mountb1cdd862018-12-21 10:54:42 -080068 fun relativeMoveTo(dx: Float, dy: Float) {
69 internalPath.rMoveTo(dx, dy)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070070 }
71
Mihai Popad8e03d32018-09-24 12:29:50 +010072 /**
73 * Adds a straight line segment from the current point to the given
74 * point.
75 */
George Mountb1cdd862018-12-21 10:54:42 -080076 fun lineTo(dx: Float, dy: Float) {
77 internalPath.lineTo(dx, dy)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070078 }
79
Mihai Popad8e03d32018-09-24 12:29:50 +010080 /**
81 * Adds a straight line segment from the current point to the point
82 * at the given offset from the current point.
83 */
George Mountb1cdd862018-12-21 10:54:42 -080084 fun relativeLineTo(dx: Float, dy: Float) {
85 internalPath.rLineTo(dx, dy)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070086 }
87
Mihai Popad8e03d32018-09-24 12:29:50 +010088 /**
89 * Adds a quadratic bezier segment that curves from the current
90 * point to the given point (x2,y2), using the control point
91 * (x1,y1).
92 */
George Mountb1cdd862018-12-21 10:54:42 -080093 fun quadraticBezierTo(x1: Float, y1: Float, x2: Float, y2: Float) {
94 internalPath.quadTo(x1, y1, x2, y2)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -070095 }
96
Mihai Popad8e03d32018-09-24 12:29:50 +010097 /**
98 * Adds a quadratic bezier segment that curves from the current
99 * point to the point at the offset (x2,y2) from the current point,
100 * using the control point at the offset (x1,y1) from the current
101 * point.
102 */
George Mountb1cdd862018-12-21 10:54:42 -0800103 fun relativeQuadraticBezierTo(x1: Float, y1: Float, x2: Float, y2: Float) {
104 internalPath.rQuadTo(x1, y1, x2, y2)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700105 }
106
Mihai Popad8e03d32018-09-24 12:29:50 +0100107 /**
108 * Adds a cubic bezier segment that curves from the current point
109 * to the given point (x3,y3), using the control points (x1,y1) and
110 * (x2,y2).
111 */
George Mountb1cdd862018-12-21 10:54:42 -0800112 fun cubicTo(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700113 internalPath.cubicTo(
George Mountb1cdd862018-12-21 10:54:42 -0800114 x1, y1,
115 x2, y2,
116 x3, y3
Nader Jawade341cd12018-08-29 14:16:57 -0700117 )
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700118 }
119
Mihai Popad8e03d32018-09-24 12:29:50 +0100120 /**
121 * Adds a cubic bezier segment that curves from the current point
122 * to the point at the offset (x3,y3) from the current point, using
123 * the control points at the offsets (x1,y1) and (x2,y2) from the
124 * current point.
125 */
George Mountb1cdd862018-12-21 10:54:42 -0800126 fun relativeCubicTo(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700127 internalPath.rCubicTo(
George Mountb1cdd862018-12-21 10:54:42 -0800128 x1, y1,
129 x2, y2,
130 x3, y3
Nader Jawade341cd12018-08-29 14:16:57 -0700131 )
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700132 }
133
Mihai Popad8e03d32018-09-24 12:29:50 +0100134 /**
135 * Adds a bezier segment that curves from the current point to the
136 * given point (x2,y2), using the control points (x1,y1) and the
137 * weight w. If the weight is greater than 1, then the curve is a
138 * hyperbola; if the weight equals 1, it's a parabola; and if it is
139 * less than 1, it is an ellipse.
Nader Jawadad983532019-08-29 16:27:37 -0700140 *
141 * Throws [UnsupportedOperationException] as Android framework does not support this API
Mihai Popad8e03d32018-09-24 12:29:50 +0100142 */
George Mountfa5c5482019-04-18 12:22:08 +0100143 @Suppress("UNUSED_PARAMETER")
George Mountb1cdd862018-12-21 10:54:42 -0800144 fun conicTo(x1: Float, y1: Float, x2: Float, y2: Float, w: Float) {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700145 // TODO(njawad) figure out how to handle unsupported framework Path operations
Nader Jawade341cd12018-08-29 14:16:57 -0700146 throw UnsupportedOperationException("conicTo not supported in framework Path")
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700147 }
148
Mihai Popad8e03d32018-09-24 12:29:50 +0100149 /**
150 * Adds a bezier segment that curves from the current point to the
151 * point at the offset (x2,y2) from the current point, using the
152 * control point at the offset (x1,y1) from the current point and
153 * the weight w. If the weight is greater than 1, then the curve is
154 * a hyperbola; if the weight equals 1, it's a parabola; and if it
155 * is less than 1, it is an ellipse.
Nader Jawadad983532019-08-29 16:27:37 -0700156 *
157 * Throws [UnsupportedOperationException] as Android framework does not support this API
Mihai Popad8e03d32018-09-24 12:29:50 +0100158 */
George Mountfa5c5482019-04-18 12:22:08 +0100159 @Suppress("UNUSED_PARAMETER")
George Mountb1cdd862018-12-21 10:54:42 -0800160 fun relativeConicTo(x1: Float, y1: Float, x2: Float, y2: Float, w: Float) {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700161 // TODO(njawad) figure out how to handle unsupported framework Path operations
Nader Jawade341cd12018-08-29 14:16:57 -0700162 throw UnsupportedOperationException("relativeConicTo not supported in framework Path")
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700163 }
164
Mihai Popad8e03d32018-09-24 12:29:50 +0100165 /**
Nader Jawad14dff8f2019-08-22 15:51:19 -0700166 * If the [forceMoveTo] argument is false, adds a straight line
Mihai Popad8e03d32018-09-24 12:29:50 +0100167 * segment and an arc segment.
168 *
Nader Jawad14dff8f2019-08-22 15:51:19 -0700169 * If the [forceMoveTo] argument is true, starts a new subpath
Mihai Popad8e03d32018-09-24 12:29:50 +0100170 * consisting of an arc segment.
171 *
172 * In either case, the arc segment consists of the arc that follows
173 * the edge of the oval bounded by the given rectangle, from
174 * startAngle radians around the oval up to startAngle + sweepAngle
175 * radians around the oval, with zero radians being the point on
176 * the right hand side of the oval that crosses the horizontal line
177 * that intersects the center of the rectangle and with positive
178 * angles going clockwise around the oval.
179 *
180 * The line segment added if `forceMoveTo` is false starts at the
181 * current point and ends at the start of the arc.
182 */
Nader Jawad14dff8f2019-08-22 15:51:19 -0700183 fun arcToRad(
184 rect: Rect,
185 startAngleRadians: Float,
186 sweepAngleRadians: Float,
187 forceMoveTo: Boolean
188 ) {
189 arcTo(rect, degrees(startAngleRadians), degrees(sweepAngleRadians), forceMoveTo)
190 }
191
192 /**
193 * If the [forceMoveTo] argument is false, adds a straight line
194 * segment and an arc segment.
195 *
196 * If the [forceMoveTo] argument is true, starts a new subpath
197 * consisting of an arc segment.
198 *
199 * In either case, the arc segment consists of the arc that follows
200 * the edge of the oval bounded by the given rectangle, from
201 * startAngle degrees around the oval up to startAngle + sweepAngle
202 * degrees around the oval, with zero degrees being the point on
203 * the right hand side of the oval that crosses the horizontal line
204 * that intersects the center of the rectangle and with positive
205 * angles going clockwise around the oval.
206 *
207 * The line segment added if `forceMoveTo` is false starts at the
208 * current point and ends at the start of the arc.
209 */
210 fun arcTo(
211 rect: Rect,
212 startAngleDegrees: Float,
213 sweepAngleDegrees: Float,
214 forceMoveTo: Boolean
215 ) {
George Mountb1cdd862018-12-21 10:54:42 -0800216 val left = rect.left
217 val top = rect.top
218 val right = rect.right
219 val bottom = rect.bottom
Nader Jawade341cd12018-08-29 14:16:57 -0700220 rectF.set(left, top, right, bottom)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700221 internalPath.arcTo(
George Mountb1cdd862018-12-21 10:54:42 -0800222 rectF,
Nader Jawad14dff8f2019-08-22 15:51:19 -0700223 startAngleDegrees,
224 sweepAngleDegrees,
George Mountb1cdd862018-12-21 10:54:42 -0800225 forceMoveTo
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700226 )
227 }
228
Mihai Popad8e03d32018-09-24 12:29:50 +0100229 /**
230 * Appends up to four conic curves weighted to describe an oval of `radius`
231 * and rotated by `rotation`.
232 *
233 * The first curve begins from the last point in the path and the last ends
234 * at `arcEnd`. The curves follow a path in a direction determined by
235 * `clockwise` and `largeArc` in such a way that the sweep angle
236 * is always less than 360 degrees.
237 *
238 * A simple line is appended if either either radii are zero or the last
239 * point in the path is `arcEnd`. The radii are scaled to fit the last path
240 * point if both are greater than zero but too small to describe an arc.
241 *
Nader Jawadad983532019-08-29 16:27:37 -0700242 * Throws [UnsupportedOperationException] as Android framework does not support this API
Mihai Popad8e03d32018-09-24 12:29:50 +0100243 */
George Mountfa5c5482019-04-18 12:22:08 +0100244 @Suppress("UNUSED_PARAMETER")
Nader Jawade341cd12018-08-29 14:16:57 -0700245 fun arcToPoint(
246 arcEnd: Offset,
247 radius: Radius = Radius.zero,
George Mountb1cdd862018-12-21 10:54:42 -0800248 rotation: Float = 0.0f,
Nader Jawade341cd12018-08-29 14:16:57 -0700249 largeArc: Boolean = false,
250 clockwise: Boolean = true
251 ) {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700252 // TODO(njawad) figure out how to handle unsupported framework Path operations
Nader Jawade341cd12018-08-29 14:16:57 -0700253 throw UnsupportedOperationException("arcToPoint not supported in framework Path")
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700254 }
255
George Mountfa5c5482019-04-18 12:22:08 +0100256 @Suppress("UNUSED_PARAMETER")
Nader Jawade341cd12018-08-29 14:16:57 -0700257 private fun _arcToPoint(
George Mountb1cdd862018-12-21 10:54:42 -0800258 arcEndX: Float,
259 arcEndY: Float,
260 radius: Float,
261 radiusY: Float,
262 rotation: Float,
Nader Jawade341cd12018-08-29 14:16:57 -0700263 largeArc: Boolean,
264 clockwise: Boolean
265 ) {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700266 // TODO(njawad): figure out how to handle unsupported framework Path operations)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700267 TODO()
268 // Flutter calls into native Path logic here
269 // native 'Path_arcToPoint'
270 }
271
Mihai Popad8e03d32018-09-24 12:29:50 +0100272 /**
273 * Appends up to four conic curves weighted to describe an oval of `radius`
274 * and rotated by `rotation`.
275 *
276 * The last path point is described by (px, py).
277 *
278 * The first curve begins from the last point in the path and the last ends
279 * at `arcEndDelta.dx + px` and `arcEndDelta.dy + py`. The curves follow a
280 * path in a direction determined by `clockwise` and `largeArc`
281 * in such a way that the sweep angle is always less than 360 degrees.
282 *
283 * A simple line is appended if either either radii are zero, or, both
284 * `arcEndDelta.dx` and `arcEndDelta.dy` are zero. The radii are scaled to
285 * fit the last path point if both are greater than zero but too small to
286 * describe an arc.
287 */
Nader Jawade341cd12018-08-29 14:16:57 -0700288 fun relativeArcToPoint(
289 arcEndDelta: Offset,
290 radius: Radius = Radius.zero,
George Mountb1cdd862018-12-21 10:54:42 -0800291 rotation: Float = 0.0f,
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700292 largeArc: Boolean = false,
Nader Jawade341cd12018-08-29 14:16:57 -0700293 clockwise: Boolean = true
294 ) {
295 _relativeArcToPoint(
296 arcEndDelta.dx,
297 arcEndDelta.dy,
298 radius.x,
299 radius.y,
300 rotation,
301 largeArc,
302 clockwise
303 )
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700304 }
305
George Mountfa5c5482019-04-18 12:22:08 +0100306 @Suppress("UNUSED_PARAMETER")
Nader Jawade341cd12018-08-29 14:16:57 -0700307 private fun _relativeArcToPoint(
George Mountb1cdd862018-12-21 10:54:42 -0800308 arcEndX: Float,
309 arcEndY: Float,
310 radius: Float,
311 radiusY: Float,
312 rotation: Float,
Nader Jawade341cd12018-08-29 14:16:57 -0700313 largeArc: Boolean,
314 clockwise: Boolean
315 ) {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700316 // TODO(njawad): figure out how to handle unsupported framework Path operations)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700317 TODO()
318 // Flutter calls into native Path logic here
319 // native 'Path_relativeArcToPoint';
320 }
321
Mihai Popad8e03d32018-09-24 12:29:50 +0100322 /**
323 * Adds a new subpath that consists of four lines that outline the
324 * given rectangle.
325 */
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700326 fun addRect(rect: Rect) {
Nader Jawade341cd12018-08-29 14:16:57 -0700327 assert(_rectIsValid(rect))
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700328 rectF.set(rect.toFrameworkRect())
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700329 // TODO(njawad) figure out what to do with Path Direction,
Nader Jawade341cd12018-08-29 14:16:57 -0700330 // Flutter does not use it, Platform does
331 internalPath.addRect(rectF, android.graphics.Path.Direction.CCW)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700332 }
333
334 // Not necessary as wrapping platform Path
George Mountfa5c5482019-04-18 12:22:08 +0100335 @Suppress("UNUSED_PARAMETER")
George Mountb1cdd862018-12-21 10:54:42 -0800336 fun _addRect(left: Float, top: Float, right: Float, bottom: Float) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700337 TODO()
338 // Flutter calls into native Path logic here
339 // native 'Path_addRect';
340 }
341
Mihai Popad8e03d32018-09-24 12:29:50 +0100342 /**
343 * Adds a new subpath that consists of a curve that forms the
344 * ellipse that fills the given rectangle.
345 *
346 * To add a circle, pass an appropriate rectangle as `oval`. [Rect.fromCircle]
347 * can be used to easily describe the circle's center [Offset] and radius.
348 */
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700349 fun addOval(oval: Rect) {
Nader Jawade341cd12018-08-29 14:16:57 -0700350 rectF.set(oval.toFrameworkRect())
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700351 // TODO(njawad): figure out what to do with Path Direction,
Nader Jawade341cd12018-08-29 14:16:57 -0700352 // Flutter does not use it, Platform does)
353 internalPath.addOval(rectF, android.graphics.Path.Direction.CCW)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700354 // _addOval(oval.left, oval.top, oval.right, oval.bottom);
355 }
356
357 // Not necessary as wrapping platform Path
George Mountfa5c5482019-04-18 12:22:08 +0100358 @Suppress("UNUSED_PARAMETER")
George Mountb1cdd862018-12-21 10:54:42 -0800359 private fun _addOval(left: Float, top: Float, right: Float, bottom: Float) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700360 TODO()
361 // Flutter calls into native Path logic here
362 // native 'Path_addOval';
363 }
364
Mihai Popad8e03d32018-09-24 12:29:50 +0100365 /**
366 * Adds a new subpath with one arc segment that consists of the arc
367 * that follows the edge of the oval bounded by the given
368 * rectangle, from startAngle radians around the oval up to
369 * startAngle + sweepAngle radians around the oval, with zero
370 * radians being the point on the right hand side of the oval that
371 * crosses the horizontal line that intersects the center of the
372 * rectangle and with positive angles going clockwise around the
373 * oval.
374 */
Nader Jawad14dff8f2019-08-22 15:51:19 -0700375 fun addArcRad(oval: Rect, startAngleRadians: Float, sweepAngleRadians: Float) {
376 addArc(oval, degrees(startAngleRadians), degrees(sweepAngleRadians))
377 }
378
379 /**
380 * Adds a new subpath with one arc segment that consists of the arc
381 * that follows the edge of the oval bounded by the given
382 * rectangle, from startAngle degrees around the oval up to
383 * startAngle + sweepAngle degrees around the oval, with zero
384 * degrees being the point on the right hand side of the oval that
385 * crosses the horizontal line that intersects the center of the
386 * rectangle and with positive angles going clockwise around the
387 * oval.
388 */
389 fun addArc(oval: Rect, startAngleDegrees: Float, sweepAngleDegrees: Float) {
Nader Jawade341cd12018-08-29 14:16:57 -0700390 assert(_rectIsValid(oval))
391 rectF.set(oval.toFrameworkRect())
Nader Jawad14dff8f2019-08-22 15:51:19 -0700392 internalPath.addArc(rectF, startAngleDegrees, sweepAngleDegrees)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700393 }
394
395 // Not necessary as wrapping platform Path
George Mountfa5c5482019-04-18 12:22:08 +0100396 @Suppress("UNUSED_PARAMETER")
George Mountb1cdd862018-12-21 10:54:42 -0800397 private fun _addArc(left: Float, top: Float, right: Float, bottom: Float) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700398 TODO()
399 // Flutter calls into native Path logic here
400 // native 'Path_addArc'
401 }
402
Mihai Popad8e03d32018-09-24 12:29:50 +0100403 /**
404 * Adds a new subpath with a sequence of line segments that connect the given
405 * points.
406 *
407 * If `close` is true, a final line segment will be added that connects the
408 * last point to the first point.
409 *
410 * The `points` argument is interpreted as offsets from the origin.
411 */
George Mountfa5c5482019-04-18 12:22:08 +0100412 @Suppress("UNUSED_PARAMETER")
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700413 fun addPolygon(points: List<Offset>, close: Boolean) {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700414 // TODO(njawad) implement with sequence of "lineTo" calls
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700415 TODO()
416 }
417
George Mountfa5c5482019-04-18 12:22:08 +0100418 @Suppress("UNUSED_PARAMETER")
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700419 private fun _addPolygon(points: FloatArray, close: Boolean) {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700420 // TODO(njawad): implement with sequence of "lineTo" calls)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700421 TODO()
422 // Flutter calls into native code here
423 // native 'Path_addPolygon'
424 }
425
426 fun addRRect(rrect: RRect) {
George Mountb1cdd862018-12-21 10:54:42 -0800427 rectF.set(rrect.left, rrect.top, rrect.right, rrect.bottom)
428 radii[0] = rrect.topLeftRadiusX
429 radii[1] = rrect.topLeftRadiusY
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700430
George Mountb1cdd862018-12-21 10:54:42 -0800431 radii[2] = rrect.topRightRadiusX
432 radii[3] = rrect.topRightRadiusY
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700433
George Mountb1cdd862018-12-21 10:54:42 -0800434 radii[4] = rrect.bottomRightRadiusX
435 radii[5] = rrect.bottomRightRadiusY
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700436
George Mountb1cdd862018-12-21 10:54:42 -0800437 radii[6] = rrect.bottomLeftRadiusX
438 radii[7] = rrect.bottomLeftRadiusY
Nader Jawade341cd12018-08-29 14:16:57 -0700439 internalPath.addRoundRect(rectF, radii, android.graphics.Path.Direction.CCW)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700440 }
441
442 // Not necessary as wrapping platform Path
George Mountfa5c5482019-04-18 12:22:08 +0100443 @Suppress("UNUSED_PARAMETER")
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700444 private fun _addRRect(rrect: FloatArray) {
445 TODO()
446 // Flutter calls into native Path logic here
447 // native 'Path_addRRect';
448 }
449
Mihai Popad8e03d32018-09-24 12:29:50 +0100450 /**
451 * Adds a new subpath that consists of the given `path` offset by the given
452 * `offset`.
453 *
454 * If `matrix4` is specified, the path will be transformed by this matrix
455 * after the matrix is translated by the given offset. The matrix is a 4x4
456 * matrix stored in column major order.
457 */
Andrey Kulikov4e314642019-06-27 16:02:45 +0100458 fun addPath(path: Path, offset: Offset = Offset.zero, matrix: Matrix4? = null) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700459 if (matrix != null) {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700460 // TODO(njawad): update logic to convert Matrix4 -> framework
Nader Jawade341cd12018-08-29 14:16:57 -0700461 // Matrix when ported)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700462 TODO("Refactor to convert Matrix4 to framework Matrix when Matrix4 is ported")
Nader Jawade341cd12018-08-29 14:16:57 -0700463 // internalPath.addPath(path.toFrameworkPath(), matrix);
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700464 } else {
George Mountb1cdd862018-12-21 10:54:42 -0800465 internalPath.addPath(path.toFrameworkPath(), offset.dx, offset.dy)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700466 }
467 }
468
469 // Not necessary as wrapping platform Path
George Mountfa5c5482019-04-18 12:22:08 +0100470 @Suppress("UNUSED_PARAMETER")
George Mountb1cdd862018-12-21 10:54:42 -0800471 private fun _addPath(path: Path, dx: Float, dy: Float) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700472 TODO()
473 // Flutter calls into native Path logic here
474 // native 'Path_addPath';
475 }
476
477 // Not necessary as wrapping platform Path
George Mountfa5c5482019-04-18 12:22:08 +0100478 @Suppress("UNUSED_PARAMETER")
George Mountb1cdd862018-12-21 10:54:42 -0800479 private fun _addPathWithMatrix(path: Path, dx: Float, dy: Float, matrix4: Matrix4) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700480 TODO()
481 // Flutter calls into native Path logic here
482 // native 'Path_addPathWithMatrix';
483 }
484
485 fun extendWithPath(path: Path, offset: Offset, matrix: Matrix4) {
Qingqing Dengb60667f2018-11-20 13:34:53 -0800486 assert(Offset.isValid(offset))
George Mountaeb3ec52018-10-22 16:07:06 -0700487// if (matrix != null) {
Nader Jawade341cd12018-08-29 14:16:57 -0700488 assert(_matrixIsValid(matrix))
489 _extendWithPathAndMatrix(path, offset.dx, offset.dy, matrix)
George Mountaeb3ec52018-10-22 16:07:06 -0700490// } else {
491// _extendWithPath(path, offset.dx, offset.dy)
492// }
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700493 }
494
George Mountfa5c5482019-04-18 12:22:08 +0100495 @Suppress("UNUSED_PARAMETER")
George Mountb1cdd862018-12-21 10:54:42 -0800496 private fun _extendWithPath(path: Path, dx: Float, dy: Float) {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700497 // TODO(njawad): figure out how to handle unsupported framework Path operations)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700498 TODO()
499 // Flutter calls into native Path logic here
500 // native 'Path_extendWithPath';
501 }
502
George Mountfa5c5482019-04-18 12:22:08 +0100503 @Suppress("UNUSED_PARAMETER")
George Mountb1cdd862018-12-21 10:54:42 -0800504 private fun _extendWithPathAndMatrix(path: Path, dx: Float, dy: Float, matrix: Matrix4) {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700505 // TODO(njawad): figure out how to handle unsupported framework Path operations)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700506 TODO()
507 // Flutter calls into native Path logic here
508 // native 'Path_extendWithPathAndMatrix';
509 }
510
Mihai Popad8e03d32018-09-24 12:29:50 +0100511 /**
512 * Closes the last subpath, as if a straight line had been drawn
513 * from the current point to the first point of the subpath.
514 */
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700515 fun close() {
Nader Jawade341cd12018-08-29 14:16:57 -0700516 internalPath.close()
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700517 }
518
Mihai Popad8e03d32018-09-24 12:29:50 +0100519 /**
520 * Clears the [Path] object of all subpaths, returning it to the
521 * same state it had when it was created. The _current point_ is
522 * reset to the origin.
523 */
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700524 fun reset() {
Nader Jawade341cd12018-08-29 14:16:57 -0700525 internalPath.reset()
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700526 }
527
Mihai Popad8e03d32018-09-24 12:29:50 +0100528 /**
529 * Tests to see if the given point is within the path. (That is, whether the
530 * point would be in the visible portion of the path if the path was used
531 * with [Canvas.clipPath].)
532 *
533 * The `point` argument is interpreted as an offset from the origin.
534 *
535 * Returns true if the point is in the path, and false otherwise.
536 */
Andrey Kulikov01f9ac22018-12-05 16:00:07 +0000537 fun contains(offset: Offset): Boolean {
Qingqing Dengb60667f2018-11-20 13:34:53 -0800538 assert(Offset.isValid(offset))
Andrey Kulikov01f9ac22018-12-05 16:00:07 +0000539 return _contains(offset)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700540 }
541
Andrey Kulikov01f9ac22018-12-05 16:00:07 +0000542 private fun _contains(offset: Offset): Boolean {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700543 // TODO(njawad) framework Path implementation does not have a contains method")
544 // TODO(njawad): figure out how to handle unsupported framework Path operations)
Andrey Kulikov01f9ac22018-12-05 16:00:07 +0000545
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700546 // TODO(Andrey): temporary non-efficient implementation)
Andrey Kulikov01f9ac22018-12-05 16:00:07 +0000547 val path = android.graphics.Path()
George Mountb1cdd862018-12-21 10:54:42 -0800548 path.addRect(
549 offset.dx - 0.01f,
550 offset.dy - 0.01f,
551 offset.dx + 0.01f,
552 offset.dy + 0.01f,
553 android.graphics.Path.Direction.CW
554 )
Andrey Kulikov01f9ac22018-12-05 16:00:07 +0000555 if (path.op(internalPath, android.graphics.Path.Op.INTERSECT)) {
556 return !path.isEmpty
557 }
558 return false
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700559 // Flutter calls into native code here
560 // native 'Path_contains';
561 }
562
Mihai Popad8e03d32018-09-24 12:29:50 +0100563 /**
Andrey Kulikov4e314642019-06-27 16:02:45 +0100564 * Translates all the segments of every subpath by the given offset.
Mihai Popad8e03d32018-09-24 12:29:50 +0100565 */
Andrey Kulikov4e314642019-06-27 16:02:45 +0100566 fun shift(offset: Offset) {
567 mMatrix.reset()
568 mMatrix.setTranslate(offset.dx, offset.dy)
569 internalPath.transform(mMatrix)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700570 }
571
Mihai Popad8e03d32018-09-24 12:29:50 +0100572 /**
573 * Returns a copy of the path with all the segments of every
574 * subpath transformed by the given matrix.
575 */
George Mountfa5c5482019-04-18 12:22:08 +0100576 @Suppress("UNUSED_PARAMETER")
George Mount8fd10882019-01-31 14:02:33 -0800577 fun transform(matrix: Matrix4): Path {
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700578 // TODO(njawad): Update implementation with Matrix4 -> android.graphics.Matrix)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700579 TODO("Update implementation with Matrix4 -> android.graphics.Matrix conversion")
Nader Jawade341cd12018-08-29 14:16:57 -0700580 // internalPath.transform(matrix);
George Mountfa5c5482019-04-18 12:22:08 +0100581// return clone()
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700582 }
583
584 // Not necessary as ported implementation with public transform method
George Mountfa5c5482019-04-18 12:22:08 +0100585 @Suppress("UNUSED_PARAMETER")
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700586 private fun _transform(matrix: Matrix4) {
587 TODO()
588 // Flutter calls into native code here
589 // native 'Path_transform';
590 }
591
Mihai Popad8e03d32018-09-24 12:29:50 +0100592 /**
593 * Computes the bounding rectangle for this path.
594 *
595 * A path containing only axis-aligned points on the same straight line will
596 * have no area, and therefore `Rect.isEmpty` will return true for such a
597 * path. Consider checking `rect.width + rect.height > 0.0` instead, or
598 * using the [computeMetrics] API to check the path length.
599 *
600 * For many more elaborate paths, the bounds may be inaccurate. For example,
601 * when a path contains a circle, the points used to compute the bounds are
602 * the circle's implied control points, which form a square around the circle;
603 * if the circle has a transformation applied using [transform] then that
604 * square is rotated, and the (axis-aligned, non-rotated) bounding box
605 * therefore ends up grossly overestimating the actual area covered by the
606 * circle.
607 */
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700608 // see https://skia.org/user/api/SkPath_Reference#SkPath_getBounds
609 fun getBounds(): Rect {
Nader Jawade341cd12018-08-29 14:16:57 -0700610 internalPath.computeBounds(rectF, true)
611 return Rect(
George Mountb1cdd862018-12-21 10:54:42 -0800612 rectF.left,
613 rectF.top,
614 rectF.right,
615 rectF.bottom
Nader Jawade341cd12018-08-29 14:16:57 -0700616 )
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700617 }
618
619 // Not necessary as implemented with framework Path#computeBounds method
620 private fun _getBounds(): Rect {
621 TODO()
622 // Flutter calls into native code here
623 // native 'Path_getBounds';
George Mountfa5c5482019-04-18 12:22:08 +0100624// return Rect(0.0f, 0.0f, 0.0f, 0.0f)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700625 }
626
627 companion object {
Mihai Popad8e03d32018-09-24 12:29:50 +0100628 /**
629 * Combines the two paths according to the manner specified by the given
630 * `operation`.
631 *
632 * The resulting path will be constructed from non-overlapping contours. The
633 * curve order is reduced where possible so that cubics may be turned into
634 * quadratics, and quadratics maybe turned into lines.
Nader Jawadad983532019-08-29 16:27:37 -0700635 *
636 * Throws [IllegalArgumentException] as Android framework does not support this API
Mihai Popad8e03d32018-09-24 12:29:50 +0100637 */
Nader Jawade341cd12018-08-29 14:16:57 -0700638 fun combine(
639 operation: PathOperation,
George Mount8fd10882019-01-31 14:02:33 -0800640 path1: Path,
641 path2: Path
642 ): Path {
643 val path = Path()
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700644
645 if (path.op(path1, path2, operation)) {
Nader Jawade341cd12018-08-29 14:16:57 -0700646 return path
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700647 }
Nader Jawade341cd12018-08-29 14:16:57 -0700648 throw IllegalArgumentException("Path.combine() failed. This may be due an invalid " +
649 "path; in particular, check for NaN values.")
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700650 // TODO(njawad) where do we put Dart's StateError or equivalent?
Nader Jawade341cd12018-08-29 14:16:57 -0700651 // throw StateError('Path.combine() failed. This may be due an invalid path;
652 // in particular, check for NaN values.');
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700653 }
654 }
655
656 // Wrapper around _op method to avoid synthetic accessor in companion combine method
Nader Jawade341cd12018-08-29 14:16:57 -0700657 fun op(
George Mount8fd10882019-01-31 14:02:33 -0800658 path1: Path,
659 path2: Path,
Nader Jawade341cd12018-08-29 14:16:57 -0700660 operation: PathOperation
661 ): Boolean {
662 return _op(path1, path2, operation)
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700663 }
664
Nader Jawade341cd12018-08-29 14:16:57 -0700665 private fun _op(
George Mount8fd10882019-01-31 14:02:33 -0800666 path1: Path,
667 path2: Path,
Nader Jawade341cd12018-08-29 14:16:57 -0700668 operation: PathOperation
669 ): Boolean {
shepshapardfa3d9f72018-10-17 15:35:09 -0700670 // TODO(shepshapard): Our current min SDK is 21, so this check shouldn't be needed.
671// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Nader Jawad5a8ff412018-09-26 16:52:09 -0700672 val op = when (operation) {
673 PathOperation.difference -> android.graphics.Path.Op.DIFFERENCE
674 PathOperation.intersect -> android.graphics.Path.Op.INTERSECT
675 PathOperation.reverseDifference -> android.graphics.Path.Op.REVERSE_DIFFERENCE
676 PathOperation.union -> android.graphics.Path.Op.UNION
677 else -> android.graphics.Path.Op.XOR
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700678 }
Nader Jawad5a8ff412018-09-26 16:52:09 -0700679 return internalPath.op(path1.toFrameworkPath(), path2.toFrameworkPath(), op)
shepshapardfa3d9f72018-10-17 15:35:09 -0700680// } else {
681// return false
682// }
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700683 }
684
Siyamed Sinir13e40c22019-10-04 22:49:40 -0700685 // TODO(njawad) figure out equivalent for PathMetrics for the framework based in Path
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700686//
Nader Jawade341cd12018-08-29 14:16:57 -0700687// // / Creates a [PathMetrics] object for this path.
688// // /
689// // / If `forceClosed` is set to true, the contours of the path will be measured
690// // / as if they had been closed, even if they were not explicitly closed.
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700691// PathMetrics computeMetrics({bool forceClosed: false}) {
692// return new PathMetrics._(this, forceClosed);
693// }
694
Andrey Kulikov48bd6fa2019-06-05 14:52:07 +0100695 /**
696 * Returns the path's convexity, as defined by the content of the path.
697 *
698 * A path is convex if it has a single contour, and only ever curves in a
699 * single direction.
700 *
701 * This function will calculate the convexity of the path from its control
702 * points, and cache the result.
703 */
704 val isConvex: Boolean get() = internalPath.isConvex
705
706 /**
707 * Returns true if the path is empty (contains no lines or curves)
708 */
709 val isEmpty: Boolean get() = internalPath.isEmpty
710
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700711 private fun _rectIsValid(rect: Rect): Boolean {
George Mountb1cdd862018-12-21 10:54:42 -0800712 assert(Float.NaN != rect.left) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700713 "Rect.left is NaN"
714 }
George Mountb1cdd862018-12-21 10:54:42 -0800715 assert(Float.NaN != rect.top) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700716 "Rect.top is NaN"
717 }
George Mountb1cdd862018-12-21 10:54:42 -0800718 assert(Float.NaN != rect.right) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700719 "Rect.right is NaN"
720 }
George Mountb1cdd862018-12-21 10:54:42 -0800721 assert(Float.NaN != rect.bottom) {
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700722 "Rect.bottom is NaN"
723 }
Nader Jawade341cd12018-08-29 14:16:57 -0700724 return true
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700725 }
726
George Mountfa5c5482019-04-18 12:22:08 +0100727 @Suppress("UNUSED_PARAMETER")
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700728 private fun _matrixIsValid(matrix: Matrix4): Boolean {
Nader Jawade341cd12018-08-29 14:16:57 -0700729 return true
Nader Jawad4e7a5ad2018-08-21 17:11:38 -0700730 }
George Mountb1cdd862018-12-21 10:54:42 -0800731}