ModifierNodeElement.kt

/*
 * Copyright 2022 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.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.areObjectsOfSameType
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.InspectorValueInfo
import androidx.compose.ui.platform.debugInspectorInfo

/**
 * A [Modifier.Element] which manages an instance of a particular [Modifier.Node] implementation. A
 * given [Modifier.Node] implementation can only be used when a [ModifierNodeElement] which creates
 * and updates that implementation is applied to a Layout.
 *
 * A [ModifierNodeElement] should be very lightweight, and do little more than hold the information
 * necessary to
 *
 * @see Modifier.Node
 * @see Modifier.Element
 * @see modifierElementOf
 */
@ExperimentalComposeUiApi
abstract class ModifierNodeElement<N : Modifier.Node>(
    /**
     * An object which holds all of the "inputs" or "parameters" that will be passed into the
     * [Modifier.Node]. Even though most inputs to the create/update of a [Modifier.Node] may
     * come from a captured reference, it is important to pass them into here explicitly as well so
     * that they will get included in equals() and hashCode() calculations. Having it as a single
     * object on the abstract class allows us to have anonymous objects implement
     * [ModifierNodeElement] while still having a reasonable equals implementation.
     */
    internal val params: Any? = null,
    /**
     * This lambda will construct a debug-only set of information for use with tooling.
     *
     * @see InspectorValueInfo
     */
    inspectorInfo: InspectorInfo.() -> Unit
) : Modifier.Element, InspectorValueInfo(inspectorInfo) {
    /**
     * This will be called the first time the modifier is applied to the Layout and it should
     * construct and return the correspoding [Modifier.Node] instance.
     */
    abstract fun create(): N

    /**
     * Called when a modifier is applied to a Layout whose [params] have changed from the previous
     * application. This lambda will have the current node instance passed in as a parameter, and
     * it is expected that the node will be brought up to date.
     */
    abstract fun update(node: N): N

    override fun hashCode(): Int {
        return params.hashCode()
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is ModifierNodeElement<*>) return false
        if (!areObjectsOfSameType(this, other)) return false
        return params == other.params
    }
}

/**
 * A helpful API for constructing a [ModifierNodeElement] corresponding to a particular
 * [Modifier.Node] implementation.
 *
 * @param params An object used to determine whether or not the created node should be updated or not.
 * @param create The initial creation of the node. This will be called the first time the modifier
 *  is applied to the Layout and it should construct the correspoding [Modifier.Node] instance,
 *  referencing any captured inputs necessary.
 * @param update Called when a modifier is applied to a Layout whose [params] have changed from the
 *  previous application. This lambda will have the current node instance passed in as a parameter,
 *  and it is expected that the node will be brought up to date.
 * @param definitions This lambda will construct a debug-only set of information for use with
 *  tooling.
 *
 * @sample androidx.compose.ui.samples.ModifierElementOfSample
 * @sample androidx.compose.ui.samples.LayoutModifierNodeSample
 * @sample androidx.compose.ui.samples.DrawModifierNodeSample
 * @sample androidx.compose.ui.samples.GlobalPositionAwareModifierNodeSample
 * @sample androidx.compose.ui.samples.LayoutAwareModifierNodeSample
 * @sample androidx.compose.ui.samples.PointerInputModifierNodeSample
 *
 * @see ModifierNodeElement
 * @see Modifier.Element
 */
// TODO(lmr): make sure this produces reasonable bytecode.
@Suppress("MissingNullability", "ModifierFactoryExtensionFunction")
@ExperimentalComposeUiApi
inline fun <reified T : Modifier.Node> modifierElementOf(
    params: Any?,
    crossinline create: () -> T,
    crossinline update: (T) -> Unit,
    crossinline definitions: InspectorInfo.() -> Unit
): Modifier = object : ModifierNodeElement<T>(params, debugInspectorInfo(definitions)) {
    override fun create(): T = create()
    override fun update(node: T): T = node.also(update)
}

/**
 * A helpful API for constructing a [ModifierNodeElement] corresponding to a particular
 * [Modifier.Node] implementation. This overload is expected to be used for parameter-less Modifier
 * factories. For Modifier factories with parameters, consider using the overload of this method
 * which accepts a "params" and "update" parameter.
 *
 * @param create The initial creation of the node. This will be called the first time the modifier
 *  is applied to the Layout and it should construct the correspoding [Modifier.Node] instance
 * @param definitions This lambda will construct a debug-only set of information for use with
 *  tooling.
 *
 * @sample androidx.compose.ui.samples.ModifierElementOfSample
 * @sample androidx.compose.ui.samples.SemanticsModifierNodeSample
 *
 * @see ModifierNodeElement
 * @see Modifier.Element
 */
@Suppress("MissingNullability", "ModifierFactoryExtensionFunction")
@ExperimentalComposeUiApi
inline fun <reified T : Modifier.Node> modifierElementOf(
    crossinline create: () -> T,
    crossinline definitions: InspectorInfo.() -> Unit
): Modifier = object : ModifierNodeElement<T>(null, debugInspectorInfo(definitions)) {
    override fun create(): T = create()
    override fun update(node: T): T = node
}