All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.androidx.compose.ui.node.NodeKind.kt Maven / Gradle / Ivy

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.ui.node

import androidx.collection.mutableObjectIntMapOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.classKeyForObject
import androidx.compose.ui.draw.DrawModifier
import androidx.compose.ui.focus.FocusEventModifierNode
import androidx.compose.ui.focus.FocusProperties
import androidx.compose.ui.focus.FocusPropertiesModifierNode
import androidx.compose.ui.focus.FocusTargetNode
import androidx.compose.ui.focus.invalidateFocusEvent
import androidx.compose.ui.focus.invalidateFocusProperties
import androidx.compose.ui.focus.invalidateFocusTarget
import androidx.compose.ui.input.key.KeyInputModifierNode
import androidx.compose.ui.input.key.SoftKeyboardInterceptionModifierNode
import androidx.compose.ui.input.pointer.PointerInputModifier
import androidx.compose.ui.input.rotary.RotaryInputModifierNode
import androidx.compose.ui.internal.checkPrecondition
import androidx.compose.ui.internal.checkPreconditionNotNull
import androidx.compose.ui.layout.ApproachLayoutModifierNode
import androidx.compose.ui.layout.LayoutModifier
import androidx.compose.ui.layout.OnGloballyPositionedModifier
import androidx.compose.ui.layout.OnPlacedModifier
import androidx.compose.ui.layout.OnRemeasuredModifier
import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.modifier.ModifierLocalConsumer
import androidx.compose.ui.modifier.ModifierLocalModifierNode
import androidx.compose.ui.modifier.ModifierLocalProvider
import androidx.compose.ui.semantics.SemanticsModifier
import kotlin.jvm.JvmInline
import kotlin.jvm.JvmStatic

@Suppress("NOTHING_TO_INLINE")
@JvmInline
internal value class NodeKind(val mask: Int) {
    inline infix fun or(other: NodeKind<*>): Int = mask or other.mask

    inline infix fun or(other: Int): Int = mask or other
}

@Suppress("NOTHING_TO_INLINE")
internal inline infix fun Int.or(other: NodeKind<*>): Int = this or other.mask

@Suppress("NOTHING_TO_INLINE")
internal inline operator fun Int.contains(value: NodeKind<*>): Boolean = this and value.mask != 0

// For a given NodeCoordinator, the "LayoutAware" nodes that it is concerned with should include
// its own measureNode if the measureNode happens to implement LayoutAware. If the measureNode
// implements any other node interfaces, such as draw, those should be visited by the coordinator
// below them.
internal val NodeKind<*>.includeSelfInTraversal: Boolean
    get() = mask and Nodes.LayoutAware.mask != 0

// Note that these don't inherit from Modifier.Node to allow for a single Modifier.Node
// instance to implement multiple Node interfaces

internal object Nodes {
    @JvmStatic
    inline val Any
        get() = NodeKind(0b1 shl 0)

    @JvmStatic
    inline val Layout
        get() = NodeKind(0b1 shl 1)

    @JvmStatic
    inline val Draw
        get() = NodeKind(0b1 shl 2)

    @JvmStatic
    inline val Semantics
        get() = NodeKind(0b1 shl 3)

    @JvmStatic
    inline val PointerInput
        get() = NodeKind(0b1 shl 4)

    @JvmStatic
    inline val Locals
        get() = NodeKind(0b1 shl 5)

    @JvmStatic
    inline val ParentData
        get() = NodeKind(0b1 shl 6)

    @JvmStatic
    inline val LayoutAware
        get() = NodeKind(0b1 shl 7)

    @JvmStatic
    inline val GlobalPositionAware
        get() = NodeKind(0b1 shl 8)

    @JvmStatic
    inline val ApproachMeasure
        get() = NodeKind(0b1 shl 9)

    @JvmStatic
    inline val FocusTarget
        get() = NodeKind(0b1 shl 10)

    @JvmStatic
    inline val FocusProperties
        get() = NodeKind(0b1 shl 11)

    @JvmStatic
    inline val FocusEvent
        get() = NodeKind(0b1 shl 12)

    @JvmStatic
    inline val KeyInput
        get() = NodeKind(0b1 shl 13)

    @JvmStatic
    inline val RotaryInput
        get() = NodeKind(0b1 shl 14)

    @JvmStatic
    inline val CompositionLocalConsumer
        get() = NodeKind(0b1 shl 15)

    @JvmStatic
    inline val SoftKeyboardKeyInput
        get() = NodeKind(0b1 shl 17)

    @JvmStatic
    inline val Traversable
        get() = NodeKind(0b1 shl 18)

    @JvmStatic
    inline val Unplaced
        get() = NodeKind(0b1 shl 19)
    // ...
}

internal fun calculateNodeKindSetFrom(element: Modifier.Element): Int {
    var mask = Nodes.Any.mask
    if (element is LayoutModifier) {
        mask = mask or Nodes.Layout
    }
    if (element is DrawModifier) {
        mask = mask or Nodes.Draw
    }
    if (element is SemanticsModifier) {
        mask = mask or Nodes.Semantics
    }
    if (element is PointerInputModifier) {
        mask = mask or Nodes.PointerInput
    }
    if (element is ModifierLocalConsumer || element is ModifierLocalProvider<*>) {
        mask = mask or Nodes.Locals
    }
    @Suppress("DEPRECATION")
    if (element is androidx.compose.ui.focus.FocusEventModifier) {
        mask = mask or Nodes.FocusEvent
    }
    @Suppress("DEPRECATION")
    if (element is androidx.compose.ui.focus.FocusOrderModifier) {
        mask = mask or Nodes.FocusProperties
    }
    if (element is OnGloballyPositionedModifier) {
        mask = mask or Nodes.GlobalPositionAware
    }
    if (element is ParentDataModifier) {
        mask = mask or Nodes.ParentData
    }
    if (element is OnPlacedModifier || element is OnRemeasuredModifier) {
        mask = mask or Nodes.LayoutAware
    }
    return mask
}

private val classToKindSetMap = mutableObjectIntMapOf()

internal fun calculateNodeKindSetFrom(node: Modifier.Node): Int {
    // This function does not take delegates into account, as a result, the kindSet will never
    // change, so if it is non-zero, it means we've already calculated it and we can just bail
    // early here.
    if (node.kindSet != 0) return node.kindSet
    return classToKindSetMap.getOrPut(classKeyForObject(node)) {
        var mask = Nodes.Any.mask
        if (node is LayoutModifierNode) {
            mask = mask or Nodes.Layout
        }
        if (node is DrawModifierNode) {
            mask = mask or Nodes.Draw
        }
        if (node is SemanticsModifierNode) {
            mask = mask or Nodes.Semantics
        }
        if (node is PointerInputModifierNode) {
            mask = mask or Nodes.PointerInput
        }
        if (node is ModifierLocalModifierNode) {
            mask = mask or Nodes.Locals
        }
        if (node is ParentDataModifierNode) {
            mask = mask or Nodes.ParentData
        }
        if (node is LayoutAwareModifierNode) {
            mask = mask or Nodes.LayoutAware
        }
        if (node is GlobalPositionAwareModifierNode) {
            mask = mask or Nodes.GlobalPositionAware
        }
        if (node is ApproachLayoutModifierNode) {
            mask = mask or Nodes.ApproachMeasure
        }
        if (node is FocusTargetNode) {
            mask = mask or Nodes.FocusTarget
        }
        if (node is FocusPropertiesModifierNode) {
            mask = mask or Nodes.FocusProperties
        }
        if (node is FocusEventModifierNode) {
            mask = mask or Nodes.FocusEvent
        }
        if (node is KeyInputModifierNode) {
            mask = mask or Nodes.KeyInput
        }
        if (node is RotaryInputModifierNode) {
            mask = mask or Nodes.RotaryInput
        }
        if (node is CompositionLocalConsumerModifierNode) {
            mask = mask or Nodes.CompositionLocalConsumer
        }
        if (node is SoftKeyboardInterceptionModifierNode) {
            mask = mask or Nodes.SoftKeyboardKeyInput
        }
        if (node is TraversableNode) {
            mask = mask or Nodes.Traversable
        }
        if (node is OnUnplacedModifierNode) {
            mask = mask or Nodes.Unplaced
        }
        mask
    }
}

@Suppress("ConstPropertyName") private const val Updated = 0
@Suppress("ConstPropertyName") private const val Inserted = 1
@Suppress("ConstPropertyName") private const val Removed = 2

internal fun autoInvalidateRemovedNode(node: Modifier.Node) {
    checkPrecondition(node.isAttached) { "autoInvalidateRemovedNode called on unattached node" }
    autoInvalidateNodeIncludingDelegates(node, 0.inv(), Removed)
}

internal fun autoInvalidateInsertedNode(node: Modifier.Node) {
    checkPrecondition(node.isAttached) { "autoInvalidateInsertedNode called on unattached node" }
    autoInvalidateNodeIncludingDelegates(node, 0.inv(), Inserted)
}

internal fun autoInvalidateUpdatedNode(node: Modifier.Node) {
    checkPrecondition(node.isAttached) { "autoInvalidateUpdatedNode called on unattached node" }
    autoInvalidateNodeIncludingDelegates(node, 0.inv(), Updated)
}

internal fun autoInvalidateNodeIncludingDelegates(
    node: Modifier.Node,
    remainingSet: Int,
    phase: Int,
) {
    if (node is DelegatingNode) {
        autoInvalidateNodeSelf(node, node.selfKindSet and remainingSet, phase)
        val newRemaining = remainingSet and node.selfKindSet.inv()
        node.forEachImmediateDelegate {
            autoInvalidateNodeIncludingDelegates(it, newRemaining, phase)
        }
    } else {
        autoInvalidateNodeSelf(node, node.kindSet and remainingSet, phase)
    }
}

private fun autoInvalidateNodeSelf(node: Modifier.Node, selfKindSet: Int, phase: Int) {
    // TODO(lmr): Implementing it this way means that delegates of an autoInvalidate=false node will
    //  still get invalidated. Not sure if that's what we want or not.
    // Don't invalidate the node if it marks itself as autoInvalidate = false.
    if (phase == Updated && !node.shouldAutoInvalidate) return
    if (Nodes.Layout in selfKindSet && node is LayoutModifierNode) {
        node.invalidateMeasurement()
        if (phase == Removed) {
            val coordinator = node.requireCoordinator(Nodes.Layout)
            coordinator.onRelease()
        }
    }
    if (Nodes.LayoutAware in selfKindSet && node is LayoutAwareModifierNode) {
        // No need to invalidate layout when removing a LayoutAwareModifierNode, as these won't be
        // invoked anyway
        if (phase != Removed) {
            node.requireLayoutNode().invalidateMeasurements()
        }
    }
    if (Nodes.GlobalPositionAware in selfKindSet && node is GlobalPositionAwareModifierNode) {
        // No need to invalidate when removing a GlobalPositionAwareModifierNode, as these won't be
        // invoked anyway
        if (phase != Removed) {
            node.requireLayoutNode().invalidateOnPositioned()
        }
    }
    if (Nodes.Draw in selfKindSet && node is DrawModifierNode) {
        node.invalidateDraw()
    }
    if (Nodes.Semantics in selfKindSet && node is SemanticsModifierNode) {
        node.invalidateSemantics()
    }
    if (Nodes.ParentData in selfKindSet && node is ParentDataModifierNode) {
        node.invalidateParentData()
    }
    if (
        Nodes.FocusProperties in selfKindSet &&
        node is FocusPropertiesModifierNode &&
        node.specifiesCanFocusProperty()
    ) {
        when (phase) {
            Removed -> node.scheduleInvalidationOfAssociatedFocusTargets()
            else -> node.invalidateFocusProperties()
        }
    }
    if (Nodes.FocusEvent in selfKindSet && node is FocusEventModifierNode) {
        node.invalidateFocusEvent()
    }
}

private fun FocusPropertiesModifierNode.scheduleInvalidationOfAssociatedFocusTargets() {
    visitChildren(Nodes.FocusTarget) {
        // Schedule invalidation for the focus target,
        // which will cause it to recalculate focus properties.
        it.invalidateFocusTarget()
    }
}

/**
 * This function checks if the FocusProperties node has set the canFocus [FocusProperties.canFocus]
 * property.
 *
 * We use a singleton CanFocusChecker to prevent extra allocations, and in doing so, we assume that
 * there won't be multiple concurrent calls of this function. This is not an issue since this is
 * called from the main thread, but if this changes in the future, replace the
 * [CanFocusChecker.reset] call with a new [FocusProperties] object for every invocation.
 */
private fun FocusPropertiesModifierNode.specifiesCanFocusProperty(): Boolean {
    CanFocusChecker.reset()
    applyFocusProperties(CanFocusChecker)
    return CanFocusChecker.isCanFocusSet()
}

private object CanFocusChecker : FocusProperties {
    private var canFocusValue: Boolean? = null
    override var canFocus: Boolean
        get() = checkPreconditionNotNull(canFocusValue) { "canFocus is read before it is written" }
        set(value) {
            canFocusValue = value
        }

    fun isCanFocusSet(): Boolean = canFocusValue != null

    fun reset() {
        canFocusValue = null
    }
}

internal fun calculateNodeKindSetFromIncludingDelegates(node: Modifier.Node): Int {
    return if (node is DelegatingNode) {
        var mask = node.selfKindSet
        node.forEachImmediateDelegate {
            mask = mask or calculateNodeKindSetFromIncludingDelegates(it)
        }
        mask
    } else {
        calculateNodeKindSetFrom(node)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy