Andrey Kulikov | a7d1104 | 2018-08-09 14:41:54 +0100 | [diff] [blame] | 1 | /* |
| 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 Jawad | ad98353 | 2019-08-29 16:27:37 -0700 | [diff] [blame] | 17 | package androidx.ui.graphics |
Andrey Kulikov | a7d1104 | 2018-08-09 14:41:54 +0100 | [diff] [blame] | 18 | |
George Mount | 5de30a5 | 2019-01-29 13:55:54 -0800 | [diff] [blame] | 19 | import androidx.ui.core.toFrameworkRect |
George Mount | 842c8c1 | 2020-01-08 16:03:42 -0800 | [diff] [blame] | 20 | import androidx.ui.geometry.Offset |
| 21 | import androidx.ui.geometry.RRect |
| 22 | import androidx.ui.geometry.Radius |
| 23 | import androidx.ui.geometry.Rect |
George Mount | c525e36 | 2020-01-10 14:53:39 -0800 | [diff] [blame] | 24 | import androidx.ui.graphics.vectormath.Matrix4 |
| 25 | import androidx.ui.graphics.vectormath.degrees |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 26 | |
Qingqing Deng | 21d88d8 | 2019-01-28 15:37:37 -0800 | [diff] [blame] | 27 | class Path(private val internalPath: android.graphics.Path = android.graphics.Path()) { |
Andrey Kulikov | 01f9ac2 | 2018-12-05 16:00:07 +0000 | [diff] [blame] | 28 | |
| 29 | // Temporary value holders to reuse an object (not part of a state): |
Nader Jawad | ae64984 | 2018-09-05 14:43:04 -0700 | [diff] [blame] | 30 | private val rectF = android.graphics.RectF() |
Nader Jawad | ae64984 | 2018-09-05 14:43:04 -0700 | [diff] [blame] | 31 | private val radii = FloatArray(8) |
Nader Jawad | ae64984 | 2018-09-05 14:43:04 -0700 | [diff] [blame] | 32 | private val mMatrix = android.graphics.Matrix() |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 33 | |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 34 | fun toFrameworkPath(): android.graphics.Path = internalPath |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 35 | |
George Mount | 8fd1088 | 2019-01-31 14:02:33 -0800 | [diff] [blame] | 36 | private fun clone() = Path().apply { |
Andrey Kulikov | 01f9ac2 | 2018-12-05 16:00:07 +0000 | [diff] [blame] | 37 | internalPath.set(this@Path.internalPath) |
| 38 | } |
| 39 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 40 | /** |
| 41 | * Determines how the interior of this path is calculated. |
| 42 | * |
| 43 | * Defaults to the non-zero winding rule, [PathFillType.nonZero]. |
| 44 | */ |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 45 | fun getFillType(): PathFillType { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 46 | if (internalPath.fillType == android.graphics.Path.FillType.EVEN_ODD) { |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 47 | return PathFillType.evenOdd |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 48 | } else { |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 49 | return PathFillType.nonZero |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 50 | } |
| 51 | } |
| 52 | |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 53 | fun setFillType(value: PathFillType) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 54 | internalPath.fillType = |
| 55 | if (value == PathFillType.evenOdd) { |
| 56 | android.graphics.Path.FillType.EVEN_ODD |
| 57 | } else { |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 58 | android.graphics.Path.FillType.WINDING |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 59 | } |
| 60 | } |
| 61 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 62 | /** Starts a new subpath at the given coordinate. */ |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 63 | fun moveTo(dx: Float, dy: Float) { |
| 64 | internalPath.moveTo(dx, dy) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 65 | } |
| 66 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 67 | /** Starts a new subpath at the given offset from the current point. */ |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 68 | fun relativeMoveTo(dx: Float, dy: Float) { |
| 69 | internalPath.rMoveTo(dx, dy) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 70 | } |
| 71 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 72 | /** |
| 73 | * Adds a straight line segment from the current point to the given |
| 74 | * point. |
| 75 | */ |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 76 | fun lineTo(dx: Float, dy: Float) { |
| 77 | internalPath.lineTo(dx, dy) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 78 | } |
| 79 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 80 | /** |
| 81 | * Adds a straight line segment from the current point to the point |
| 82 | * at the given offset from the current point. |
| 83 | */ |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 84 | fun relativeLineTo(dx: Float, dy: Float) { |
| 85 | internalPath.rLineTo(dx, dy) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 86 | } |
| 87 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 88 | /** |
| 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 Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 93 | fun quadraticBezierTo(x1: Float, y1: Float, x2: Float, y2: Float) { |
| 94 | internalPath.quadTo(x1, y1, x2, y2) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 95 | } |
| 96 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 97 | /** |
| 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 Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 103 | fun relativeQuadraticBezierTo(x1: Float, y1: Float, x2: Float, y2: Float) { |
| 104 | internalPath.rQuadTo(x1, y1, x2, y2) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 105 | } |
| 106 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 107 | /** |
| 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 Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 112 | fun cubicTo(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 113 | internalPath.cubicTo( |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 114 | x1, y1, |
| 115 | x2, y2, |
| 116 | x3, y3 |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 117 | ) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 118 | } |
| 119 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 120 | /** |
| 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 Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 126 | fun relativeCubicTo(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 127 | internalPath.rCubicTo( |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 128 | x1, y1, |
| 129 | x2, y2, |
| 130 | x3, y3 |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 131 | ) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 132 | } |
| 133 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 134 | /** |
| 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 Jawad | ad98353 | 2019-08-29 16:27:37 -0700 | [diff] [blame] | 140 | * |
| 141 | * Throws [UnsupportedOperationException] as Android framework does not support this API |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 142 | */ |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 143 | @Suppress("UNUSED_PARAMETER") |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 144 | fun conicTo(x1: Float, y1: Float, x2: Float, y2: Float, w: Float) { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 145 | // TODO(njawad) figure out how to handle unsupported framework Path operations |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 146 | throw UnsupportedOperationException("conicTo not supported in framework Path") |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 147 | } |
| 148 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 149 | /** |
| 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 Jawad | ad98353 | 2019-08-29 16:27:37 -0700 | [diff] [blame] | 156 | * |
| 157 | * Throws [UnsupportedOperationException] as Android framework does not support this API |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 158 | */ |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 159 | @Suppress("UNUSED_PARAMETER") |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 160 | fun relativeConicTo(x1: Float, y1: Float, x2: Float, y2: Float, w: Float) { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 161 | // TODO(njawad) figure out how to handle unsupported framework Path operations |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 162 | throw UnsupportedOperationException("relativeConicTo not supported in framework Path") |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 163 | } |
| 164 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 165 | /** |
Nader Jawad | 14dff8f | 2019-08-22 15:51:19 -0700 | [diff] [blame] | 166 | * If the [forceMoveTo] argument is false, adds a straight line |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 167 | * segment and an arc segment. |
| 168 | * |
Nader Jawad | 14dff8f | 2019-08-22 15:51:19 -0700 | [diff] [blame] | 169 | * If the [forceMoveTo] argument is true, starts a new subpath |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 170 | * 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 Jawad | 14dff8f | 2019-08-22 15:51:19 -0700 | [diff] [blame] | 183 | 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 Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 216 | val left = rect.left |
| 217 | val top = rect.top |
| 218 | val right = rect.right |
| 219 | val bottom = rect.bottom |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 220 | rectF.set(left, top, right, bottom) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 221 | internalPath.arcTo( |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 222 | rectF, |
Nader Jawad | 14dff8f | 2019-08-22 15:51:19 -0700 | [diff] [blame] | 223 | startAngleDegrees, |
| 224 | sweepAngleDegrees, |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 225 | forceMoveTo |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 226 | ) |
| 227 | } |
| 228 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 229 | /** |
| 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 Jawad | ad98353 | 2019-08-29 16:27:37 -0700 | [diff] [blame] | 242 | * Throws [UnsupportedOperationException] as Android framework does not support this API |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 243 | */ |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 244 | @Suppress("UNUSED_PARAMETER") |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 245 | fun arcToPoint( |
| 246 | arcEnd: Offset, |
| 247 | radius: Radius = Radius.zero, |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 248 | rotation: Float = 0.0f, |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 249 | largeArc: Boolean = false, |
| 250 | clockwise: Boolean = true |
| 251 | ) { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 252 | // TODO(njawad) figure out how to handle unsupported framework Path operations |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 253 | throw UnsupportedOperationException("arcToPoint not supported in framework Path") |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 254 | } |
| 255 | |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 256 | @Suppress("UNUSED_PARAMETER") |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 257 | private fun _arcToPoint( |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 258 | arcEndX: Float, |
| 259 | arcEndY: Float, |
| 260 | radius: Float, |
| 261 | radiusY: Float, |
| 262 | rotation: Float, |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 263 | largeArc: Boolean, |
| 264 | clockwise: Boolean |
| 265 | ) { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 266 | // TODO(njawad): figure out how to handle unsupported framework Path operations) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 267 | TODO() |
| 268 | // Flutter calls into native Path logic here |
| 269 | // native 'Path_arcToPoint' |
| 270 | } |
| 271 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 272 | /** |
| 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 Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 288 | fun relativeArcToPoint( |
| 289 | arcEndDelta: Offset, |
| 290 | radius: Radius = Radius.zero, |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 291 | rotation: Float = 0.0f, |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 292 | largeArc: Boolean = false, |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 293 | 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 Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 304 | } |
| 305 | |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 306 | @Suppress("UNUSED_PARAMETER") |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 307 | private fun _relativeArcToPoint( |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 308 | arcEndX: Float, |
| 309 | arcEndY: Float, |
| 310 | radius: Float, |
| 311 | radiusY: Float, |
| 312 | rotation: Float, |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 313 | largeArc: Boolean, |
| 314 | clockwise: Boolean |
| 315 | ) { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 316 | // TODO(njawad): figure out how to handle unsupported framework Path operations) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 317 | TODO() |
| 318 | // Flutter calls into native Path logic here |
| 319 | // native 'Path_relativeArcToPoint'; |
| 320 | } |
| 321 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 322 | /** |
| 323 | * Adds a new subpath that consists of four lines that outline the |
| 324 | * given rectangle. |
| 325 | */ |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 326 | fun addRect(rect: Rect) { |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 327 | assert(_rectIsValid(rect)) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 328 | rectF.set(rect.toFrameworkRect()) |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 329 | // TODO(njawad) figure out what to do with Path Direction, |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 330 | // Flutter does not use it, Platform does |
| 331 | internalPath.addRect(rectF, android.graphics.Path.Direction.CCW) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 332 | } |
| 333 | |
| 334 | // Not necessary as wrapping platform Path |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 335 | @Suppress("UNUSED_PARAMETER") |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 336 | fun _addRect(left: Float, top: Float, right: Float, bottom: Float) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 337 | TODO() |
| 338 | // Flutter calls into native Path logic here |
| 339 | // native 'Path_addRect'; |
| 340 | } |
| 341 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 342 | /** |
| 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 Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 349 | fun addOval(oval: Rect) { |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 350 | rectF.set(oval.toFrameworkRect()) |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 351 | // TODO(njawad): figure out what to do with Path Direction, |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 352 | // Flutter does not use it, Platform does) |
| 353 | internalPath.addOval(rectF, android.graphics.Path.Direction.CCW) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 354 | // _addOval(oval.left, oval.top, oval.right, oval.bottom); |
| 355 | } |
| 356 | |
| 357 | // Not necessary as wrapping platform Path |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 358 | @Suppress("UNUSED_PARAMETER") |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 359 | private fun _addOval(left: Float, top: Float, right: Float, bottom: Float) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 360 | TODO() |
| 361 | // Flutter calls into native Path logic here |
| 362 | // native 'Path_addOval'; |
| 363 | } |
| 364 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 365 | /** |
| 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 Jawad | 14dff8f | 2019-08-22 15:51:19 -0700 | [diff] [blame] | 375 | 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 Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 390 | assert(_rectIsValid(oval)) |
| 391 | rectF.set(oval.toFrameworkRect()) |
Nader Jawad | 14dff8f | 2019-08-22 15:51:19 -0700 | [diff] [blame] | 392 | internalPath.addArc(rectF, startAngleDegrees, sweepAngleDegrees) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 393 | } |
| 394 | |
| 395 | // Not necessary as wrapping platform Path |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 396 | @Suppress("UNUSED_PARAMETER") |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 397 | private fun _addArc(left: Float, top: Float, right: Float, bottom: Float) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 398 | TODO() |
| 399 | // Flutter calls into native Path logic here |
| 400 | // native 'Path_addArc' |
| 401 | } |
| 402 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 403 | /** |
| 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 Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 412 | @Suppress("UNUSED_PARAMETER") |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 413 | fun addPolygon(points: List<Offset>, close: Boolean) { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 414 | // TODO(njawad) implement with sequence of "lineTo" calls |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 415 | TODO() |
| 416 | } |
| 417 | |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 418 | @Suppress("UNUSED_PARAMETER") |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 419 | private fun _addPolygon(points: FloatArray, close: Boolean) { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 420 | // TODO(njawad): implement with sequence of "lineTo" calls) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 421 | TODO() |
| 422 | // Flutter calls into native code here |
| 423 | // native 'Path_addPolygon' |
| 424 | } |
| 425 | |
| 426 | fun addRRect(rrect: RRect) { |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 427 | rectF.set(rrect.left, rrect.top, rrect.right, rrect.bottom) |
| 428 | radii[0] = rrect.topLeftRadiusX |
| 429 | radii[1] = rrect.topLeftRadiusY |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 430 | |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 431 | radii[2] = rrect.topRightRadiusX |
| 432 | radii[3] = rrect.topRightRadiusY |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 433 | |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 434 | radii[4] = rrect.bottomRightRadiusX |
| 435 | radii[5] = rrect.bottomRightRadiusY |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 436 | |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 437 | radii[6] = rrect.bottomLeftRadiusX |
| 438 | radii[7] = rrect.bottomLeftRadiusY |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 439 | internalPath.addRoundRect(rectF, radii, android.graphics.Path.Direction.CCW) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 440 | } |
| 441 | |
| 442 | // Not necessary as wrapping platform Path |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 443 | @Suppress("UNUSED_PARAMETER") |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 444 | private fun _addRRect(rrect: FloatArray) { |
| 445 | TODO() |
| 446 | // Flutter calls into native Path logic here |
| 447 | // native 'Path_addRRect'; |
| 448 | } |
| 449 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 450 | /** |
| 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 Kulikov | 4e31464 | 2019-06-27 16:02:45 +0100 | [diff] [blame] | 458 | fun addPath(path: Path, offset: Offset = Offset.zero, matrix: Matrix4? = null) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 459 | if (matrix != null) { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 460 | // TODO(njawad): update logic to convert Matrix4 -> framework |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 461 | // Matrix when ported) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 462 | TODO("Refactor to convert Matrix4 to framework Matrix when Matrix4 is ported") |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 463 | // internalPath.addPath(path.toFrameworkPath(), matrix); |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 464 | } else { |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 465 | internalPath.addPath(path.toFrameworkPath(), offset.dx, offset.dy) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 466 | } |
| 467 | } |
| 468 | |
| 469 | // Not necessary as wrapping platform Path |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 470 | @Suppress("UNUSED_PARAMETER") |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 471 | private fun _addPath(path: Path, dx: Float, dy: Float) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 472 | TODO() |
| 473 | // Flutter calls into native Path logic here |
| 474 | // native 'Path_addPath'; |
| 475 | } |
| 476 | |
| 477 | // Not necessary as wrapping platform Path |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 478 | @Suppress("UNUSED_PARAMETER") |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 479 | private fun _addPathWithMatrix(path: Path, dx: Float, dy: Float, matrix4: Matrix4) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 480 | 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 Deng | b60667f | 2018-11-20 13:34:53 -0800 | [diff] [blame] | 486 | assert(Offset.isValid(offset)) |
George Mount | aeb3ec5 | 2018-10-22 16:07:06 -0700 | [diff] [blame] | 487 | // if (matrix != null) { |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 488 | assert(_matrixIsValid(matrix)) |
| 489 | _extendWithPathAndMatrix(path, offset.dx, offset.dy, matrix) |
George Mount | aeb3ec5 | 2018-10-22 16:07:06 -0700 | [diff] [blame] | 490 | // } else { |
| 491 | // _extendWithPath(path, offset.dx, offset.dy) |
| 492 | // } |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 493 | } |
| 494 | |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 495 | @Suppress("UNUSED_PARAMETER") |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 496 | private fun _extendWithPath(path: Path, dx: Float, dy: Float) { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 497 | // TODO(njawad): figure out how to handle unsupported framework Path operations) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 498 | TODO() |
| 499 | // Flutter calls into native Path logic here |
| 500 | // native 'Path_extendWithPath'; |
| 501 | } |
| 502 | |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 503 | @Suppress("UNUSED_PARAMETER") |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 504 | private fun _extendWithPathAndMatrix(path: Path, dx: Float, dy: Float, matrix: Matrix4) { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 505 | // TODO(njawad): figure out how to handle unsupported framework Path operations) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 506 | TODO() |
| 507 | // Flutter calls into native Path logic here |
| 508 | // native 'Path_extendWithPathAndMatrix'; |
| 509 | } |
| 510 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 511 | /** |
| 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 Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 515 | fun close() { |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 516 | internalPath.close() |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 517 | } |
| 518 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 519 | /** |
| 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 Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 524 | fun reset() { |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 525 | internalPath.reset() |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 526 | } |
| 527 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 528 | /** |
| 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 Kulikov | 01f9ac2 | 2018-12-05 16:00:07 +0000 | [diff] [blame] | 537 | fun contains(offset: Offset): Boolean { |
Qingqing Deng | b60667f | 2018-11-20 13:34:53 -0800 | [diff] [blame] | 538 | assert(Offset.isValid(offset)) |
Andrey Kulikov | 01f9ac2 | 2018-12-05 16:00:07 +0000 | [diff] [blame] | 539 | return _contains(offset) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 540 | } |
| 541 | |
Andrey Kulikov | 01f9ac2 | 2018-12-05 16:00:07 +0000 | [diff] [blame] | 542 | private fun _contains(offset: Offset): Boolean { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 543 | // TODO(njawad) framework Path implementation does not have a contains method") |
| 544 | // TODO(njawad): figure out how to handle unsupported framework Path operations) |
Andrey Kulikov | 01f9ac2 | 2018-12-05 16:00:07 +0000 | [diff] [blame] | 545 | |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 546 | // TODO(Andrey): temporary non-efficient implementation) |
Andrey Kulikov | 01f9ac2 | 2018-12-05 16:00:07 +0000 | [diff] [blame] | 547 | val path = android.graphics.Path() |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 548 | 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 Kulikov | 01f9ac2 | 2018-12-05 16:00:07 +0000 | [diff] [blame] | 555 | if (path.op(internalPath, android.graphics.Path.Op.INTERSECT)) { |
| 556 | return !path.isEmpty |
| 557 | } |
| 558 | return false |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 559 | // Flutter calls into native code here |
| 560 | // native 'Path_contains'; |
| 561 | } |
| 562 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 563 | /** |
Andrey Kulikov | 4e31464 | 2019-06-27 16:02:45 +0100 | [diff] [blame] | 564 | * Translates all the segments of every subpath by the given offset. |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 565 | */ |
Andrey Kulikov | 4e31464 | 2019-06-27 16:02:45 +0100 | [diff] [blame] | 566 | fun shift(offset: Offset) { |
| 567 | mMatrix.reset() |
| 568 | mMatrix.setTranslate(offset.dx, offset.dy) |
| 569 | internalPath.transform(mMatrix) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 570 | } |
| 571 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 572 | /** |
| 573 | * Returns a copy of the path with all the segments of every |
| 574 | * subpath transformed by the given matrix. |
| 575 | */ |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 576 | @Suppress("UNUSED_PARAMETER") |
George Mount | 8fd1088 | 2019-01-31 14:02:33 -0800 | [diff] [blame] | 577 | fun transform(matrix: Matrix4): Path { |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 578 | // TODO(njawad): Update implementation with Matrix4 -> android.graphics.Matrix) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 579 | TODO("Update implementation with Matrix4 -> android.graphics.Matrix conversion") |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 580 | // internalPath.transform(matrix); |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 581 | // return clone() |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 582 | } |
| 583 | |
| 584 | // Not necessary as ported implementation with public transform method |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 585 | @Suppress("UNUSED_PARAMETER") |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 586 | private fun _transform(matrix: Matrix4) { |
| 587 | TODO() |
| 588 | // Flutter calls into native code here |
| 589 | // native 'Path_transform'; |
| 590 | } |
| 591 | |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 592 | /** |
| 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 Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 608 | // see https://skia.org/user/api/SkPath_Reference#SkPath_getBounds |
| 609 | fun getBounds(): Rect { |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 610 | internalPath.computeBounds(rectF, true) |
| 611 | return Rect( |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 612 | rectF.left, |
| 613 | rectF.top, |
| 614 | rectF.right, |
| 615 | rectF.bottom |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 616 | ) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 617 | } |
| 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 Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 624 | // return Rect(0.0f, 0.0f, 0.0f, 0.0f) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 625 | } |
| 626 | |
| 627 | companion object { |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 628 | /** |
| 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 Jawad | ad98353 | 2019-08-29 16:27:37 -0700 | [diff] [blame] | 635 | * |
| 636 | * Throws [IllegalArgumentException] as Android framework does not support this API |
Mihai Popa | d8e03d3 | 2018-09-24 12:29:50 +0100 | [diff] [blame] | 637 | */ |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 638 | fun combine( |
| 639 | operation: PathOperation, |
George Mount | 8fd1088 | 2019-01-31 14:02:33 -0800 | [diff] [blame] | 640 | path1: Path, |
| 641 | path2: Path |
| 642 | ): Path { |
| 643 | val path = Path() |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 644 | |
| 645 | if (path.op(path1, path2, operation)) { |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 646 | return path |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 647 | } |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 648 | throw IllegalArgumentException("Path.combine() failed. This may be due an invalid " + |
| 649 | "path; in particular, check for NaN values.") |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 650 | // TODO(njawad) where do we put Dart's StateError or equivalent? |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 651 | // throw StateError('Path.combine() failed. This may be due an invalid path; |
| 652 | // in particular, check for NaN values.'); |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 653 | } |
| 654 | } |
| 655 | |
| 656 | // Wrapper around _op method to avoid synthetic accessor in companion combine method |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 657 | fun op( |
George Mount | 8fd1088 | 2019-01-31 14:02:33 -0800 | [diff] [blame] | 658 | path1: Path, |
| 659 | path2: Path, |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 660 | operation: PathOperation |
| 661 | ): Boolean { |
| 662 | return _op(path1, path2, operation) |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 663 | } |
| 664 | |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 665 | private fun _op( |
George Mount | 8fd1088 | 2019-01-31 14:02:33 -0800 | [diff] [blame] | 666 | path1: Path, |
| 667 | path2: Path, |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 668 | operation: PathOperation |
| 669 | ): Boolean { |
shepshapard | fa3d9f7 | 2018-10-17 15:35:09 -0700 | [diff] [blame] | 670 | // 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 Jawad | 5a8ff41 | 2018-09-26 16:52:09 -0700 | [diff] [blame] | 672 | 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 Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 678 | } |
Nader Jawad | 5a8ff41 | 2018-09-26 16:52:09 -0700 | [diff] [blame] | 679 | return internalPath.op(path1.toFrameworkPath(), path2.toFrameworkPath(), op) |
shepshapard | fa3d9f7 | 2018-10-17 15:35:09 -0700 | [diff] [blame] | 680 | // } else { |
| 681 | // return false |
| 682 | // } |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 683 | } |
| 684 | |
Siyamed Sinir | 13e40c2 | 2019-10-04 22:49:40 -0700 | [diff] [blame] | 685 | // TODO(njawad) figure out equivalent for PathMetrics for the framework based in Path |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 686 | // |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 687 | // // / 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 Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 691 | // PathMetrics computeMetrics({bool forceClosed: false}) { |
| 692 | // return new PathMetrics._(this, forceClosed); |
| 693 | // } |
| 694 | |
Andrey Kulikov | 48bd6fa | 2019-06-05 14:52:07 +0100 | [diff] [blame] | 695 | /** |
| 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 Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 711 | private fun _rectIsValid(rect: Rect): Boolean { |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 712 | assert(Float.NaN != rect.left) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 713 | "Rect.left is NaN" |
| 714 | } |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 715 | assert(Float.NaN != rect.top) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 716 | "Rect.top is NaN" |
| 717 | } |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 718 | assert(Float.NaN != rect.right) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 719 | "Rect.right is NaN" |
| 720 | } |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 721 | assert(Float.NaN != rect.bottom) { |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 722 | "Rect.bottom is NaN" |
| 723 | } |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 724 | return true |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 725 | } |
| 726 | |
George Mount | fa5c548 | 2019-04-18 12:22:08 +0100 | [diff] [blame] | 727 | @Suppress("UNUSED_PARAMETER") |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 728 | private fun _matrixIsValid(matrix: Matrix4): Boolean { |
Nader Jawad | e341cd1 | 2018-08-29 14:16:57 -0700 | [diff] [blame] | 729 | return true |
Nader Jawad | 4e7a5ad | 2018-08-21 17:11:38 -0700 | [diff] [blame] | 730 | } |
George Mount | b1cdd86 | 2018-12-21 10:54:42 -0800 | [diff] [blame] | 731 | } |