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