• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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 com.android.compose.ui.graphics
18 
19 import androidx.compose.runtime.getValue
20 import androidx.compose.runtime.mutableFloatStateOf
21 import androidx.compose.runtime.mutableStateListOf
22 import androidx.compose.runtime.mutableStateOf
23 import androidx.compose.runtime.setValue
24 import androidx.compose.ui.Modifier
25 import androidx.compose.ui.geometry.Offset
26 import androidx.compose.ui.graphics.Path
27 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
28 import androidx.compose.ui.graphics.drawscope.DrawScope
29 import androidx.compose.ui.graphics.drawscope.clipPath
30 import androidx.compose.ui.graphics.drawscope.translate
31 import androidx.compose.ui.graphics.layer.GraphicsLayer
32 import androidx.compose.ui.graphics.layer.drawLayer
33 import androidx.compose.ui.layout.LayoutCoordinates
34 import androidx.compose.ui.layout.positionInWindow
35 import androidx.compose.ui.modifier.ModifierLocalModifierNode
36 import androidx.compose.ui.node.DrawModifierNode
37 import androidx.compose.ui.node.LayoutAwareModifierNode
38 import androidx.compose.ui.node.ModifierNodeElement
39 import androidx.compose.ui.node.requireDensity
40 import androidx.compose.ui.node.requireGraphicsContext
41 import androidx.compose.ui.unit.Density
42 import androidx.compose.ui.unit.LayoutDirection
43 import androidx.compose.ui.util.fastForEach
44 
45 /**
46  * Define this as a container into which other composables can be drawn using [drawInContainer].
47  *
48  * The elements redirected to this container will be drawn above the content of this composable.
49  */
50 fun Modifier.container(state: ContainerState): Modifier {
51     return this then ContainerElement(state)
52 }
53 
54 /**
55  * Draw this composable into the container associated to [state].
56  *
57  * @param state the state of the container into which we should draw this composable.
58  * @param enabled whether the redirection of the drawing to the container is enabled.
59  * @param zIndex the z-index of the composable in the container.
60  * @param clipPath the clip path applied when drawing this composable into the container.
61  */
drawInContainernull62 fun Modifier.drawInContainer(
63     state: ContainerState,
64     enabled: () -> Boolean = { true },
65     zIndex: Float = 0f,
_null66     clipPath: (LayoutDirection, Density) -> Path? = { _, _ -> null },
67 ): Modifier {
68     return this.then(
69         DrawInContainerElement(
70             state = state,
71             enabled = enabled,
72             zIndex = zIndex,
73             clipPath = clipPath,
74         )
75     )
76 }
77 
78 class ContainerState {
79     private var renderers = mutableStateListOf<LayerRenderer>()
80     internal var lastOffsetInWindow by mutableStateOf(Offset.Zero)
81 
onLayerRendererAttachednull82     internal fun onLayerRendererAttached(renderer: LayerRenderer) {
83         renderers.add(renderer)
84         renderers.sortBy { it.zIndex }
85     }
86 
onLayerRendererDetachednull87     internal fun onLayerRendererDetached(renderer: LayerRenderer) {
88         renderers.remove(renderer)
89     }
90 
drawInOverlaynull91     internal fun drawInOverlay(drawScope: DrawScope) {
92         renderers.fastForEach { it.drawInOverlay(drawScope) }
93     }
94 }
95 
96 internal interface LayerRenderer {
97     val zIndex: Float
98 
drawInOverlaynull99     fun drawInOverlay(drawScope: DrawScope)
100 }
101 
102 private data class ContainerElement(private val state: ContainerState) :
103     ModifierNodeElement<ContainerNode>() {
104     override fun create(): ContainerNode {
105         return ContainerNode(state)
106     }
107 
108     override fun update(node: ContainerNode) {
109         node.state = state
110     }
111 }
112 
113 /** A node implementing [container] that can be delegated to. */
114 class ContainerNode(var state: ContainerState) :
115     Modifier.Node(), LayoutAwareModifierNode, DrawModifierNode {
onPlacednull116     override fun onPlaced(coordinates: LayoutCoordinates) {
117         state.lastOffsetInWindow = coordinates.positionInWindow()
118     }
119 
drawnull120     override fun ContentDrawScope.draw() {
121         drawContent()
122         state.drawInOverlay(this)
123     }
124 }
125 
126 private data class DrawInContainerElement(
127     var state: ContainerState,
128     var enabled: () -> Boolean,
129     val zIndex: Float,
130     val clipPath: (LayoutDirection, Density) -> Path?,
131 ) : ModifierNodeElement<DrawInContainerNode>() {
createnull132     override fun create(): DrawInContainerNode {
133         return DrawInContainerNode(state, enabled, zIndex, clipPath)
134     }
135 
updatenull136     override fun update(node: DrawInContainerNode) {
137         node.state = state
138         node.enabled = enabled
139         node.zIndex = zIndex
140         node.clipPath = clipPath
141     }
142 }
143 
144 /**
145  * The implementation of [drawInContainer].
146  *
147  * Note: this was forked from AndroidX RenderInTransitionOverlayNodeElement.kt
148  * (http://shortn/_3dfSFPbm8f).
149  */
150 internal class DrawInContainerNode(
151     var state: ContainerState,
<lambda>null152     var enabled: () -> Boolean = { true },
153     zIndex: Float = 0f,
_null154     var clipPath: (LayoutDirection, Density) -> Path? = { _, _ -> null },
155 ) : Modifier.Node(), LayoutAwareModifierNode, DrawModifierNode, ModifierLocalModifierNode {
156     private var lastOffsetInWindow by mutableStateOf(Offset.Zero)
157     var zIndex by mutableFloatStateOf(zIndex)
158 
159     private inner class LayerWithRenderer(val layer: GraphicsLayer) : LayerRenderer {
160         override val zIndex: Float
161             get() = this@DrawInContainerNode.zIndex
162 
drawInOverlaynull163         override fun drawInOverlay(drawScope: DrawScope) {
164             if (enabled()) {
165                 with(drawScope) {
166                     val (x, y) = lastOffsetInWindow - state.lastOffsetInWindow
167                     val clipPath = clipPath(layoutDirection, requireDensity())
168                     if (clipPath != null) {
169                         clipPath(clipPath) { translate(x, y) { drawLayer(layer) } }
170                     } else {
171                         translate(x, y) { drawLayer(layer) }
172                     }
173                 }
174             }
175         }
176     }
177 
178     // Render in-place logic. Depending on the result of `renderInOverlay()`, the content will
179     // either render in-place or in the overlay, but never in both places.
drawnull180     override fun ContentDrawScope.draw() {
181         val layer = requireNotNull(layer) { "Error: layer never initialized" }
182         layer.record { this@draw.drawContent() }
183         if (!enabled()) {
184             drawLayer(layer)
185         }
186     }
187 
onPlacednull188     override fun onPlaced(coordinates: LayoutCoordinates) {
189         lastOffsetInWindow = coordinates.positionInWindow()
190     }
191 
192     val layer: GraphicsLayer?
193         get() = layerWithRenderer?.layer
194 
195     private var layerWithRenderer: LayerWithRenderer? = null
196 
onAttachnull197     override fun onAttach() {
198         LayerWithRenderer(requireGraphicsContext().createGraphicsLayer()).let {
199             state.onLayerRendererAttached(it)
200             layerWithRenderer = it
201         }
202     }
203 
onDetachnull204     override fun onDetach() {
205         layerWithRenderer?.let {
206             state.onLayerRendererDetached(it)
207             requireGraphicsContext().releaseGraphicsLayer(it.layer)
208         }
209     }
210 }
211