1 /*
2  * Copyright 2023 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 android.graphics.Rect
20 import android.view.FocusFinder
21 import android.view.View
22 import android.view.ViewGroup
23 import androidx.compose.ui.focus.FocusInteropUtils.Companion.tempCoordinates
24 import androidx.compose.ui.platform.AndroidComposeView
25 import androidx.compose.ui.unit.LayoutDirection
26 
27 private class FocusInteropUtils {
28     companion object {
29         val tempCoordinates = IntArray(2)
30     }
31 }
32 
33 /** Converts an android focus direction to a compose [focus direction][FocusDirection]. */
toFocusDirectionnull34 internal fun toFocusDirection(androidDirection: Int): FocusDirection? =
35     when (androidDirection) {
36         ViewGroup.FOCUS_UP -> FocusDirection.Up
37         ViewGroup.FOCUS_DOWN -> FocusDirection.Down
38         ViewGroup.FOCUS_LEFT -> FocusDirection.Left
39         ViewGroup.FOCUS_RIGHT -> FocusDirection.Right
40         ViewGroup.FOCUS_FORWARD -> FocusDirection.Next
41         ViewGroup.FOCUS_BACKWARD -> FocusDirection.Previous
42         else -> null
43     }
44 
45 /** Converts a compose [focus direction][FocusDirection] to an android focus direction. */
toAndroidFocusDirectionnull46 internal fun FocusDirection.toAndroidFocusDirection(): Int? =
47     when (this) {
48         FocusDirection.Up -> ViewGroup.FOCUS_UP
49         FocusDirection.Down -> ViewGroup.FOCUS_DOWN
50         FocusDirection.Left -> ViewGroup.FOCUS_LEFT
51         FocusDirection.Right -> ViewGroup.FOCUS_RIGHT
52         FocusDirection.Next -> ViewGroup.FOCUS_FORWARD
53         FocusDirection.Previous -> ViewGroup.FOCUS_BACKWARD
54         else -> null
55     }
56 
57 /** Convert an Android layout direction to a compose [layout direction][LayoutDirection]. */
toLayoutDirectionnull58 internal fun toLayoutDirection(androidLayoutDirection: Int): LayoutDirection? {
59     return when (androidLayoutDirection) {
60         android.util.LayoutDirection.LTR -> LayoutDirection.Ltr
61         android.util.LayoutDirection.RTL -> LayoutDirection.Rtl
62         else -> null
63     }
64 }
65 
66 /** Returns the bounding rect of the view in the current window. */
calculateBoundingRectRelativeTonull67 internal fun View.calculateBoundingRectRelativeTo(view: View): androidx.compose.ui.geometry.Rect {
68     getLocationInWindow(tempCoordinates)
69     val xInWindow = tempCoordinates[0]
70     val yInWindow = tempCoordinates[1]
71     view.getLocationInWindow(tempCoordinates)
72     val targetX = tempCoordinates[0]
73     val targetY = tempCoordinates[1]
74     val x = (xInWindow - targetX).toFloat()
75     val y = (yInWindow - targetY).toFloat()
76     return androidx.compose.ui.geometry.Rect(x, y, x + width, y + height)
77 }
78 
requestInteropFocusnull79 internal fun View.requestInteropFocus(direction: Int?, rect: Rect?): Boolean {
80     return when {
81         direction == null -> requestFocus()
82         this !is ViewGroup -> requestFocus(direction, rect)
83         isFocused -> true
84         isFocusable && !hasFocus() -> requestFocus(direction, rect)
85         this is AndroidComposeView -> requestFocus(direction, rect)
86         rect != null ->
87             FocusFinder.getInstance()
88                 .findNextFocusFromRect(this, rect, direction)
89                 ?.requestFocus(direction, rect) ?: requestFocus(direction, rect)
90         else -> {
91             val focusedView = if (hasFocus()) findFocus() else null
92             FocusFinder.getInstance()
93                 .findNextFocus(this, focusedView, direction)
94                 ?.requestFocus(direction) ?: requestFocus(direction)
95         }
96     }
97 }
98