[go: nahoru, domu]

blob: b6e53fbc61d39a47650670604afbd48feda20f3e [file] [log] [blame]
/*
* 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.ui.core.focus
import androidx.ui.core.DelegatingLayoutNodeWrapper
import androidx.ui.core.ExperimentalLayoutNodeApi
import androidx.ui.core.LayoutNodeWrapper
import androidx.ui.core.focus.FocusDetailedState.Active
import androidx.ui.core.focus.FocusDetailedState.ActiveParent
import androidx.ui.core.focus.FocusDetailedState.Captured
import androidx.ui.core.focus.FocusDetailedState.Disabled
import androidx.ui.core.focus.FocusDetailedState.Inactive
import androidx.ui.util.fastForEach
@OptIn(ExperimentalLayoutNodeApi::class)
internal class ModifiedFocusNode(
wrapped: LayoutNodeWrapper,
modifier: FocusModifierImpl
) : DelegatingLayoutNodeWrapper<FocusModifierImpl>(wrapped, modifier) {
/**
* Request focus for this node.
*
* @param propagateFocus Whether the focus should be propagated to the node's children.
*
* In Compose, the parent [FocusNode][ModifiedFocusNode] controls focus for its focusable
* children. Calling this function will send a focus request to this
* [FocusNode][ModifiedFocusNode]'s parent [FocusNode][ModifiedFocusNode].
*/
fun requestFocus(propagateFocus: Boolean = true) {
when (modifier.focusDetailedState) {
Active, Captured, Disabled -> return
ActiveParent -> {
val focusedChild = modifier.focusedChild
requireNotNull(focusedChild)
/** We don't need to do anything if [propagateFocus] is true,
since this subtree already has focus.*/
if (propagateFocus) return
if (focusedChild.clearFocus()) {
grantFocus(propagateFocus)
}
}
Inactive -> {
val focusParent = findParentFocusNode()
if (focusParent == null) {
if (requestFocusForOwner()) {
grantFocus(propagateFocus)
}
} else {
focusParent.requestFocusForChild(this, propagateFocus)
}
}
}
}
/**
* Deny requests to clear focus.
*
* This is used when a component wants to hold onto focus (eg. A phone number field with an
* invalid number.
*
* @return true if the focus was successfully captured. False otherwise.
*/
fun captureFocus() = when (modifier.focusDetailedState) {
Active, Captured -> {
modifier.focusDetailedState = Captured
true
}
else -> false
}
/**
* When the node is in the [Captured] state, it rejects all requests to clear focus. Calling
* [freeFocus] puts the node in the [Active] state, where it is no longer preventing other
* nodes from requesting focus.
*
* @return true if the captured focus was released. If the node is not in the [Captured]
* state. this function returns false to indicate that this operation was a no-op.
*/
fun freeFocus() = if (modifier.focusDetailedState == Captured) {
modifier.focusDetailedState = Active
true
} else {
false
}
/**
* This function grants focus to this node.
*
* @param propagateFocus Whether the focus should be propagated to the node's children.
*
* Note: This function is private, and should only be called by a parent [ModifiedFocusNode] to
* grant focus to one of its child [ModifiedFocusNode]s.
*/
private fun grantFocus(propagateFocus: Boolean) {
// TODO (b/144126570) use ChildFocusablility.
// For now we assume children get focus before parent).
// TODO (b/144126759): Design a system to decide which child get's focus.
// for now we grant focus to the first child.
val focusedCandidate = focusableChildren().firstOrNull()
if (focusedCandidate == null || !propagateFocus) {
// No Focused Children, or we don't want to propagate focus to children.
modifier.focusDetailedState = Active
} else {
modifier.focusDetailedState = ActiveParent
modifier.focusedChild = focusedCandidate
focusedCandidate.grantFocus(propagateFocus)
}
}
/**
* This function clears focus from this node.
*
* Note: This function should only be called by a parent [ModifiedFocusNode] to
* clear focus from one of its child [ModifiedFocusNode]s. It does not change the state of
* the parent.
*/
internal fun clearFocus(forcedClear: Boolean = false): Boolean {
return when (modifier.focusDetailedState) {
Active -> {
findParentFocusNode()?.modifier?.focusedChild = null
modifier.focusDetailedState = Inactive
true
}
/**
* If the node is [ActiveParent], we need to clear focus from the [Active] descendant
* first, before clearing focus of this node.
*/
ActiveParent -> {
val focusedChild = modifier.focusedChild
requireNotNull(focusedChild)
val success = focusedChild.clearFocus(forcedClear)
if (success) {
findParentFocusNode()?.modifier?.focusedChild = null
modifier.focusDetailedState = Inactive
}
success
}
/**
* If the node is [Captured], deny requests to clear focus, except for a forced clear.
*/
Captured -> {
if (forcedClear) {
modifier.focusDetailedState = Inactive
findParentFocusNode()?.modifier?.focusedChild = null
}
forcedClear
}
/**
* Nothing to do if the node is not focused.
*/
Inactive, Disabled -> true
}
}
/**
* Focusable children of this [ModifiedFocusNode] can use this function to request focus.
*
* @param childNode: The node that is requesting focus.
* @param propagateFocus Whether the focus should be propagated to the node's children.
* @return true if focus was granted, false otherwise.
*/
private fun requestFocusForChild(
childNode: ModifiedFocusNode,
propagateFocus: Boolean
): Boolean {
// Only this node's children can ask for focus.
if (!focusableChildren().contains(childNode)) {
error("Non child node cannot request focus.")
}
return when (modifier.focusDetailedState) {
/**
* If this node is [Active], it can give focus to the requesting child.
*/
Active -> {
modifier.focusDetailedState = ActiveParent
modifier.focusedChild = childNode
childNode.grantFocus(propagateFocus)
true
}
/**
* If this node is [ActiveParent] ie, one of the parent's descendants is [Active],
* remove focus from the currently focused child and grant it to the requesting child.
*/
ActiveParent -> {
val previouslyFocusedNode = modifier.focusedChild
requireNotNull(previouslyFocusedNode)
if (previouslyFocusedNode.clearFocus()) {
modifier.focusedChild = childNode
childNode.grantFocus(propagateFocus)
true
} else {
// Currently focused component does not want to give up focus.
false
}
}
/**
* If this node is not [Active], we must gain focus first before granting it
* to the requesting child.
*/
Inactive -> {
val focusParent = findParentFocusNode()
if (focusParent == null) {
// If the owner successfully gains focus, proceed otherwise return false.
if (requestFocusForOwner()) {
modifier.focusDetailedState = Active
requestFocusForChild(childNode, propagateFocus)
} else {
false
}
} else if (focusParent.requestFocusForChild(this, propagateFocus = false)) {
requestFocusForChild(childNode, propagateFocus)
} else {
// Could not gain focus, so have no focus to give.
false
}
}
/**
* If this node is [Captured], decline requests from the children.
*/
Captured -> false
/**
* Children of a [Disabled] parent should also be [Disabled].
*/
Disabled -> error("non root FocusNode needs a focusable parent")
}
}
private fun requestFocusForOwner(): Boolean {
val owner = layoutNode.owner
requireNotNull(owner, { "Owner not initialized." })
return owner.requestFocus()
}
override fun findPreviousFocusWrapper() = this
override fun findNextFocusWrapper() = this
// TODO(b/152051577): Measure the performance of focusableChildren.
// Consider caching the children.
internal fun focusableChildren(): List<ModifiedFocusNode> {
// Check the modifier chain that this focus node is part of. If it has a focus modifier,
// that means you have found the only focusable child for this node.
val focusableChild = wrapped.findNextFocusWrapper()
// findChildFocusNodeInWrapperChain()
if (focusableChild != null) {
return listOf(focusableChild)
}
// Go through all your children and find the first focusable node from each child.
val focusableChildren = mutableListOf<ModifiedFocusNode>()
layoutNode.children.fastForEach { node ->
focusableChildren.addAll(node.focusableChildren())
}
return focusableChildren
}
internal fun findActiveFocusNode(): ModifiedFocusNode? {
return when (modifier.focusDetailedState) {
Active, Captured -> this
ActiveParent -> modifier.focusedChild?.findActiveFocusNode()
Inactive, Disabled -> null
}
}
}