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