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