1 /* 2 * 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.graphics.Outline 20 import android.graphics.RenderNode 21 import android.os.Build 22 import androidx.annotation.RequiresApi 23 import androidx.compose.ui.graphics.BlendMode 24 import androidx.compose.ui.graphics.Canvas 25 import androidx.compose.ui.graphics.CanvasHolder 26 import androidx.compose.ui.graphics.ColorFilter 27 import androidx.compose.ui.graphics.CompositingStrategy 28 import androidx.compose.ui.graphics.Paint 29 import androidx.compose.ui.graphics.Path 30 import androidx.compose.ui.graphics.RenderEffect 31 32 /** RenderNode on Q+ devices, where it is officially supported. */ 33 @RequiresApi(Build.VERSION_CODES.Q) 34 internal class RenderNodeApi29(val ownerView: AndroidComposeView) : DeviceRenderNode { 35 private val renderNode = RenderNode("Compose") 36 37 private var internalRenderEffect: RenderEffect? = null 38 39 private var internalCompositingStrategy: CompositingStrategy = CompositingStrategy.Auto 40 41 private var layerPaint: Paint? = null 42 isUsingCompositingLayernull43 internal fun isUsingCompositingLayer(): Boolean = renderNode.useCompositingLayer 44 45 internal fun hasOverlappingRendering(): Boolean = renderNode.hasOverlappingRendering() 46 47 override val uniqueId: Long 48 get() = renderNode.uniqueId 49 50 override val left: Int 51 get() = renderNode.left 52 53 override val top: Int 54 get() = renderNode.top 55 56 override val right: Int 57 get() = renderNode.right 58 59 override val bottom: Int 60 get() = renderNode.bottom 61 62 override val width: Int 63 get() = renderNode.width 64 65 override val height: Int 66 get() = renderNode.height 67 68 override var scaleX: Float 69 get() = renderNode.scaleX 70 set(value) { 71 renderNode.scaleX = value 72 } 73 74 override var scaleY: Float 75 get() = renderNode.scaleY 76 set(value) { 77 renderNode.scaleY = value 78 } 79 80 override var translationX: Float 81 get() = renderNode.translationX 82 set(value) { 83 renderNode.translationX = value 84 } 85 86 override var translationY: Float 87 get() = renderNode.translationY 88 set(value) { 89 renderNode.translationY = value 90 } 91 92 override var elevation: Float 93 get() = renderNode.elevation 94 set(value) { 95 renderNode.elevation = value 96 } 97 98 override var ambientShadowColor: Int 99 get() = renderNode.ambientShadowColor 100 set(value) { 101 renderNode.ambientShadowColor = value 102 } 103 104 override var spotShadowColor: Int 105 get() = renderNode.spotShadowColor 106 set(value) { 107 renderNode.spotShadowColor = value 108 } 109 110 override var rotationZ: Float 111 get() = renderNode.rotationZ 112 set(value) { 113 renderNode.rotationZ = value 114 } 115 116 override var rotationX: Float 117 get() = renderNode.rotationX 118 set(value) { 119 renderNode.rotationX = value 120 } 121 122 override var rotationY: Float 123 get() = renderNode.rotationY 124 set(value) { 125 renderNode.rotationY = value 126 } 127 128 override var cameraDistance: Float 129 get() = renderNode.cameraDistance 130 set(value) { 131 renderNode.cameraDistance = value 132 } 133 134 override var pivotX: Float 135 get() = renderNode.pivotX 136 set(value) { 137 renderNode.pivotX = value 138 } 139 140 override var pivotY: Float 141 get() = renderNode.pivotY 142 set(value) { 143 renderNode.pivotY = value 144 } 145 146 override var clipToOutline: Boolean 147 get() = renderNode.clipToOutline 148 set(value) { 149 renderNode.clipToOutline = value 150 } 151 152 override var clipToBounds: Boolean 153 get() = renderNode.clipToBounds 154 set(value) { 155 renderNode.clipToBounds = value 156 } 157 158 override var alpha: Float 159 get() = renderNode.alpha 160 set(value) { 161 renderNode.alpha = value 162 } 163 164 override var blendMode: BlendMode = BlendMode.SrcOver 165 set(value) { 166 field = value <lambda>null167 obtainLayerPaint().apply { blendMode = value } 168 updateLayerProperties() 169 } 170 171 override var colorFilter: ColorFilter? = null 172 set(value) { 173 field = value <lambda>null174 obtainLayerPaint().apply { colorFilter = value } 175 updateLayerProperties() 176 } 177 178 override var renderEffect: RenderEffect? 179 get() = internalRenderEffect 180 set(value) { 181 internalRenderEffect = value 182 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 183 RenderNodeApi29VerificationHelper.setRenderEffect(renderNode, value) 184 } 185 } 186 187 override var compositingStrategy: CompositingStrategy 188 get() = internalCompositingStrategy 189 set(value) { 190 internalCompositingStrategy = value 191 updateLayerProperties() 192 } 193 updateLayerPropertiesnull194 private fun updateLayerProperties() { 195 if (requiresCompositingLayer()) { 196 renderNode.applyCompositingStrategy(CompositingStrategy.Offscreen) 197 } else { 198 renderNode.applyCompositingStrategy(internalCompositingStrategy) 199 } 200 } 201 requiresCompositingLayernull202 private fun requiresCompositingLayer(): Boolean = 203 compositingStrategy == CompositingStrategy.Offscreen || requiresLayerPaint() 204 205 private fun requiresLayerPaint(): Boolean = 206 blendMode != BlendMode.SrcOver || colorFilter != null 207 208 private fun obtainLayerPaint(): Paint = layerPaint ?: Paint().also { layerPaint = it } 209 RenderNodenull210 private fun RenderNode.applyCompositingStrategy(compositingStrategy: CompositingStrategy) { 211 when (compositingStrategy) { 212 CompositingStrategy.Offscreen -> { 213 setUseCompositingLayer(true, layerPaint?.asFrameworkPaint()) 214 setHasOverlappingRendering(true) 215 } 216 CompositingStrategy.ModulateAlpha -> { 217 setUseCompositingLayer(false, null) 218 setHasOverlappingRendering(false) 219 } 220 else -> { 221 setUseCompositingLayer(false, null) 222 setHasOverlappingRendering(true) 223 } 224 } 225 } 226 227 override val hasDisplayList: Boolean 228 get() = renderNode.hasDisplayList() 229 setOutlinenull230 override fun setOutline(outline: Outline?) { 231 renderNode.setOutline(outline) 232 } 233 setPositionnull234 override fun setPosition(left: Int, top: Int, right: Int, bottom: Int): Boolean { 235 return renderNode.setPosition(left, top, right, bottom) 236 } 237 offsetLeftAndRightnull238 override fun offsetLeftAndRight(offset: Int) { 239 renderNode.offsetLeftAndRight(offset) 240 } 241 offsetTopAndBottomnull242 override fun offsetTopAndBottom(offset: Int) { 243 renderNode.offsetTopAndBottom(offset) 244 } 245 recordnull246 override fun record(canvasHolder: CanvasHolder, clipPath: Path?, drawBlock: (Canvas) -> Unit) { 247 canvasHolder.drawInto(renderNode.beginRecording()) { 248 if (clipPath != null) { 249 save() 250 clipPath(clipPath) 251 } 252 drawBlock(this) 253 if (clipPath != null) { 254 restore() 255 } 256 } 257 renderNode.endRecording() 258 } 259 getMatrixnull260 override fun getMatrix(matrix: android.graphics.Matrix) { 261 return renderNode.getMatrix(matrix) 262 } 263 getInverseMatrixnull264 override fun getInverseMatrix(matrix: android.graphics.Matrix) { 265 return renderNode.getInverseMatrix(matrix) 266 } 267 drawIntonull268 override fun drawInto(canvas: android.graphics.Canvas) { 269 canvas.drawRenderNode(renderNode) 270 } 271 setHasOverlappingRenderingnull272 override fun setHasOverlappingRendering(hasOverlappingRendering: Boolean): Boolean = 273 renderNode.setHasOverlappingRendering(hasOverlappingRendering) 274 275 override fun dumpRenderNodeData(): DeviceRenderNodeData = 276 DeviceRenderNodeData( 277 uniqueId = renderNode.uniqueId, 278 left = renderNode.left, 279 top = renderNode.top, 280 right = renderNode.right, 281 bottom = renderNode.bottom, 282 width = renderNode.width, 283 height = renderNode.height, 284 scaleX = renderNode.scaleX, 285 scaleY = renderNode.scaleY, 286 translationX = renderNode.translationX, 287 translationY = renderNode.translationY, 288 elevation = renderNode.elevation, 289 ambientShadowColor = renderNode.ambientShadowColor, 290 spotShadowColor = renderNode.spotShadowColor, 291 rotationZ = renderNode.rotationZ, 292 rotationX = renderNode.rotationX, 293 rotationY = renderNode.rotationY, 294 cameraDistance = renderNode.cameraDistance, 295 pivotX = renderNode.pivotX, 296 pivotY = renderNode.pivotY, 297 clipToOutline = renderNode.clipToOutline, 298 clipToBounds = renderNode.clipToBounds, 299 alpha = renderNode.alpha, 300 renderEffect = internalRenderEffect, 301 blendMode = blendMode, 302 colorFilter = colorFilter, 303 compositingStrategy = internalCompositingStrategy 304 ) 305 306 override fun discardDisplayList() { 307 renderNode.discardDisplayList() 308 } 309 } 310 311 @RequiresApi(Build.VERSION_CODES.S) 312 private object RenderNodeApi29VerificationHelper { 313 setRenderEffectnull314 fun setRenderEffect(renderNode: RenderNode, target: RenderEffect?) { 315 renderNode.setRenderEffect(target?.asAndroidRenderEffect()) 316 } 317 } 318