1 /*
2  * Copyright 2022 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.internal.checkPrecondition
21 
22 /**
23  * A [Modifier.Node] which is able to delegate work to other [Modifier.Node] instances.
24  *
25  * This can be useful to compose multiple node implementations into one.
26  *
27  * @sample androidx.compose.ui.samples.DelegatedNodeSampleExplicit
28  * @sample androidx.compose.ui.samples.DelegatedNodeSampleImplicit
29  * @sample androidx.compose.ui.samples.LazyDelegationExample
30  * @sample androidx.compose.ui.samples.ConditionalDelegationExample
31  * @sample androidx.compose.ui.samples.DelegateInAttachSample
32  * @see DelegatingNode
33  */
34 abstract class DelegatingNode : Modifier.Node() {
35 
36     /**
37      * This is the kindSet of the node if it had no delegates. This will never change, but kindSet
38      * might, so we cache this value to be able to more efficiently recalculate the kindSet
39      */
40     @Suppress("LeakingThis") internal val selfKindSet: Int = calculateNodeKindSetFrom(this)
41 
updateCoordinatornull42     override fun updateCoordinator(coordinator: NodeCoordinator?) {
43         super.updateCoordinator(coordinator)
44         forEachImmediateDelegate { it.updateCoordinator(coordinator) }
45     }
46 
47     internal var delegate: Modifier.Node? = null
48 
49     // @TestOnly
delegateUnprotectednull50     internal fun <T : DelegatableNode> delegateUnprotected(delegatableNode: T): T =
51         delegate(delegatableNode)
52 
53     // @TestOnly
54     internal fun undelegateUnprotected(instance: DelegatableNode) = undelegate(instance)
55 
56     override fun setAsDelegateTo(owner: Modifier.Node) {
57         super.setAsDelegateTo(owner)
58         // At this point _this_ node is being delegated to, however _this_ node may also
59         // have delegates of its own, and their current `node` pointers need to be updated
60         // so that they point to the right node in the tree.
61         forEachImmediateDelegate { it.setAsDelegateTo(owner) }
62     }
63 
64     /**
65      * In order to properly delegate work to another [Modifier.Node], the delegated instance must be
66      * created and returned inside of a [delegate] call. Doing this will ensure that the created
67      * node instance follows all of the right lifecycles and is properly discoverable in this
68      * position of the node tree.
69      *
70      * By using [delegate], the [delegatableNode] parameter is returned from this function for
71      * convenience.
72      *
73      * This method can be called from within an `init` block, however the returned delegated node
74      * will not be attached until the delegating node is attached. If [delegate] is called after the
75      * delegating node is already attached, the returned delegated node will be attached.
76      */
delegatenull77     protected fun <T : DelegatableNode> delegate(delegatableNode: T): T {
78         val delegateNode = delegatableNode.node
79         val isAlreadyDelegated = delegateNode !== delegatableNode
80         if (isAlreadyDelegated) {
81             val delegator = (delegatableNode as? Modifier.Node)?.parent
82             val isDelegatedToThisNode = delegateNode === node && delegator == this
83             if (isDelegatedToThisNode) {
84                 // nothing left to do
85                 return delegatableNode
86             } else {
87                 error("Cannot delegate to an already delegated node")
88             }
89         }
90         checkPrecondition(!delegateNode.isAttached) {
91             "Cannot delegate to an already attached node"
92         }
93         // this could be a delegate itself, so we make sure to setAsDelegateTo(node) instead of
94         // setAsDelegateTo(this).
95         delegateNode.setAsDelegateTo(node)
96         val beforeKindSet = kindSet
97         // need to include the delegate's delegates in the calculation
98         val delegatedKindSet = calculateNodeKindSetFromIncludingDelegates(delegateNode)
99         delegateNode.kindSet = delegatedKindSet
100         validateDelegateKindSet(delegatedKindSet, delegateNode)
101 
102         // We store the delegates of a node as a singly-linked-list, with the "head" as `delegate`
103         // and the next pointer as `child`.
104         delegateNode.child = delegate
105         delegate = delegateNode
106 
107         // for a delegate, parent always points to the node which delegated to it
108         delegateNode.parent = this
109         updateNodeKindSet(kindSet or delegatedKindSet, recalculateOwner = false)
110 
111         if (isAttached) {
112             if (Nodes.Layout in delegatedKindSet && Nodes.Layout !in beforeKindSet) {
113                 // We delegated to a layout modifier. In this case, we need to ensure that a new
114                 // NodeCoordinator gets created for this node
115                 val chain = requireLayoutNode().nodes
116                 node.updateCoordinator(null)
117                 chain.syncCoordinators()
118             } else {
119                 updateCoordinator(coordinator)
120             }
121             delegateNode.markAsAttached()
122             delegateNode.runAttachLifecycle()
123             autoInvalidateInsertedNode(delegateNode)
124         }
125         return delegatableNode
126     }
127 
128     /**
129      * This function expects a node which was passed in to [delegate] for this node, and is
130      * currently being delegated to to be passed in as [instance]. After this function returns, the
131      * node will no longer be attached, and will not be an active delegate of this node.
132      *
133      * If [instance] is not an active delegate of this node, this function will throw an
134      * [IllegalStateException].
135      */
undelegatenull136     protected fun undelegate(instance: DelegatableNode) {
137         var prev: Modifier.Node? = null
138         var it: Modifier.Node? = delegate
139         var found = false
140         while (it != null) {
141             if (it === instance) {
142                 // remove from delegate chain
143                 if (it.isAttached) {
144                     autoInvalidateRemovedNode(it)
145                     it.runDetachLifecycle()
146                     it.markAsDetached()
147                 }
148                 it.setAsDelegateTo(it) // sets "node" back to itself
149                 it.aggregateChildKindSet = 0
150                 if (prev == null) {
151                     this.delegate = it.child
152                 } else {
153                     prev.child = it.child
154                 }
155                 it.child = null
156                 it.parent = null
157                 found = true
158                 break
159             }
160             prev = it
161             it = it.child
162         }
163         if (found) {
164             val beforeKindSet = kindSet
165             val afterKindSet = calculateNodeKindSetFromIncludingDelegates(this)
166             updateNodeKindSet(afterKindSet, recalculateOwner = true)
167 
168             if (isAttached && Nodes.Layout in beforeKindSet && Nodes.Layout !in afterKindSet) {
169                 // the delegate getting removed was a layout delegate. As a result, we need
170                 // to sync coordinators
171                 val chain = requireLayoutNode().nodes
172                 node.updateCoordinator(null)
173                 chain.syncCoordinators()
174             }
175         } else {
176             error("Could not find delegate: $instance")
177         }
178     }
179 
validateDelegateKindSetnull180     private fun validateDelegateKindSet(delegateKindSet: Int, delegateNode: Modifier.Node) {
181         val current = kindSet
182         if (Nodes.Layout in delegateKindSet && Nodes.Layout in current) {
183             // at this point, we know that the node was _already_ a layout modifier, and we are
184             // delegating to another layout modifier. In order to properly handle this, we need
185             // to require that the delegating node is itself a LayoutModifierNode to ensure that
186             // they are explicitly handling the combination. If not, we throw, since
187             checkPrecondition(this is LayoutModifierNode) {
188                 "Delegating to multiple LayoutModifierNodes without the delegating node " +
189                     "implementing LayoutModifierNode itself is not allowed." +
190                     "\nDelegating Node: $this" +
191                     "\nDelegate Node: $delegateNode"
192             }
193         }
194     }
195 
updateNodeKindSetnull196     private fun updateNodeKindSet(newKindSet: Int, recalculateOwner: Boolean) {
197         val before = kindSet
198         kindSet = newKindSet
199         if (before != newKindSet) {
200             var agg = newKindSet
201             if (isDelegationRoot) {
202                 aggregateChildKindSet = agg
203             }
204             // if we changed, then we must update our aggregateChildKindSet of ourselves and
205             // everything up the spine
206 
207             if (isAttached) {
208                 val owner = node
209                 var it: Modifier.Node? = this
210                 // first we traverse up the delegate tree until we hit the "owner" node, which is
211                 // the node which is actually part of the tree, ie the "root delegating node".
212                 // As we iterate here, we update the aggregateChildKindSet as well as the kindSet,
213                 // since a delegating node takes on the kinds of the nodes it delegates to.
214                 while (it != null) {
215                     agg = it.kindSet or agg
216                     it.kindSet = agg
217                     if (it === owner) break
218                     it = it.parent
219                 }
220 
221                 if (recalculateOwner && it === owner) {
222                     agg = calculateNodeKindSetFromIncludingDelegates(owner)
223                     owner.kindSet = agg
224                 }
225 
226                 agg = agg or (it?.child?.aggregateChildKindSet ?: 0)
227 
228                 // Now we are traversing the spine of nodes in the actual tree, so we update the
229                 // aggregateChildKindSet here, but not the kindSets.
230                 while (it != null) {
231                     agg = it.kindSet or agg
232                     it.aggregateChildKindSet = agg
233                     it = it.parent
234                 }
235             }
236         }
237     }
238 
forEachImmediateDelegatenull239     internal inline fun forEachImmediateDelegate(block: (Modifier.Node) -> Unit) {
240         var node: Modifier.Node? = delegate
241         while (node != null) {
242             block(node)
243             node = node.child
244         }
245     }
246 
markAsAttachednull247     override fun markAsAttached() {
248         super.markAsAttached()
249         forEachImmediateDelegate {
250             it.updateCoordinator(coordinator)
251             // NOTE: it might already be attached if the delegate was delegated to inside of
252             // onAttach()
253             if (!it.isAttached) {
254                 it.markAsAttached()
255             }
256         }
257     }
258 
runAttachLifecyclenull259     override fun runAttachLifecycle() {
260         forEachImmediateDelegate { it.runAttachLifecycle() }
261         super.runAttachLifecycle()
262     }
263 
runDetachLifecyclenull264     override fun runDetachLifecycle() {
265         super.runDetachLifecycle()
266         forEachImmediateDelegate { it.runDetachLifecycle() }
267     }
268 
markAsDetachednull269     override fun markAsDetached() {
270         forEachImmediateDelegate { it.markAsDetached() }
271         super.markAsDetached()
272     }
273 
resetnull274     override fun reset() {
275         super.reset()
276         forEachImmediateDelegate { it.reset() }
277     }
278 }
279