| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.ui.graphics |
| |
| import androidx.ui.core.toAndroidRect |
| import androidx.ui.geometry.Offset |
| import androidx.ui.geometry.RRect |
| import androidx.ui.geometry.Rect |
| import androidx.ui.graphics.vectormath.degrees |
| import java.lang.UnsupportedOperationException |
| |
| fun Path(): Path = AndroidPath() |
| |
| /** |
| * Convert the [android.graphics.Path] instance into a compose compatible Path |
| */ |
| fun android.graphics.Path.asComposePath(): Path = AndroidPath(this) |
| |
| /** |
| * @Throws UnsupportedOperationException if this Path is not backed by an android.graphics.Path |
| */ |
| @Suppress("NOTHING_TO_INLINE") |
| inline fun Path.asAndroidPath(): android.graphics.Path = |
| if (this is AndroidPath) { |
| internalPath |
| } else { |
| throw UnsupportedOperationException("Unable to obtain android.graphics.Path") |
| } |
| |
| /* actual */ class AndroidPath( |
| val internalPath: android.graphics.Path = android.graphics.Path() |
| ) : Path { |
| |
| // Temporary value holders to reuse an object (not part of a state): |
| private val rectF = android.graphics.RectF() |
| private val radii = FloatArray(8) |
| private val mMatrix = android.graphics.Matrix() |
| |
| override var fillType: PathFillType |
| get() { |
| if (internalPath.fillType == android.graphics.Path.FillType.EVEN_ODD) { |
| return PathFillType.evenOdd |
| } else { |
| return PathFillType.nonZero |
| } |
| } |
| |
| set(value) { |
| internalPath.fillType = |
| if (value == PathFillType.evenOdd) { |
| android.graphics.Path.FillType.EVEN_ODD |
| } else { |
| android.graphics.Path.FillType.WINDING |
| } |
| } |
| |
| override fun moveTo(dx: Float, dy: Float) { |
| internalPath.moveTo(dx, dy) |
| } |
| |
| override fun relativeMoveTo(dx: Float, dy: Float) { |
| internalPath.rMoveTo(dx, dy) |
| } |
| |
| override fun lineTo(dx: Float, dy: Float) { |
| internalPath.lineTo(dx, dy) |
| } |
| |
| override fun relativeLineTo(dx: Float, dy: Float) { |
| internalPath.rLineTo(dx, dy) |
| } |
| |
| override fun quadraticBezierTo(x1: Float, y1: Float, x2: Float, y2: Float) { |
| internalPath.quadTo(x1, y1, x2, y2) |
| } |
| |
| override fun relativeQuadraticBezierTo(x1: Float, y1: Float, x2: Float, y2: Float) { |
| internalPath.rQuadTo(x1, y1, x2, y2) |
| } |
| |
| override fun cubicTo(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) { |
| internalPath.cubicTo( |
| x1, y1, |
| x2, y2, |
| x3, y3 |
| ) |
| } |
| |
| override fun relativeCubicTo(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) { |
| internalPath.rCubicTo( |
| x1, y1, |
| x2, y2, |
| x3, y3 |
| ) |
| } |
| |
| override fun arcTo( |
| rect: Rect, |
| startAngleDegrees: Float, |
| sweepAngleDegrees: Float, |
| forceMoveTo: Boolean |
| ) { |
| val left = rect.left |
| val top = rect.top |
| val right = rect.right |
| val bottom = rect.bottom |
| rectF.set(left, top, right, bottom) |
| internalPath.arcTo( |
| rectF, |
| startAngleDegrees, |
| sweepAngleDegrees, |
| forceMoveTo |
| ) |
| } |
| |
| override fun addRect(rect: Rect) { |
| check(_rectIsValid(rect)) |
| rectF.set(rect.toAndroidRect()) |
| // TODO(njawad) figure out what to do with Path Direction, |
| // Flutter does not use it, Platform does |
| internalPath.addRect(rectF, android.graphics.Path.Direction.CCW) |
| } |
| |
| override fun addOval(oval: Rect) { |
| rectF.set(oval.toAndroidRect()) |
| internalPath.addOval(rectF, android.graphics.Path.Direction.CCW) |
| } |
| |
| override fun addArcRad(oval: Rect, startAngleRadians: Float, sweepAngleRadians: Float) { |
| addArc(oval, degrees(startAngleRadians), degrees(sweepAngleRadians)) |
| } |
| |
| override fun addArc(oval: Rect, startAngleDegrees: Float, sweepAngleDegrees: Float) { |
| check(_rectIsValid(oval)) |
| rectF.set(oval.toAndroidRect()) |
| internalPath.addArc(rectF, startAngleDegrees, sweepAngleDegrees) |
| } |
| |
| override fun addRRect(rrect: RRect) { |
| rectF.set(rrect.left, rrect.top, rrect.right, rrect.bottom) |
| radii[0] = rrect.topLeftRadiusX |
| radii[1] = rrect.topLeftRadiusY |
| |
| radii[2] = rrect.topRightRadiusX |
| radii[3] = rrect.topRightRadiusY |
| |
| radii[4] = rrect.bottomRightRadiusX |
| radii[5] = rrect.bottomRightRadiusY |
| |
| radii[6] = rrect.bottomLeftRadiusX |
| radii[7] = rrect.bottomLeftRadiusY |
| internalPath.addRoundRect(rectF, radii, android.graphics.Path.Direction.CCW) |
| } |
| |
| override fun addPath(path: Path, offset: Offset) { |
| internalPath.addPath(path.asAndroidPath(), offset.dx, offset.dy) |
| } |
| |
| override fun close() { |
| internalPath.close() |
| } |
| |
| override fun reset() { |
| internalPath.reset() |
| } |
| |
| override fun shift(offset: Offset) { |
| mMatrix.reset() |
| mMatrix.setTranslate(offset.dx, offset.dy) |
| internalPath.transform(mMatrix) |
| } |
| |
| override fun getBounds(): Rect { |
| internalPath.computeBounds(rectF, true) |
| return Rect( |
| rectF.left, |
| rectF.top, |
| rectF.right, |
| rectF.bottom |
| ) |
| } |
| |
| override fun op( |
| path1: Path, |
| path2: Path, |
| operation: PathOperation |
| ): Boolean { |
| // TODO njawad: determine requirements for potential API level |
| // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
| val op = when (operation) { |
| PathOperation.difference -> android.graphics.Path.Op.DIFFERENCE |
| PathOperation.intersect -> android.graphics.Path.Op.INTERSECT |
| PathOperation.reverseDifference -> android.graphics.Path.Op.REVERSE_DIFFERENCE |
| PathOperation.union -> android.graphics.Path.Op.UNION |
| else -> android.graphics.Path.Op.XOR |
| } |
| return internalPath.op(path1.asAndroidPath(), path2.asAndroidPath(), op) |
| } |
| |
| override val isConvex: Boolean get() = internalPath.isConvex |
| |
| override val isEmpty: Boolean get() = internalPath.isEmpty |
| |
| private fun _rectIsValid(rect: Rect): Boolean { |
| check(!rect.left.isNaN()) { |
| "Rect.left is NaN" |
| } |
| check(!rect.top.isNaN()) { |
| "Rect.top is NaN" |
| } |
| check(!rect.right.isNaN()) { |
| "Rect.right is NaN" |
| } |
| check(!rect.bottom.isNaN()) { |
| "Rect.bottom is NaN" |
| } |
| return true |
| } |
| } |