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