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.modifier
18
19 import androidx.compose.runtime.getValue
20 import androidx.compose.runtime.mutableStateMapOf
21 import androidx.compose.runtime.mutableStateOf
22 import androidx.compose.runtime.setValue
23 import androidx.compose.ui.internal.checkPrecondition
24 import androidx.compose.ui.internal.requirePrecondition
25 import androidx.compose.ui.node.DelegatableNode
26 import androidx.compose.ui.node.Nodes
27 import androidx.compose.ui.node.visitAncestors
28 import androidx.compose.ui.util.fastMap
29
30 /**
31 * An opaque key-value holder of [ModifierLocal]s to be used with [ModifierLocalModifierNode].
32 *
33 * @see modifierLocalMapOf
34 */
35 sealed class ModifierLocalMap {
setnull36 internal abstract operator fun <T> set(key: ModifierLocal<T>, value: T)
37
38 internal abstract operator fun <T> get(key: ModifierLocal<T>): T?
39
40 internal abstract operator fun contains(key: ModifierLocal<*>): Boolean
41 }
42
43 internal class SingleLocalMap(private val key: ModifierLocal<*>) : ModifierLocalMap() {
44 private var value: Any? by mutableStateOf(null)
45
46 internal fun forceValue(value: Any?) {
47 this.value = value
48 }
49
50 override operator fun <T> set(key: ModifierLocal<T>, value: T) {
51 @Suppress("ExceptionMessage") checkPrecondition(key === this.key)
52 this.value = value
53 }
54
55 @Suppress("UNCHECKED_CAST", "ExceptionMessage")
56 override operator fun <T> get(key: ModifierLocal<T>): T? {
57 checkPrecondition(key === this.key)
58 return value as? T?
59 }
60
61 override operator fun contains(key: ModifierLocal<*>): Boolean = key === this.key
62 }
63
64 internal class BackwardsCompatLocalMap(var element: ModifierLocalProvider<*>) : ModifierLocalMap() {
setnull65 override operator fun <T> set(key: ModifierLocal<T>, value: T) {
66 error("Set is not allowed on a backwards compat provider")
67 }
68
69 @Suppress("UNCHECKED_CAST", "ExceptionMessage")
getnull70 override operator fun <T> get(key: ModifierLocal<T>): T? {
71 checkPrecondition(key === element.key)
72 return element.value as T
73 }
74
containsnull75 override operator fun contains(key: ModifierLocal<*>): Boolean = key === element.key
76 }
77
78 internal class MultiLocalMap(
79 entry1: Pair<ModifierLocal<*>, Any?>,
80 vararg entries: Pair<ModifierLocal<*>, Any?>
81 ) : ModifierLocalMap() {
82 private val map = mutableStateMapOf<ModifierLocal<*>, Any?>()
83
84 init {
85 map += entry1
86 map.putAll(entries.toMap())
87 }
88
89 override operator fun <T> set(key: ModifierLocal<T>, value: T) {
90 map[key] = value
91 }
92
93 override operator fun <T> get(key: ModifierLocal<T>): T? {
94 @Suppress("UNCHECKED_CAST") return map[key] as? T?
95 }
96
97 override operator fun contains(key: ModifierLocal<*>): Boolean = map.containsKey(key)
98 }
99
100 internal object EmptyMap : ModifierLocalMap() {
setnull101 override fun <T> set(key: ModifierLocal<T>, value: T) = error("")
102
103 override fun <T> get(key: ModifierLocal<T>): T = error("")
104
105 override fun contains(key: ModifierLocal<*>): Boolean = false
106 }
107
108 /**
109 * A [androidx.compose.ui.Modifier.Node] that is capable of consuming and providing [ModifierLocal]
110 * values.
111 *
112 * This is the [androidx.compose.ui.Modifier.Node] equivalent of the [ModifierLocalConsumer] and
113 * [ModifierLocalProvider] interfaces.
114 *
115 * @sample androidx.compose.ui.samples.JustReadingOrProvidingModifierLocalNodeSample
116 * @see modifierLocalOf
117 * @see ModifierLocal
118 * @see androidx.compose.runtime.CompositionLocal
119 */
120 interface ModifierLocalModifierNode : ModifierLocalReadScope, DelegatableNode {
121 /**
122 * The map of provided ModifierLocal <-> value pairs that this node is providing. This value
123 * must be overridden if you are going to provide any values. It should be overridden as a
124 * field-backed property initialized with values for all of the keys that it will ever possibly
125 * provide.
126 *
127 * By default, this property will be set to an empty map, which means that this node will only
128 * consume [ModifierLocal]s and will not provide any new values.
129 *
130 * If you would like to change a value provided in the map over time, you must use the [provide]
131 * API.
132 *
133 * @see modifierLocalMapOf
134 * @see provide
135 */
136 val providedValues: ModifierLocalMap
137 get() = EmptyMap
138
139 /**
140 * This method will cause this node to provide a new [value] for [key]. This can be called at
141 * any time on the UI thread, but in order to use this API, [providedValues] must be implemented
142 * and [key] must be a key that was included in it.
143 *
144 * By providing this new value, any [ModifierLocalModifierNode] below it in the tree will read
145 * this [value] when reading [current], until another [ModifierLocalModifierNode] provides a
146 * value for the same [key], however, consuming [ModifierLocalModifierNode]s will NOT be
147 * notified that a new value was provided.
148 */
149 fun <T> provide(key: ModifierLocal<T>, value: T) {
150 requirePrecondition(providedValues !== EmptyMap) {
151 "In order to provide locals you must override providedValues: ModifierLocalMap"
152 }
153 requirePrecondition(providedValues.contains(key)) {
154 "Any provided key must be initially provided in the overridden providedValues: " +
155 "ModifierLocalMap property. Key $key was not found."
156 }
157 providedValues[key] = value
158 }
159
160 /**
161 * Read a [ModifierLocal] that was provided by other modifiers to the left of this modifier, or
162 * above this modifier in the layout tree.
163 */
164 override val <T> ModifierLocal<T>.current: T
165 get() {
166 requirePrecondition(node.isAttached) {
167 "ModifierLocal accessed from an unattached node"
168 }
169 val key = this
170 visitAncestors(Nodes.Locals) {
171 if (it.providedValues.contains(key)) {
172 @Suppress("UNCHECKED_CAST") return it.providedValues[key] as T
173 }
174 }
175 return key.defaultFactory()
176 }
177 }
178
179 /** Creates an empty [ModifierLocalMap] */
modifierLocalMapOfnull180 fun modifierLocalMapOf(): ModifierLocalMap = EmptyMap
181
182 /** Creates a [ModifierLocalMap] with a single key and value initialized to null. */
183 fun <T> modifierLocalMapOf(key: ModifierLocal<T>): ModifierLocalMap = SingleLocalMap(key)
184
185 /**
186 * Creates a [ModifierLocalMap] with a single key and value. The provided [entry] should have
187 * [Pair::first] be the [ModifierLocal] key, and the [Pair::second] be the corresponding value.
188 */
189 fun <T> modifierLocalMapOf(entry: Pair<ModifierLocal<T>, T>): ModifierLocalMap =
190 SingleLocalMap(entry.first).also { it[entry.first] = entry.second }
191
192 /** Creates a [ModifierLocalMap] with several keys, all initialized with values of null */
modifierLocalMapOfnull193 fun modifierLocalMapOf(
194 key1: ModifierLocal<*>,
195 key2: ModifierLocal<*>,
196 vararg keys: ModifierLocal<*>
197 ): ModifierLocalMap =
198 MultiLocalMap(key1 to null, key2 to null, *keys.map { it to null }.toTypedArray())
199
200 /**
201 * Creates a [ModifierLocalMap] with multiple keys and values. The provided [entries] should have
202 * each item's [Pair::first] be the [ModifierLocal] key, and the [Pair::second] be the corresponding
203 * value.
204 */
modifierLocalMapOfnull205 fun modifierLocalMapOf(
206 entry1: Pair<ModifierLocal<*>, Any>,
207 entry2: Pair<ModifierLocal<*>, Any>,
208 vararg entries: Pair<ModifierLocal<*>, Any>
209 ): ModifierLocalMap = MultiLocalMap(entry1, entry2, *entries)
210
211 // b/280116113.
212 @Deprecated(
213 message = "Use a different overloaded version of this function",
214 level = DeprecationLevel.HIDDEN
215 )
216 fun modifierLocalMapOf(vararg keys: ModifierLocal<*>): ModifierLocalMap =
217 when (keys.size) {
218 0 -> EmptyMap
219 1 -> SingleLocalMap(keys.first())
220 else ->
221 MultiLocalMap(keys.first() to null, *keys.drop(1).fastMap { it to null }.toTypedArray())
222 }
223
224 // b/280116113.
225 @Deprecated(
226 message = "Use a different overloaded version of this function",
227 level = DeprecationLevel.HIDDEN
228 )
modifierLocalMapOfnull229 fun modifierLocalMapOf(vararg entries: Pair<ModifierLocal<*>, Any>): ModifierLocalMap =
230 when (entries.size) {
231 0 -> EmptyMap
232 1 -> MultiLocalMap(entries.first())
233 else -> MultiLocalMap(entries.first(), *entries.drop(1).toTypedArray())
234 }
235