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