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