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.node
18 
19 import androidx.compose.ui.Modifier
20 import androidx.compose.ui.geometry.Size
21 import androidx.compose.ui.graphics.Canvas
22 import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
23 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
24 import androidx.compose.ui.graphics.drawscope.DrawScope
25 import androidx.compose.ui.graphics.drawscope.draw
26 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
27 import androidx.compose.ui.graphics.layer.GraphicsLayer
28 import androidx.compose.ui.internal.checkPreconditionNotNull
29 import androidx.compose.ui.unit.IntSize
30 import androidx.compose.ui.unit.toSize
31 
32 /**
33  * [ContentDrawScope] implementation that extracts density and layout direction information from the
34  * given NodeCoordinator
35  */
36 internal class LayoutNodeDrawScope(val canvasDrawScope: CanvasDrawScope = CanvasDrawScope()) :
37     DrawScope by canvasDrawScope, ContentDrawScope {
38 
39     // NOTE, currently a single ComponentDrawScope is shared across composables
40     // which done to allocate a single set of Paint objects and re-use them across
41     // draw calls for all composables.
42     // As a result there could be thread safety concerns here for multi-threaded drawing
43     // scenarios, generally a single ComponentDrawScope should be shared for a particular thread
44     private var drawNode: DrawModifierNode? = null
45 
46     override fun drawContent() {
47         drawIntoCanvas { canvas ->
48             val drawNode =
49                 checkPreconditionNotNull(drawNode) {
50                     "Attempting to drawContent for a `null` node. This usually means that a call" +
51                         " to ContentDrawScope#drawContent() has been captured inside a lambda," +
52                         " and is being invoked outside of the draw pass. Capturing the scope" +
53                         " this way is unsupported - if you are trying to record drawContent" +
54                         " with graphicsLayer.record(), make sure you are using the" +
55                         " GraphicsLayer#record function within DrawScope, instead of the" +
56                         " member function on GraphicsLayer."
57                 }
58             val nextDrawNode = drawNode.nextDrawNode()
59             // NOTE(lmr): we only run performDraw directly on the node if the node's coordinator
60             // is our own. This seems to work, but we should think about a cleaner way to dispatch
61             // the draw pass as with the new modifier.node / coordinator structure this feels
62             // somewhat error prone.
63             if (nextDrawNode != null) {
64                 nextDrawNode.dispatchForKind(Nodes.Draw) {
65                     it.performDraw(canvas, drawContext.graphicsLayer)
66                 }
67             } else {
68                 // TODO(lmr): this is needed in the case that the drawnode is also a measure node,
69                 //  but we should think about the right ways to handle this as this is very error
70                 //  prone i think
71                 val coordinator = drawNode.requireCoordinator(Nodes.Draw)
72                 val nextCoordinator =
73                     if (coordinator.tail === drawNode.node) coordinator.wrapped!! else coordinator
74                 nextCoordinator.performDraw(canvas, drawContext.graphicsLayer)
75             }
76         }
77     }
78 
79     override fun GraphicsLayer.record(size: IntSize, block: DrawScope.() -> Unit) {
80         // When we record drawContent, we need to make sure to restore the drawModifierNode that is
81         // being drawn when we draw the recorded layer later, since the block passed to record
82         // sometimes needs to be invoked outside of this current draw pass
83         val currentDrawNode = drawNode
84         record(this@LayoutNodeDrawScope, this@LayoutNodeDrawScope.layoutDirection, size) {
85             val previousDrawNode = this@LayoutNodeDrawScope.drawNode
86             this@LayoutNodeDrawScope.drawNode = currentDrawNode
87             try {
88                 this@LayoutNodeDrawScope.draw(
89                     // we can use this@record.drawContext directly as the values in this@DrawScope
90                     // and this@record are the same
91                     drawContext.density,
92                     drawContext.layoutDirection,
93                     drawContext.canvas,
94                     drawContext.size,
95                     drawContext.graphicsLayer,
96                     block
97                 )
98             } finally {
99                 this@LayoutNodeDrawScope.drawNode = previousDrawNode
100             }
101         }
102     }
103 
104     // This is not thread safe
105     fun DrawModifierNode.performDraw(canvas: Canvas, layer: GraphicsLayer?) {
106         val coordinator = requireCoordinator(Nodes.Draw)
107         val size = coordinator.size.toSize()
108         val drawScope = coordinator.layoutNode.mDrawScope
109         drawScope.drawDirect(canvas, size, coordinator, this, layer)
110     }
111 
112     internal fun draw(
113         canvas: Canvas,
114         size: Size,
115         coordinator: NodeCoordinator,
116         drawNode: Modifier.Node,
117         layer: GraphicsLayer?
118     ) {
119         drawNode.dispatchForKind(Nodes.Draw) { drawDirect(canvas, size, coordinator, it, layer) }
120     }
121 
122     internal fun drawDirect(
123         canvas: Canvas,
124         size: Size,
125         coordinator: NodeCoordinator,
126         drawNode: DrawModifierNode,
127         layer: GraphicsLayer?
128     ) {
129         val previousDrawNode = this.drawNode
130         this.drawNode = drawNode
131         canvasDrawScope.draw(coordinator, coordinator.layoutDirection, canvas, size, layer) {
132             with(drawNode) { this@LayoutNodeDrawScope.draw() }
133         }
134         this.drawNode = previousDrawNode
135     }
136 }
137 
nextDrawNodenull138 private fun DelegatableNode.nextDrawNode(): Modifier.Node? {
139     val drawMask = Nodes.Draw.mask
140     val measureMask = Nodes.Layout.mask
141     val child = node.child ?: return null
142     if (child.aggregateChildKindSet and drawMask == 0) return null
143     var next: Modifier.Node? = child
144     while (next != null) {
145         if (next.kindSet and measureMask != 0) return null
146         if (next.kindSet and drawMask != 0) {
147             return next
148         }
149         next = next.child
150     }
151     return null
152 }
153