1 /* <lambda>null2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.compose.ui.focus 18 19 import androidx.compose.runtime.Stable 20 import androidx.compose.runtime.annotation.RememberInComposition 21 import androidx.compose.runtime.collection.MutableVector 22 import androidx.compose.runtime.collection.mutableVectorOf 23 import androidx.compose.ui.focus.FocusDirection.Companion.Enter 24 import androidx.compose.ui.node.Nodes 25 import androidx.compose.ui.node.visitChildren 26 27 private const val FocusRequesterNotInitialized = 28 """ 29 FocusRequester is not initialized. Here are some possible fixes: 30 31 1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() } 32 2. Did you forget to add a Modifier.focusRequester() ? 33 3. Are you attempting to request focus during composition? Focus requests should be made in 34 response to some event. Eg Modifier.clickable { focusRequester.requestFocus() } 35 """ 36 37 private const val InvalidFocusRequesterInvocation = 38 """ 39 Please check whether the focusRequester is FocusRequester.Cancel or FocusRequester.Default 40 before invoking any functions on the focusRequester. 41 """ 42 43 /** 44 * The [FocusRequester] is used in conjunction with 45 * [Modifier.focusRequester][androidx.compose.ui.focus.focusRequester] to send requests to change 46 * focus. 47 * 48 * @sample androidx.compose.ui.samples.RequestFocusSample 49 * @see androidx.compose.ui.focus.focusRequester 50 */ 51 @Stable 52 class FocusRequester @RememberInComposition constructor() { 53 54 internal val focusRequesterNodes: MutableVector<FocusRequesterModifierNode> = mutableVectorOf() 55 56 /** 57 * Use this function to request focus. If the system grants focus to a component associated with 58 * this [FocusRequester], its [onFocusChanged] modifiers will receive a [FocusState] object 59 * where [FocusState.isFocused] is true. 60 * 61 * @sample androidx.compose.ui.samples.RequestFocusSample 62 */ 63 @Deprecated( 64 message = "use the version the has a FocusDirection", 65 replaceWith = ReplaceWith("this.requestFocus()"), 66 level = DeprecationLevel.HIDDEN 67 ) 68 fun requestFocus() { 69 requestFocus(Enter) 70 } 71 72 /** 73 * Use this function to request focus with a specific direction. If the system grants focus to a 74 * component associated with this [FocusRequester], its [onFocusChanged] modifiers will receive 75 * a [FocusState] object where [FocusState.isFocused] is true. 76 * 77 * @param focusDirection The direction passed to the [FocusTargetModifierNode] to indicate the 78 * direction that the focus request comes from. 79 * @return `true` if the focus was successfully requested or `false` if the focus request was 80 * canceled. 81 * @sample androidx.compose.ui.samples.RequestFocusSample 82 */ 83 fun requestFocus(focusDirection: FocusDirection = Enter): Boolean = findFocusTargetNode { 84 it.requestFocus(focusDirection) 85 } 86 87 internal fun findFocusTargetNode(onFound: (FocusTargetNode) -> Boolean): Boolean { 88 return findFocusTarget { focusTarget -> 89 if (focusTarget.fetchFocusProperties().canFocus) { 90 onFound(focusTarget) 91 } else { 92 focusTarget.findChildCorrespondingToFocusEnter(Enter, onFound) 93 } 94 } 95 } 96 97 /** 98 * Deny requests to clear focus. 99 * 100 * Use this function to send a request to capture focus. If a component captures focus, it will 101 * send a [FocusState] object to its associated [onFocusChanged] modifiers where 102 * [FocusState.isCaptured]() == true. 103 * 104 * When a component is in a Captured state, all focus requests from other components are 105 * declined. 106 * 107 * @return true if the focus was successfully captured by one of the [focus][focusTarget] 108 * modifiers associated with this [FocusRequester]. False otherwise. 109 * @sample androidx.compose.ui.samples.CaptureFocusSample 110 */ 111 fun captureFocus(): Boolean { 112 if (focusRequesterNodes.isEmpty()) { 113 println("$FocusWarning: $FocusRequesterNotInitialized") 114 return false 115 } 116 focusRequesterNodes.forEach { 117 if (it.captureFocus()) { 118 return true 119 } 120 } 121 return false 122 } 123 124 /** 125 * Use this function to send a request to free focus when one of the components associated with 126 * this [FocusRequester] is in a Captured state. If a component frees focus, it will send a 127 * [FocusState] object to its associated [onFocusChanged] modifiers where 128 * [FocusState.isCaptured]() == false. 129 * 130 * When a component is in a Captured state, all focus requests from other components are 131 * declined. . 132 * 133 * @return true if the captured focus was successfully released. i.e. At the end of this 134 * operation, one of the components associated with this [focusRequester] freed focus. 135 * @sample androidx.compose.ui.samples.CaptureFocusSample 136 */ 137 fun freeFocus(): Boolean { 138 if (focusRequesterNodes.isEmpty()) { 139 println("$FocusWarning: $FocusRequesterNotInitialized") 140 return false 141 } 142 focusRequesterNodes.forEach { 143 if (it.freeFocus()) { 144 return true 145 } 146 } 147 return false 148 } 149 150 /** 151 * Use this function to request the focus target to save a reference to the currently focused 152 * child in its saved instance state. After calling this, focus can be restored to the saved 153 * child by making a call to [restoreFocusedChild]. 154 * 155 * @return true if the focus target associated with this [FocusRequester] has a focused child 156 * and we successfully saved a reference to it. 157 * @sample androidx.compose.ui.samples.RestoreFocusSample 158 */ 159 fun saveFocusedChild(): Boolean { 160 if (focusRequesterNodes.isEmpty()) { 161 println("$FocusWarning: $FocusRequesterNotInitialized") 162 return false 163 } 164 focusRequesterNodes.forEach { if (it.saveFocusedChild()) return true } 165 return false 166 } 167 168 /** 169 * Use this function to restore focus to one of the children of the node pointed to by this 170 * [FocusRequester]. This restores focus to a previously focused child that was saved by using 171 * [saveFocusedChild]. 172 * 173 * @return true if we successfully restored focus to one of the children of the [focusTarget] 174 * associated with this [FocusRequester] 175 * @sample androidx.compose.ui.samples.RestoreFocusSample 176 */ 177 fun restoreFocusedChild(): Boolean { 178 if (focusRequesterNodes.isEmpty()) { 179 println("$FocusWarning: $FocusRequesterNotInitialized") 180 return false 181 } 182 var success = false 183 focusRequesterNodes.forEach { success = it.restoreFocusedChild() || success } 184 return success 185 } 186 187 companion object { 188 /** 189 * Default [focusRequester], which when used in [Modifier.focusProperties][focusProperties] 190 * implies that we want to use the default system focus order, that is based on the position 191 * of the items on the screen. 192 */ 193 val Default = FocusRequester() 194 195 /** 196 * Cancelled [focusRequester], which when used in 197 * [Modifier.focusProperties][focusProperties] implies that we want to block focus search 198 * from proceeding in the specified [direction][FocusDirection]. 199 * 200 * @sample androidx.compose.ui.samples.CancelFocusMoveSample 201 */ 202 val Cancel = FocusRequester() 203 204 /** Used to indicate that the focus has been redirected during an enter/exit lambda. */ 205 internal val Redirect = FocusRequester() 206 207 /** 208 * Convenient way to create multiple [FocusRequester] instances. 209 * 210 * @sample androidx.compose.ui.samples.CreateFocusRequesterRefsSample 211 */ 212 object FocusRequesterFactory { 213 operator fun component1() = FocusRequester() 214 215 operator fun component2() = FocusRequester() 216 217 operator fun component3() = FocusRequester() 218 219 operator fun component4() = FocusRequester() 220 221 operator fun component5() = FocusRequester() 222 223 operator fun component6() = FocusRequester() 224 225 operator fun component7() = FocusRequester() 226 227 operator fun component8() = FocusRequester() 228 229 operator fun component9() = FocusRequester() 230 231 operator fun component10() = FocusRequester() 232 233 operator fun component11() = FocusRequester() 234 235 operator fun component12() = FocusRequester() 236 237 operator fun component13() = FocusRequester() 238 239 operator fun component14() = FocusRequester() 240 241 operator fun component15() = FocusRequester() 242 243 operator fun component16() = FocusRequester() 244 } 245 246 /** 247 * Convenient way to create multiple [FocusRequester]s, which can to be used to request 248 * focus, or to specify a focus traversal order. 249 * 250 * @sample androidx.compose.ui.samples.CreateFocusRequesterRefsSample 251 */ 252 fun createRefs(): FocusRequesterFactory = FocusRequesterFactory 253 } 254 255 /** 256 * This function searches down the hierarchy and calls [onFound] for all focus nodes associated 257 * with this [FocusRequester]. 258 * 259 * @param onFound the callback that is run when the child is found. 260 * @return false if no focus nodes were found or if the FocusRequester is 261 * [FocusRequester.Cancel]. Returns null if the FocusRequester is [FocusRequester.Default]. 262 * Otherwise returns a logical or of the result of calling [onFound] for each focus node 263 * associated with this [FocusRequester]. 264 */ 265 private inline fun findFocusTarget(onFound: (FocusTargetNode) -> Boolean): Boolean { 266 check(this !== Default) { InvalidFocusRequesterInvocation } 267 check(this !== Cancel) { InvalidFocusRequesterInvocation } 268 if (focusRequesterNodes.isEmpty()) { 269 println("$FocusWarning: $FocusRequesterNotInitialized") 270 return false 271 } 272 var success = false 273 focusRequesterNodes.forEach { node -> 274 node.visitChildren(Nodes.FocusTarget) { 275 if (onFound(it)) { 276 success = true 277 return@forEach 278 } 279 } 280 } 281 return success 282 } 283 } 284