1 /* <lambda>null2 * Copyright 2020 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.platform 18 19 import android.os.Build 20 import android.view.View 21 import androidx.annotation.RequiresApi 22 import androidx.compose.ui.geometry.MutableRect 23 import androidx.compose.ui.geometry.Offset 24 import androidx.compose.ui.graphics.Canvas 25 import androidx.compose.ui.graphics.CanvasHolder 26 import androidx.compose.ui.graphics.Fields 27 import androidx.compose.ui.graphics.Matrix 28 import androidx.compose.ui.graphics.Paint 29 import androidx.compose.ui.graphics.RectangleShape 30 import androidx.compose.ui.graphics.ReusableGraphicsLayerScope 31 import androidx.compose.ui.graphics.TransformOrigin 32 import androidx.compose.ui.graphics.layer.GraphicsLayer 33 import androidx.compose.ui.graphics.nativeCanvas 34 import androidx.compose.ui.graphics.toArgb 35 import androidx.compose.ui.layout.GraphicLayerInfo 36 import androidx.compose.ui.node.OwnedLayer 37 import androidx.compose.ui.unit.IntOffset 38 import androidx.compose.ui.unit.IntSize 39 40 /** RenderNode implementation of OwnedLayer. */ 41 @RequiresApi(Build.VERSION_CODES.M) 42 internal class RenderNodeLayer( 43 val ownerView: AndroidComposeView, 44 drawBlock: (canvas: Canvas, parentLayer: GraphicsLayer?) -> Unit, 45 invalidateParentLayer: () -> Unit 46 ) : OwnedLayer, GraphicLayerInfo { 47 private var drawBlock: ((canvas: Canvas, parentLayer: GraphicsLayer?) -> Unit)? = drawBlock 48 private var invalidateParentLayer: (() -> Unit)? = invalidateParentLayer 49 50 /** True when the RenderNodeLayer has been invalidated and not yet drawn. */ 51 private var isDirty = false 52 set(value) { 53 if (value != field) { 54 field = value 55 ownerView.notifyLayerIsDirty(this, value) 56 } 57 } 58 59 private val outlineResolver = OutlineResolver() 60 private var isDestroyed = false 61 private var drawnWithZ = false 62 63 /** 64 * Optional paint used when the RenderNode is rendered on a software backed canvas and is 65 * somewhat transparent (i.e. alpha less than 1.0f) 66 */ 67 private var softwareLayerPaint: Paint? = null 68 69 private val matrixCache = LayerMatrixCache(getMatrix) 70 71 private val canvasHolder = CanvasHolder() 72 73 /** 74 * Local copy of the transform origin as GraphicsLayerModifier can be implemented as a model 75 * object. Update this field within [updateLayerProperties] and use it in [resize] or other 76 * methods 77 */ 78 private var transformOrigin: TransformOrigin = TransformOrigin.Center 79 80 private val renderNode = 81 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 82 RenderNodeApi29(ownerView) 83 } else { 84 RenderNodeApi23(ownerView) 85 } 86 .apply { 87 setHasOverlappingRendering(true) 88 // in compose the default is to not clip. 89 clipToBounds = false 90 } 91 92 override val layerId: Long 93 get() = renderNode.uniqueId 94 95 override val ownerViewId: Long 96 get() = 97 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 98 UniqueDrawingIdApi29.getUniqueDrawingId(ownerView) 99 } else { 100 -1 101 } 102 103 @RequiresApi(29) 104 private object UniqueDrawingIdApi29 { 105 @JvmStatic fun getUniqueDrawingId(view: View) = view.uniqueDrawingId 106 } 107 108 private var mutatedFields: Int = 0 109 110 override fun updateLayerProperties(scope: ReusableGraphicsLayerScope) { 111 val maybeChangedFields = scope.mutatedFields or mutatedFields 112 if (maybeChangedFields and Fields.TransformOrigin != 0) { 113 this.transformOrigin = scope.transformOrigin 114 } 115 val wasClippingManually = renderNode.clipToOutline && !outlineResolver.outlineClipSupported 116 if (maybeChangedFields and Fields.ScaleX != 0) { 117 renderNode.scaleX = scope.scaleX 118 } 119 if (maybeChangedFields and Fields.ScaleY != 0) { 120 renderNode.scaleY = scope.scaleY 121 } 122 if (maybeChangedFields and Fields.Alpha != 0) { 123 renderNode.alpha = scope.alpha 124 } 125 if (maybeChangedFields and Fields.TranslationX != 0) { 126 renderNode.translationX = scope.translationX 127 } 128 if (maybeChangedFields and Fields.TranslationY != 0) { 129 renderNode.translationY = scope.translationY 130 } 131 if (maybeChangedFields and Fields.ShadowElevation != 0) { 132 renderNode.elevation = scope.shadowElevation 133 } 134 if (maybeChangedFields and Fields.AmbientShadowColor != 0) { 135 renderNode.ambientShadowColor = scope.ambientShadowColor.toArgb() 136 } 137 if (maybeChangedFields and Fields.SpotShadowColor != 0) { 138 renderNode.spotShadowColor = scope.spotShadowColor.toArgb() 139 } 140 if (maybeChangedFields and Fields.RotationZ != 0) { 141 renderNode.rotationZ = scope.rotationZ 142 } 143 if (maybeChangedFields and Fields.RotationX != 0) { 144 renderNode.rotationX = scope.rotationX 145 } 146 if (maybeChangedFields and Fields.RotationY != 0) { 147 renderNode.rotationY = scope.rotationY 148 } 149 if (maybeChangedFields and Fields.CameraDistance != 0) { 150 renderNode.cameraDistance = scope.cameraDistance 151 } 152 if (maybeChangedFields and Fields.TransformOrigin != 0) { 153 renderNode.pivotX = transformOrigin.pivotFractionX * renderNode.width 154 renderNode.pivotY = transformOrigin.pivotFractionY * renderNode.height 155 } 156 val clipToOutline = scope.clip && scope.shape !== RectangleShape 157 if (maybeChangedFields and (Fields.Clip or Fields.Shape) != 0) { 158 renderNode.clipToOutline = clipToOutline 159 renderNode.clipToBounds = scope.clip && scope.shape === RectangleShape 160 } 161 if (maybeChangedFields and Fields.RenderEffect != 0) { 162 renderNode.renderEffect = scope.renderEffect 163 } 164 if (maybeChangedFields and Fields.ColorFilter != 0) { 165 renderNode.colorFilter = scope.colorFilter 166 } 167 if (maybeChangedFields and Fields.BlendMode != 0) { 168 renderNode.blendMode = scope.blendMode 169 } 170 if (maybeChangedFields and Fields.CompositingStrategy != 0) { 171 renderNode.compositingStrategy = scope.compositingStrategy 172 } 173 val shapeChanged = 174 outlineResolver.update( 175 scope.outline, 176 scope.alpha, 177 clipToOutline, 178 scope.shadowElevation, 179 scope.size, 180 ) 181 if (outlineResolver.cacheIsDirty) { 182 renderNode.setOutline(outlineResolver.androidOutline) 183 } 184 val isClippingManually = clipToOutline && !outlineResolver.outlineClipSupported 185 if (wasClippingManually != isClippingManually || (isClippingManually && shapeChanged)) { 186 invalidate() 187 } else { 188 triggerRepaint() 189 } 190 if (!drawnWithZ && renderNode.elevation > 0f) { 191 invalidateParentLayer?.invoke() 192 } 193 194 if (maybeChangedFields and Fields.MatrixAffectingFields != 0) { 195 matrixCache.invalidate() 196 } 197 198 mutatedFields = scope.mutatedFields 199 } 200 201 override fun isInLayer(position: Offset): Boolean { 202 val x = position.x 203 val y = position.y 204 if (renderNode.clipToBounds) { 205 return 0f <= x && x < renderNode.width && 0f <= y && y < renderNode.height 206 } 207 208 if (renderNode.clipToOutline) { 209 return outlineResolver.isInOutline(position) 210 } 211 212 return true 213 } 214 215 override fun resize(size: IntSize) { 216 val width = size.width 217 val height = size.height 218 renderNode.pivotX = transformOrigin.pivotFractionX * width 219 renderNode.pivotY = transformOrigin.pivotFractionY * height 220 if ( 221 renderNode.setPosition( 222 renderNode.left, 223 renderNode.top, 224 renderNode.left + width, 225 renderNode.top + height 226 ) 227 ) { 228 renderNode.setOutline(outlineResolver.androidOutline) 229 invalidate() 230 matrixCache.invalidate() 231 } 232 } 233 234 override fun move(position: IntOffset) { 235 val oldLeft = renderNode.left 236 val oldTop = renderNode.top 237 val newLeft = position.x 238 val newTop = position.y 239 if (oldLeft != newLeft || oldTop != newTop) { 240 if (oldLeft != newLeft) { 241 renderNode.offsetLeftAndRight(newLeft - oldLeft) 242 } 243 if (oldTop != newTop) { 244 renderNode.offsetTopAndBottom(newTop - oldTop) 245 } 246 triggerRepaint() 247 matrixCache.invalidate() 248 } 249 } 250 251 override fun invalidate() { 252 if (!isDirty && !isDestroyed) { 253 ownerView.invalidate() 254 isDirty = true 255 } 256 } 257 258 /** 259 * This only triggers the system so that it knows that some kind of painting must happen without 260 * actually causing the layer to be invalidated and have to re-record its drawing. 261 */ 262 private fun triggerRepaint() { 263 // onDescendantInvalidated is only supported on O+ 264 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 265 WrapperRenderNodeLayerHelperMethods.onDescendantInvalidated(ownerView) 266 } else { 267 ownerView.invalidate() 268 } 269 } 270 271 override fun drawLayer(canvas: Canvas, parentLayer: GraphicsLayer?) { 272 val androidCanvas = canvas.nativeCanvas 273 if (androidCanvas.isHardwareAccelerated) { 274 updateDisplayList() 275 drawnWithZ = renderNode.elevation > 0f 276 if (drawnWithZ) { 277 canvas.enableZ() 278 } 279 renderNode.drawInto(androidCanvas) 280 if (drawnWithZ) { 281 canvas.disableZ() 282 } 283 } else { 284 val left = renderNode.left.toFloat() 285 val top = renderNode.top.toFloat() 286 val right = renderNode.right.toFloat() 287 val bottom = renderNode.bottom.toFloat() 288 // If there is alpha applied, we must render into an offscreen buffer to 289 // properly blend the contents of this layer against the background content 290 if (renderNode.alpha < 1.0f) { 291 val paint = 292 (softwareLayerPaint ?: Paint().also { softwareLayerPaint = it }).apply { 293 alpha = renderNode.alpha 294 } 295 androidCanvas.saveLayer(left, top, right, bottom, paint.asFrameworkPaint()) 296 } else { 297 canvas.save() 298 } 299 // If we are software rendered we must translate the canvas based on the offset provided 300 // in the move call which operates directly on the RenderNode 301 canvas.translate(left, top) 302 canvas.concat(matrixCache.calculateMatrix(renderNode)) 303 clipRenderNode(canvas) 304 drawBlock?.invoke(canvas, null) 305 canvas.restore() 306 isDirty = false 307 } 308 } 309 310 /** 311 * Manually clips the content of the RenderNodeLayer in the provided canvas. This is used only 312 * in software rendered use cases 313 */ 314 private fun clipRenderNode(canvas: Canvas) { 315 if (renderNode.clipToOutline || renderNode.clipToBounds) { 316 outlineResolver.clipToOutline(canvas) 317 } 318 } 319 320 override fun updateDisplayList() { 321 if (isDirty || !renderNode.hasDisplayList) { 322 val clipPath = 323 if (renderNode.clipToOutline && !outlineResolver.outlineClipSupported) { 324 outlineResolver.clipPath 325 } else { 326 null 327 } 328 drawBlock?.let { drawBlock -> 329 renderNode.record(canvasHolder, clipPath) { drawBlock(it, null) } 330 } 331 isDirty = false 332 } 333 } 334 335 override fun destroy() { 336 if (renderNode.hasDisplayList) { 337 renderNode.discardDisplayList() 338 } 339 drawBlock = null 340 invalidateParentLayer = null 341 isDestroyed = true 342 isDirty = false 343 ownerView.requestClearInvalidObservations() 344 ownerView.recycle(this) 345 } 346 347 override val underlyingMatrix: Matrix 348 get() = matrixCache.calculateMatrix(renderNode) 349 350 override var frameRate: Float = 0f 351 352 override var isFrameRateFromParent = false 353 354 override fun mapOffset(point: Offset, inverse: Boolean): Offset { 355 return if (inverse) { 356 matrixCache.mapInverse(renderNode, point) 357 } else { 358 matrixCache.map(renderNode, point) 359 } 360 } 361 362 override fun mapBounds(rect: MutableRect, inverse: Boolean) { 363 if (inverse) { 364 matrixCache.mapInverse(renderNode, rect) 365 } else { 366 matrixCache.map(renderNode, rect) 367 } 368 } 369 370 override fun reuseLayer( 371 drawBlock: (canvas: Canvas, parentLayer: GraphicsLayer?) -> Unit, 372 invalidateParentLayer: () -> Unit 373 ) { 374 matrixCache.reset() 375 isDirty = false 376 isDestroyed = false 377 drawnWithZ = false 378 transformOrigin = TransformOrigin.Center 379 this.drawBlock = drawBlock 380 this.invalidateParentLayer = invalidateParentLayer 381 } 382 383 override fun transform(matrix: Matrix) { 384 matrix.timesAssign(matrixCache.calculateMatrix(renderNode)) 385 } 386 387 override fun inverseTransform(matrix: Matrix) { 388 val inverse = matrixCache.calculateInverseMatrix(renderNode) 389 if (inverse != null) { 390 matrix.timesAssign(inverse) 391 } 392 } 393 394 companion object { 395 private val getMatrix: (DeviceRenderNode, android.graphics.Matrix) -> Unit = { rn, matrix -> 396 rn.getMatrix(matrix) 397 } 398 } 399 } 400 401 /** 402 * This class is here to ensure that the classes that use this API will get verified and can be AOT 403 * compiled. It is expected that this class will soft-fail verification, but the classes which use 404 * this method will pass. 405 */ 406 @RequiresApi(Build.VERSION_CODES.O) 407 internal object WrapperRenderNodeLayerHelperMethods { onDescendantInvalidatednull408 fun onDescendantInvalidated(ownerView: AndroidComposeView) { 409 ownerView.parent?.onDescendantInvalidated(ownerView, ownerView) 410 } 411 } 412