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