1 /* <lambda>null2 * 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.modifier 18 19 import androidx.compose.runtime.collection.mutableVectorOf 20 import androidx.compose.ui.Modifier 21 import androidx.compose.ui.node.BackwardsCompatNode 22 import androidx.compose.ui.node.LayoutNode 23 import androidx.compose.ui.node.Nodes 24 import androidx.compose.ui.node.Owner 25 import androidx.compose.ui.node.requireLayoutNode 26 import androidx.compose.ui.node.visitSubtreeIf 27 28 /** 29 * This is a (maybe temporary?) thing to provide proper backwards compatibility with ModifierLocals. 30 * This class makes it possible to properly "update" ModifierLocalConsumers whenever 31 * ModiferLocalProviders get added/removed in the tree somewhere dynamically. This can be quite 32 * costly, so we attempt to do this in a way where we don't waste time on nodes that are being 33 * inserted for the first time and thus don't have any consumers below them that need to be 34 * invalidated, and also ignore the case where a provider is being detached because a chunk of UI is 35 * being removed, meaning that no consumers below them are going to be around to be updated anyway. 36 * 37 * I think we need to have a bigger discussion around what modifier locals should look like in the 38 * Modifer.Node world. 39 */ 40 internal class ModifierLocalManager(val owner: Owner) { 41 private val inserted = mutableVectorOf<BackwardsCompatNode>() 42 private val insertedLocal = mutableVectorOf<ModifierLocal<*>>() 43 private val removed = mutableVectorOf<LayoutNode>() 44 private val removedLocal = mutableVectorOf<ModifierLocal<*>>() 45 private var invalidated: Boolean = false 46 47 fun invalidate() { 48 if (!invalidated) { 49 invalidated = true 50 owner.registerOnEndApplyChangesListener { this.triggerUpdates() } 51 } 52 } 53 54 fun triggerUpdates() { 55 invalidated = false 56 // We want to make sure that we only call update on a node once, but if a provider gets 57 // removed and a new one gets inserted in its place, we will encounter it when we iterate 58 // both the rmoved node and the inserted one, so we store all of the consumers we want to 59 // update in a set and call update on them at the end. 60 val toUpdate = hashSetOf<BackwardsCompatNode>() 61 removed.forEachIndexed { i, layout -> 62 val key = removedLocal[i] 63 if (layout.nodes.head.isAttached) { 64 // if the layout is still attached, that means that this provider got removed and 65 // there's possible some consumers below it that need to be updated 66 invalidateConsumersOfNodeForKey(layout.nodes.head, key, toUpdate) 67 } 68 } 69 removed.clear() 70 removedLocal.clear() 71 // TODO(lmr): we could potentially opt for a more sophisticated strategy here where we 72 // start from the higher up nodes, and invalidate in a way where during traversal if we 73 // happen upon other inserted nodes we can remove them from the inserted set 74 inserted.forEachIndexed { i, node -> 75 val key = insertedLocal[i] 76 if (node.isAttached) { 77 invalidateConsumersOfNodeForKey(node, key, toUpdate) 78 } 79 } 80 inserted.clear() 81 insertedLocal.clear() 82 toUpdate.forEach { it.updateModifierLocalConsumer() } 83 } 84 85 private fun invalidateConsumersOfNodeForKey( 86 node: Modifier.Node, 87 key: ModifierLocal<*>, 88 set: MutableSet<BackwardsCompatNode> 89 ) { 90 node.visitSubtreeIf(Nodes.Locals) { 91 if (it is BackwardsCompatNode && it.element is ModifierLocalConsumer) { 92 if (it.readValues.contains(key)) { 93 set.add(it) 94 } 95 } 96 // only continue if this node didn't also provide it 97 !it.providedValues.contains(key) 98 } 99 } 100 101 fun updatedProvider(node: BackwardsCompatNode, key: ModifierLocal<*>) { 102 inserted += node 103 insertedLocal += key 104 invalidate() 105 } 106 107 fun insertedProvider(node: BackwardsCompatNode, key: ModifierLocal<*>) { 108 inserted += node 109 insertedLocal += key 110 invalidate() 111 } 112 113 fun removedProvider(node: BackwardsCompatNode, key: ModifierLocal<*>) { 114 removed += node.requireLayoutNode() 115 removedLocal += key 116 invalidate() 117 } 118 } 119