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
17 package androidx.compose.ui.graphics
18
19 import android.graphics.Matrix as PlatformMatrix
20 import android.graphics.Path as PlatformPath
21 import android.graphics.RectF as PlatformRectF
22 import androidx.compose.ui.geometry.Offset
23 import androidx.compose.ui.geometry.Rect
24 import androidx.compose.ui.geometry.RoundRect
25
Pathnull26 actual fun Path(): Path = AndroidPath()
27
28 /** Convert the [android.graphics.Path] instance into a Compose-compatible Path */
29 fun PlatformPath.asComposePath(): Path = AndroidPath(this)
30
31 /**
32 * @Throws UnsupportedOperationException if this Path is not backed by an [android.graphics.Path].
33 */
34 @Suppress("NOTHING_TO_INLINE")
35 inline fun Path.asAndroidPath(): PlatformPath =
36 if (this is AndroidPath) {
37 internalPath
38 } else {
39 throw UnsupportedOperationException("Unable to obtain android.graphics.Path")
40 }
41
42 @Suppress("OVERRIDE_DEPRECATION") // b/407491706
43 /* actual */ class AndroidPath(val internalPath: PlatformPath = PlatformPath()) : Path {
44
45 // Temporary value holders to reuse an object (not part of a state):
46 private var rectF: PlatformRectF? = null
47 private var radii: FloatArray? = null
48 private var mMatrix: PlatformMatrix? = null
49
50 override var fillType: PathFillType
51 get() {
52 return if (internalPath.fillType == PlatformPath.FillType.EVEN_ODD) {
53 PathFillType.EvenOdd
54 } else {
55 PathFillType.NonZero
56 }
57 }
58 set(value) {
59 internalPath.fillType =
60 if (value == PathFillType.EvenOdd) {
61 PlatformPath.FillType.EVEN_ODD
62 } else {
63 PlatformPath.FillType.WINDING
64 }
65 }
66
moveTonull67 override fun moveTo(x: Float, y: Float) {
68 internalPath.moveTo(x, y)
69 }
70
relativeMoveTonull71 override fun relativeMoveTo(dx: Float, dy: Float) {
72 internalPath.rMoveTo(dx, dy)
73 }
74
lineTonull75 override fun lineTo(x: Float, y: Float) {
76 internalPath.lineTo(x, y)
77 }
78
relativeLineTonull79 override fun relativeLineTo(dx: Float, dy: Float) {
80 internalPath.rLineTo(dx, dy)
81 }
82
quadraticBezierTonull83 override fun quadraticBezierTo(x1: Float, y1: Float, x2: Float, y2: Float) {
84 internalPath.quadTo(x1, y1, x2, y2)
85 }
86
quadraticTonull87 override fun quadraticTo(x1: Float, y1: Float, x2: Float, y2: Float) {
88 internalPath.quadTo(x1, y1, x2, y2)
89 }
90
relativeQuadraticBezierTonull91 override fun relativeQuadraticBezierTo(dx1: Float, dy1: Float, dx2: Float, dy2: Float) {
92 internalPath.rQuadTo(dx1, dy1, dx2, dy2)
93 }
94
relativeQuadraticTonull95 override fun relativeQuadraticTo(dx1: Float, dy1: Float, dx2: Float, dy2: Float) {
96 internalPath.rQuadTo(dx1, dy1, dx2, dy2)
97 }
98
cubicTonull99 override fun cubicTo(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) {
100 internalPath.cubicTo(x1, y1, x2, y2, x3, y3)
101 }
102
relativeCubicTonull103 override fun relativeCubicTo(
104 dx1: Float,
105 dy1: Float,
106 dx2: Float,
107 dy2: Float,
108 dx3: Float,
109 dy3: Float
110 ) {
111 internalPath.rCubicTo(dx1, dy1, dx2, dy2, dx3, dy3)
112 }
113
arcTonull114 override fun arcTo(
115 rect: Rect,
116 startAngleDegrees: Float,
117 sweepAngleDegrees: Float,
118 forceMoveTo: Boolean
119 ) {
120 val left = rect.left
121 val top = rect.top
122 val right = rect.right
123 val bottom = rect.bottom
124 if (rectF == null) rectF = PlatformRectF()
125 rectF!!.set(left, top, right, bottom)
126 internalPath.arcTo(rectF!!, startAngleDegrees, sweepAngleDegrees, forceMoveTo)
127 }
128
addRectnull129 override fun addRect(rect: Rect) {
130 addRect(rect, Path.Direction.CounterClockwise)
131 }
132
addRectnull133 override fun addRect(rect: Rect, direction: Path.Direction) {
134 validateRectangle(rect)
135 if (rectF == null) rectF = PlatformRectF()
136 rectF!!.set(rect.left, rect.top, rect.right, rect.bottom)
137 internalPath.addRect(rectF!!, direction.toPlatformPathDirection())
138 }
139
addOvalnull140 override fun addOval(oval: Rect) {
141 addOval(oval, Path.Direction.CounterClockwise)
142 }
143
addOvalnull144 override fun addOval(oval: Rect, direction: Path.Direction) {
145 if (rectF == null) rectF = PlatformRectF()
146 rectF!!.set(oval.left, oval.top, oval.right, oval.bottom)
147 internalPath.addOval(rectF!!, direction.toPlatformPathDirection())
148 }
149
addRoundRectnull150 override fun addRoundRect(roundRect: RoundRect) {
151 addRoundRect(roundRect, Path.Direction.CounterClockwise)
152 }
153
addRoundRectnull154 override fun addRoundRect(roundRect: RoundRect, direction: Path.Direction) {
155 if (rectF == null) rectF = PlatformRectF()
156 rectF!!.set(roundRect.left, roundRect.top, roundRect.right, roundRect.bottom)
157
158 if (radii == null) radii = FloatArray(8)
159 with(radii!!) {
160 this[0] = roundRect.topLeftCornerRadius.x
161 this[1] = roundRect.topLeftCornerRadius.y
162
163 this[2] = roundRect.topRightCornerRadius.x
164 this[3] = roundRect.topRightCornerRadius.y
165
166 this[4] = roundRect.bottomRightCornerRadius.x
167 this[5] = roundRect.bottomRightCornerRadius.y
168
169 this[6] = roundRect.bottomLeftCornerRadius.x
170 this[7] = roundRect.bottomLeftCornerRadius.y
171 }
172 internalPath.addRoundRect(rectF!!, radii!!, direction.toPlatformPathDirection())
173 }
174
addArcRadnull175 override fun addArcRad(oval: Rect, startAngleRadians: Float, sweepAngleRadians: Float) {
176 addArc(oval, degrees(startAngleRadians), degrees(sweepAngleRadians))
177 }
178
addArcnull179 override fun addArc(oval: Rect, startAngleDegrees: Float, sweepAngleDegrees: Float) {
180 validateRectangle(oval)
181 if (rectF == null) rectF = PlatformRectF()
182 rectF!!.set(oval.left, oval.top, oval.right, oval.bottom)
183 internalPath.addArc(rectF!!, startAngleDegrees, sweepAngleDegrees)
184 }
185
addPathnull186 override fun addPath(path: Path, offset: Offset) {
187 internalPath.addPath(path.asAndroidPath(), offset.x, offset.y)
188 }
189
closenull190 override fun close() {
191 internalPath.close()
192 }
193
resetnull194 override fun reset() {
195 internalPath.reset()
196 }
197
rewindnull198 override fun rewind() {
199 internalPath.rewind()
200 }
201
translatenull202 override fun translate(offset: Offset) {
203 if (mMatrix == null) mMatrix = PlatformMatrix() else mMatrix!!.reset()
204 mMatrix!!.setTranslate(offset.x, offset.y)
205 internalPath.transform(mMatrix!!)
206 }
207
transformnull208 override fun transform(matrix: Matrix) {
209 if (mMatrix == null) mMatrix = PlatformMatrix()
210 mMatrix!!.setFrom(matrix)
211 internalPath.transform(mMatrix!!)
212 }
213
getBoundsnull214 override fun getBounds(): Rect {
215 if (rectF == null) rectF = PlatformRectF()
216 with(rectF!!) {
217 @Suppress("DEPRECATION") internalPath.computeBounds(this, true)
218 return Rect(this.left, this.top, this.right, this.bottom)
219 }
220 }
221
opnull222 override fun op(path1: Path, path2: Path, operation: PathOperation): Boolean {
223 val op =
224 when (operation) {
225 PathOperation.Difference -> PlatformPath.Op.DIFFERENCE
226 PathOperation.Intersect -> PlatformPath.Op.INTERSECT
227 PathOperation.ReverseDifference -> PlatformPath.Op.REVERSE_DIFFERENCE
228 PathOperation.Union -> PlatformPath.Op.UNION
229 else -> PlatformPath.Op.XOR
230 }
231 return internalPath.op(path1.asAndroidPath(), path2.asAndroidPath(), op)
232 }
233
234 @Suppress("DEPRECATION") // Path.isConvex
235 override val isConvex: Boolean
236 get() = internalPath.isConvex
237
238 override val isEmpty: Boolean
239 get() = internalPath.isEmpty
240
validateRectanglenull241 private fun validateRectangle(rect: Rect) {
242 if (rect.left.isNaN() || rect.top.isNaN() || rect.right.isNaN() || rect.bottom.isNaN()) {
243 throwIllegalStateException("Invalid rectangle, make sure no value is NaN")
244 }
245 }
246 }
247
throwIllegalStateExceptionnull248 internal fun throwIllegalStateException(message: String) {
249 throw IllegalStateException(message)
250 }
251
Pathnull252 private fun Path.Direction.toPlatformPathDirection() =
253 when (this) {
254 Path.Direction.CounterClockwise -> PlatformPath.Direction.CCW
255 Path.Direction.Clockwise -> PlatformPath.Direction.CW
256 }
257