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.collection.mutableObjectIntMapOf
20 import androidx.compose.ui.ComposeUiFlags
21 import androidx.compose.ui.ExperimentalComposeUiApi
22 import androidx.compose.ui.Modifier
23 import androidx.compose.ui.classKeyForObject
24 import androidx.compose.ui.draw.DrawModifier
25 import androidx.compose.ui.focus.FocusEventModifierNode
26 import androidx.compose.ui.focus.FocusProperties
27 import androidx.compose.ui.focus.FocusPropertiesModifierNode
28 import androidx.compose.ui.focus.FocusTargetNode
29 import androidx.compose.ui.focus.invalidateFocusEvent
30 import androidx.compose.ui.focus.invalidateFocusProperties
31 import androidx.compose.ui.focus.invalidateFocusTarget
32 import androidx.compose.ui.input.indirect.IndirectTouchInputModifierNode
33 import androidx.compose.ui.input.key.KeyInputModifierNode
34 import androidx.compose.ui.input.key.SoftKeyboardInterceptionModifierNode
35 import androidx.compose.ui.input.pointer.PointerInputModifier
36 import androidx.compose.ui.input.rotary.RotaryInputModifierNode
37 import androidx.compose.ui.internal.checkPrecondition
38 import androidx.compose.ui.internal.checkPreconditionNotNull
39 import androidx.compose.ui.layout.ApproachLayoutModifierNode
40 import androidx.compose.ui.layout.LayoutModifier
41 import androidx.compose.ui.layout.OnGloballyPositionedModifier
42 import androidx.compose.ui.layout.OnPlacedModifier
43 import androidx.compose.ui.layout.OnRemeasuredModifier
44 import androidx.compose.ui.layout.ParentDataModifier
45 import androidx.compose.ui.modifier.ModifierLocalConsumer
46 import androidx.compose.ui.modifier.ModifierLocalModifierNode
47 import androidx.compose.ui.modifier.ModifierLocalProvider
48 import androidx.compose.ui.relocation.BringIntoViewModifierNode
49 import androidx.compose.ui.semantics.SemanticsModifier
50 import kotlin.jvm.JvmInline
51 import kotlin.jvm.JvmStatic
52
53 @Suppress("NOTHING_TO_INLINE")
54 @JvmInline
55 internal value class NodeKind<T>(val mask: Int) {
ornull56 inline infix fun or(other: NodeKind<*>): Int = mask or other.mask
57
58 inline infix fun or(other: Int): Int = mask or other
59 }
60
61 @Suppress("NOTHING_TO_INLINE")
62 internal inline infix fun Int.or(other: NodeKind<*>): Int = this or other.mask
63
64 @Suppress("NOTHING_TO_INLINE")
65 internal inline operator fun Int.contains(value: NodeKind<*>): Boolean = this and value.mask != 0
66
67 // For a given NodeCoordinator, the "LayoutAware" nodes that it is concerned with should include
68 // its own measureNode if the measureNode happens to implement LayoutAware. If the measureNode
69 // implements any other node interfaces, such as draw, those should be visited by the coordinator
70 // below them.
71 internal val NodeKind<*>.includeSelfInTraversal: Boolean
72 get() = mask and Nodes.LayoutAware.mask != 0
73
74 // Note that these don't inherit from Modifier.Node to allow for a single Modifier.Node
75 // instance to implement multiple Node interfaces
76
77 internal object Nodes {
78 @JvmStatic
79 inline val Any
80 get() = NodeKind<Modifier.Node>(0b1 shl 0)
81
82 @JvmStatic
83 inline val Layout
84 get() = NodeKind<LayoutModifierNode>(0b1 shl 1)
85
86 @JvmStatic
87 inline val Draw
88 get() = NodeKind<DrawModifierNode>(0b1 shl 2)
89
90 @JvmStatic
91 inline val Semantics
92 get() = NodeKind<SemanticsModifierNode>(0b1 shl 3)
93
94 @JvmStatic
95 inline val PointerInput
96 get() = NodeKind<PointerInputModifierNode>(0b1 shl 4)
97
98 @JvmStatic
99 inline val Locals
100 get() = NodeKind<ModifierLocalModifierNode>(0b1 shl 5)
101
102 @JvmStatic
103 inline val ParentData
104 get() = NodeKind<ParentDataModifierNode>(0b1 shl 6)
105
106 @JvmStatic
107 inline val LayoutAware
108 get() = NodeKind<LayoutAwareModifierNode>(0b1 shl 7)
109
110 @JvmStatic
111 inline val GlobalPositionAware
112 get() = NodeKind<GlobalPositionAwareModifierNode>(0b1 shl 8)
113
114 @JvmStatic
115 inline val ApproachMeasure
116 get() = NodeKind<ApproachLayoutModifierNode>(0b1 shl 9)
117
118 @JvmStatic
119 inline val FocusTarget
120 get() = NodeKind<FocusTargetNode>(0b1 shl 10)
121
122 @JvmStatic
123 inline val FocusProperties
124 get() = NodeKind<FocusPropertiesModifierNode>(0b1 shl 11)
125
126 @JvmStatic
127 inline val FocusEvent
128 get() = NodeKind<FocusEventModifierNode>(0b1 shl 12)
129
130 @JvmStatic
131 inline val KeyInput
132 get() = NodeKind<KeyInputModifierNode>(0b1 shl 13)
133
134 @JvmStatic
135 inline val RotaryInput
136 get() = NodeKind<RotaryInputModifierNode>(0b1 shl 14)
137
138 @JvmStatic
139 inline val CompositionLocalConsumer
140 get() = NodeKind<CompositionLocalConsumerModifierNode>(0b1 shl 15)
141
142 @JvmStatic
143 inline val SoftKeyboardKeyInput
144 get() = NodeKind<SoftKeyboardInterceptionModifierNode>(0b1 shl 17)
145
146 @JvmStatic
147 inline val Traversable
148 get() = NodeKind<TraversableNode>(0b1 shl 18)
149
150 @JvmStatic
151 inline val BringIntoView
152 get() = NodeKind<BringIntoViewModifierNode>(0b1 shl 19)
153
154 @JvmStatic
155 inline val Unplaced
156 get() = NodeKind<OnUnplacedModifierNode>(0b1 shl 20)
157
158 @JvmStatic
159 @OptIn(ExperimentalComposeUiApi::class)
160 inline val IndirectTouchInput
161 get() = NodeKind<IndirectTouchInputModifierNode>(0b1 shl 21)
162 // ...
163 }
164
calculateNodeKindSetFromnull165 internal fun calculateNodeKindSetFrom(element: Modifier.Element): Int {
166 var mask = Nodes.Any.mask
167 if (element is LayoutModifier) {
168 mask = mask or Nodes.Layout
169 }
170 if (element is DrawModifier) {
171 mask = mask or Nodes.Draw
172 }
173 if (element is SemanticsModifier) {
174 mask = mask or Nodes.Semantics
175 }
176 if (element is PointerInputModifier) {
177 mask = mask or Nodes.PointerInput
178 }
179 if (element is ModifierLocalConsumer || element is ModifierLocalProvider<*>) {
180 mask = mask or Nodes.Locals
181 }
182 @Suppress("DEPRECATION")
183 if (element is androidx.compose.ui.focus.FocusEventModifier) {
184 mask = mask or Nodes.FocusEvent
185 }
186 @Suppress("DEPRECATION")
187 if (element is androidx.compose.ui.focus.FocusOrderModifier) {
188 mask = mask or Nodes.FocusProperties
189 }
190 if (element is OnGloballyPositionedModifier) {
191 mask = mask or Nodes.GlobalPositionAware
192 }
193 if (element is ParentDataModifier) {
194 mask = mask or Nodes.ParentData
195 }
196 if (element is OnPlacedModifier || element is OnRemeasuredModifier) {
197 mask = mask or Nodes.LayoutAware
198 }
199 if (element is BringIntoViewModifierNode) {
200 mask = mask or Nodes.BringIntoView
201 }
202 return mask
203 }
204
205 private val classToKindSetMap = mutableObjectIntMapOf<Any>()
206
calculateNodeKindSetFromnull207 internal fun calculateNodeKindSetFrom(node: Modifier.Node): Int {
208 // This function does not take delegates into account, as a result, the kindSet will never
209 // change, so if it is non-zero, it means we've already calculated it and we can just bail
210 // early here.
211 if (node.kindSet != 0) return node.kindSet
212 return classToKindSetMap.getOrPut(classKeyForObject(node)) {
213 var mask = Nodes.Any.mask
214 if (node is LayoutModifierNode) {
215 mask = mask or Nodes.Layout
216 }
217 if (node is DrawModifierNode) {
218 mask = mask or Nodes.Draw
219 }
220 if (node is SemanticsModifierNode) {
221 mask = mask or Nodes.Semantics
222 }
223 if (node is PointerInputModifierNode) {
224 mask = mask or Nodes.PointerInput
225 }
226 if (node is ModifierLocalModifierNode) {
227 mask = mask or Nodes.Locals
228 }
229 if (node is ParentDataModifierNode) {
230 mask = mask or Nodes.ParentData
231 }
232 if (node is LayoutAwareModifierNode) {
233 mask = mask or Nodes.LayoutAware
234 }
235 if (node is GlobalPositionAwareModifierNode) {
236 mask = mask or Nodes.GlobalPositionAware
237 }
238 if (node is ApproachLayoutModifierNode) {
239 mask = mask or Nodes.ApproachMeasure
240 }
241 if (node is FocusTargetNode) {
242 mask = mask or Nodes.FocusTarget
243 }
244 if (node is FocusPropertiesModifierNode) {
245 mask = mask or Nodes.FocusProperties
246 }
247 if (node is FocusEventModifierNode) {
248 mask = mask or Nodes.FocusEvent
249 }
250 if (node is KeyInputModifierNode) {
251 mask = mask or Nodes.KeyInput
252 }
253 if (node is RotaryInputModifierNode) {
254 mask = mask or Nodes.RotaryInput
255 }
256 if (node is CompositionLocalConsumerModifierNode) {
257 mask = mask or Nodes.CompositionLocalConsumer
258 }
259 if (node is SoftKeyboardInterceptionModifierNode) {
260 mask = mask or Nodes.SoftKeyboardKeyInput
261 }
262 if (node is TraversableNode) {
263 mask = mask or Nodes.Traversable
264 }
265 if (node is BringIntoViewModifierNode) {
266 mask = mask or Nodes.BringIntoView
267 }
268 if (node is OnUnplacedModifierNode) {
269 mask = mask or Nodes.Unplaced
270 }
271 @OptIn(ExperimentalComposeUiApi::class)
272 if (node is IndirectTouchInputModifierNode) {
273 mask = mask or Nodes.IndirectTouchInput
274 }
275 mask
276 }
277 }
278
279 @Suppress("ConstPropertyName") private const val Updated = 0
280 @Suppress("ConstPropertyName") private const val Inserted = 1
281 @Suppress("ConstPropertyName") private const val Removed = 2
282
autoInvalidateRemovedNodenull283 internal fun autoInvalidateRemovedNode(node: Modifier.Node) {
284 checkPrecondition(node.isAttached) { "autoInvalidateRemovedNode called on unattached node" }
285 autoInvalidateNodeIncludingDelegates(node, 0.inv(), Removed)
286 }
287
autoInvalidateInsertedNodenull288 internal fun autoInvalidateInsertedNode(node: Modifier.Node) {
289 checkPrecondition(node.isAttached) { "autoInvalidateInsertedNode called on unattached node" }
290 autoInvalidateNodeIncludingDelegates(node, 0.inv(), Inserted)
291 }
292
autoInvalidateUpdatedNodenull293 internal fun autoInvalidateUpdatedNode(node: Modifier.Node) {
294 checkPrecondition(node.isAttached) { "autoInvalidateUpdatedNode called on unattached node" }
295 autoInvalidateNodeIncludingDelegates(node, 0.inv(), Updated)
296 }
297
autoInvalidateNodeIncludingDelegatesnull298 internal fun autoInvalidateNodeIncludingDelegates(
299 node: Modifier.Node,
300 remainingSet: Int,
301 phase: Int,
302 ) {
303 if (node is DelegatingNode) {
304 autoInvalidateNodeSelf(node, node.selfKindSet and remainingSet, phase)
305 val newRemaining = remainingSet and node.selfKindSet.inv()
306 node.forEachImmediateDelegate {
307 autoInvalidateNodeIncludingDelegates(it, newRemaining, phase)
308 }
309 } else {
310 autoInvalidateNodeSelf(node, node.kindSet and remainingSet, phase)
311 }
312 }
313
autoInvalidateNodeSelfnull314 private fun autoInvalidateNodeSelf(node: Modifier.Node, selfKindSet: Int, phase: Int) {
315 // TODO(lmr): Implementing it this way means that delegates of an autoInvalidate=false node will
316 // still get invalidated. Not sure if that's what we want or not.
317 // Don't invalidate the node if it marks itself as autoInvalidate = false.
318 if (phase == Updated && !node.shouldAutoInvalidate) return
319 if (Nodes.Layout in selfKindSet && node is LayoutModifierNode) {
320 node.invalidateMeasurement()
321 if (phase == Removed) {
322 val coordinator = node.requireCoordinator(Nodes.Layout)
323 coordinator.onRelease()
324 }
325 }
326 if (Nodes.LayoutAware in selfKindSet && node is LayoutAwareModifierNode) {
327 // No need to invalidate layout when removing a LayoutAwareModifierNode, as these won't be
328 // invoked anyway
329 if (phase != Removed) {
330 node.requireLayoutNode().invalidateMeasurements()
331 }
332 }
333 if (Nodes.GlobalPositionAware in selfKindSet && node is GlobalPositionAwareModifierNode) {
334 // No need to invalidate when removing a GlobalPositionAwareModifierNode, as these won't be
335 // invoked anyway
336 if (phase != Removed) {
337 node.requireLayoutNode().invalidateOnPositioned()
338 }
339 }
340 if (Nodes.Draw in selfKindSet && node is DrawModifierNode) {
341 node.invalidateDraw()
342 }
343 if (Nodes.Semantics in selfKindSet && node is SemanticsModifierNode) {
344 node.requireLayoutNode().isSemanticsInvalidated = true
345 }
346 if (Nodes.ParentData in selfKindSet && node is ParentDataModifierNode) {
347 node.invalidateParentData()
348 }
349 if (
350 Nodes.FocusProperties in selfKindSet &&
351 node is FocusPropertiesModifierNode &&
352 node.specifiesCanFocusProperty()
353 ) {
354 if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isTrackFocusEnabled)
355 node.scheduleInvalidationOfAssociatedFocusTargets()
356 else {
357 when (phase) {
358 Removed -> node.scheduleInvalidationOfAssociatedFocusTargets()
359 else -> node.invalidateFocusProperties()
360 }
361 }
362 }
363 if (Nodes.FocusEvent in selfKindSet && node is FocusEventModifierNode) {
364 node.invalidateFocusEvent()
365 }
366 }
367
scheduleInvalidationOfAssociatedFocusTargetsnull368 private fun FocusPropertiesModifierNode.scheduleInvalidationOfAssociatedFocusTargets() {
369 visitChildren(Nodes.FocusTarget) {
370 // Schedule invalidation for the focus target,
371 // which will cause it to recalculate focus properties.
372 it.invalidateFocusTarget()
373 }
374 }
375
376 /**
377 * This function checks if the FocusProperties node has set the canFocus [FocusProperties.canFocus]
378 * property.
379 *
380 * We use a singleton CanFocusChecker to prevent extra allocations, and in doing so, we assume that
381 * there won't be multiple concurrent calls of this function. This is not an issue since this is
382 * called from the main thread, but if this changes in the future, replace the
383 * [CanFocusChecker.reset] call with a new [FocusProperties] object for every invocation.
384 */
specifiesCanFocusPropertynull385 private fun FocusPropertiesModifierNode.specifiesCanFocusProperty(): Boolean {
386 CanFocusChecker.reset()
387 applyFocusProperties(CanFocusChecker)
388 return CanFocusChecker.isCanFocusSet()
389 }
390
391 private object CanFocusChecker : FocusProperties {
392 private var canFocusValue: Boolean? = null
393 override var canFocus: Boolean
<lambda>null394 get() = checkPreconditionNotNull(canFocusValue) { "canFocus is read before it is written" }
395 set(value) {
396 canFocusValue = value
397 }
398
isCanFocusSetnull399 fun isCanFocusSet(): Boolean = canFocusValue != null
400
401 fun reset() {
402 canFocusValue = null
403 }
404 }
405
calculateNodeKindSetFromIncludingDelegatesnull406 internal fun calculateNodeKindSetFromIncludingDelegates(node: Modifier.Node): Int {
407 return if (node is DelegatingNode) {
408 var mask = node.selfKindSet
409 node.forEachImmediateDelegate {
410 mask = mask or calculateNodeKindSetFromIncludingDelegates(it)
411 }
412 mask
413 } else {
414 calculateNodeKindSetFrom(node)
415 }
416 }
417