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