FocusOrderModifier.kt

/*
 * Copyright 2020 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.focus

import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.InspectorValueInfo
import androidx.compose.ui.platform.debugInspectorInfo

/**
 * A [modifier][Modifier.Element] that can be used to set a custom focus traversal order.
 *
 * @see Modifier.focusOrder
 */
interface FocusOrderModifier : Modifier.Element {

    /**
     * Populates the [next][FocusOrder.next] / [left][FocusOrder.left] /
     * [right][FocusOrder.right] / [up][FocusOrder.up] / [down][FocusOrder.down] items if
     * you don't want to use the default focus traversal order.
     */
    fun populateFocusOrder(focusOrder: FocusOrder)
}

/**
 * Specifies custom focus destinations that are used instead of the default focus traversal order.
 *
 * @sample androidx.compose.ui.samples.CustomFocusOrderSample
 */
class FocusOrder {

    /**
     * A custom item to be used when the user requests a focus moves to the "next" item.
     *
     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
     */
    var next: FocusRequester = FocusRequester.Default

    /**
     * A custom item to be used when the user requests a focus moves to the "previous" item.
     *
     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
     */
    var previous: FocusRequester = FocusRequester.Default

    /**
     *  A custom item to be used when the user moves focus "up".
     *
     *  @sample androidx.compose.ui.samples.CustomFocusOrderSample
     */
    var up: FocusRequester = FocusRequester.Default

    /**
     *  A custom item to be used when the user moves focus "down".
     *
     *  @sample androidx.compose.ui.samples.CustomFocusOrderSample
     */
    var down: FocusRequester = FocusRequester.Default

    /**
     * A custom item to be used when the user requests a focus moves to the "left" item.
     *
     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
     */
    var left: FocusRequester = FocusRequester.Default

    /**
     * A custom item to be used when the user requests a focus moves to the "right" item.
     *
     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
     */
    var right: FocusRequester = FocusRequester.Default

    /**
     * A custom item to be used when the user requests a focus moves to the "left" in LTR mode and
     * "right" in RTL mode.
     *
     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
     */
    var start: FocusRequester = FocusRequester.Default

    /**
     * A custom item to be used when the user requests a focus moves to the "right" in LTR mode
     * and "left" in RTL mode.
     *
     * @sample androidx.compose.ui.samples.CustomFocusOrderSample
     */
    var end: FocusRequester = FocusRequester.Default
}

internal class FocusOrderModifierImpl(
    val focusOrderReceiver: FocusOrder.() -> Unit,
    inspectorInfo: InspectorInfo.() -> Unit
) : FocusOrderModifier, InspectorValueInfo(inspectorInfo) {
    override fun populateFocusOrder(focusOrder: FocusOrder) {
        focusOrderReceiver(focusOrder)
    }
}

/**
 * Use this modifier to specify a custom focus traversal order.
 *
 * @param focusOrderReceiver Specifies [FocusRequester]s that are used when the user wants
 * to move the current focus to the [next][FocusOrder.next] item, or wants to move
 * focus [left][FocusOrder.left], [right][FocusOrder.right], [up][FocusOrder.up] or
 * [down][FocusOrder.down].
 *
 * @sample androidx.compose.ui.samples.CustomFocusOrderSample
 */
fun Modifier.focusOrder(focusOrderReceiver: FocusOrder.() -> Unit): Modifier {
    return this.then(
        FocusOrderModifierImpl(
            focusOrderReceiver = focusOrderReceiver,
            inspectorInfo = debugInspectorInfo {
                name = "focusOrder"
                properties["focusOrderReceiver"] = focusOrderReceiver
            }
        )
    )
}

/**
 * A modifier that lets you specify a [FocusRequester] for the current composable so that this
 * [focusRequester] can be used by another composable to specify a custom focus order.
 *
 * @sample androidx.compose.ui.samples.CustomFocusOrderSample
 */
fun Modifier.focusOrder(focusRequester: FocusRequester): Modifier = focusRequester(focusRequester)

/**
 * A modifier that lets you specify a [FocusRequester] for the current composable along with
 * [focusOrder].
 *
 * @sample androidx.compose.ui.samples.CustomFocusOrderSample
 */
fun Modifier.focusOrder(
    focusRequester: FocusRequester,
    focusOrderReceiver: FocusOrder.() -> Unit
): Modifier = this
    .focusRequester(focusRequester)
    .focusOrder(focusOrderReceiver)