FocusProperties.kt
/*
* Copyright 2021 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.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.modifier.ModifierLocalConsumer
import androidx.compose.ui.modifier.ModifierLocalProvider
import androidx.compose.ui.modifier.ModifierLocalReadScope
import androidx.compose.ui.modifier.modifierLocalOf
import androidx.compose.ui.node.ModifiedFocusNode
import androidx.compose.ui.platform.debugInspectorInfo
/**
* A Modifier local that stores [FocusProperties] for a sub-hierarchy.
*
* @see [focusProperties]
*/
internal val ModifierLocalFocusProperties = modifierLocalOf { defaultFocusProperties }
internal val defaultFocusProperties: FocusProperties = FocusPropertiesImpl(canFocus = true)
/**
* Properties that are applied to [focusTarget]s that can read the [ModifierLocalFocusProperties]
* Modifier Local.
*
* @see [focusProperties]
*/
interface FocusProperties {
/**
* When set to false, indicates that the [focusTarget] that this is applied to can no longer
* take focus. If the [focusTarget] is currently focused, setting this property to false will
* end up clearing focus.
*/
var canFocus: Boolean
}
/**
* This modifier allows you to specify properties that are accessible to [focusTarget]s further
* down the modifier chain or on child layout nodes.
*
* @sample androidx.compose.ui.samples.FocusPropertiesSample
*/
fun Modifier.focusProperties(scope: FocusProperties.() -> Unit): Modifier = composed(
debugInspectorInfo {
name = "focusProperties"
properties["scope"] = scope
}
) {
val rememberedScope by rememberUpdatedState(scope)
remember { FocusPropertiesModifier(focusPropertiesScope = rememberedScope) }
}
internal class FocusPropertiesModifier(
val focusPropertiesScope: FocusProperties.() -> Unit
) : ModifierLocalConsumer, ModifierLocalProvider<FocusProperties> {
private var parentFocusProperties: FocusProperties? = null
override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
parentFocusProperties = scope.run { ModifierLocalFocusProperties.current }
}
override val key = ModifierLocalFocusProperties
override val value: FocusProperties
get() = defaultFocusProperties.copy {
// Populate with the specified focus properties.
apply(focusPropertiesScope)
// current value for deactivated can be overridden by a parent's value.
parentFocusProperties?.let {
if (it != defaultFocusProperties) {
canFocus = it.canFocus
}
}
}
}
internal fun ModifiedFocusNode.setUpdatedProperties(properties: FocusProperties) {
if (properties.canFocus) activateNode() else deactivateNode()
}
private class FocusPropertiesImpl(override var canFocus: Boolean) : FocusProperties
private fun FocusProperties.copy(scope: FocusProperties.() -> Unit): FocusProperties {
return FocusPropertiesImpl(canFocus = canFocus).apply(scope)
}