forked from androidx/androidx
-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Revamp interop API to align it with Android and make it reusable (#1489)
Refactor interop API on iOS and Desktop to make it aligned with `AndroidView` - Make an `InteropViewHolder` a type responsible for configuring and emitting a `LayoutNode` associated with interop view. - Introduce a `TypedInteropViewHolder` that allows type-bound user-provided callbacks to run and be correctly updated in the scope of `InteropViewHolder`. - Introduce an `InteropView` composable function as an entry point for all interop implementations. - Merge `SwingInteropViewHolder` and `SwingInteropViewHolder2` and wire them with `TypedInteropViewHolder`. - Delete `InteropViewUpdater` and use interop container resident `SnapshotObserver` to track updates read by `InteropViewHolder`. - Change Swing update scheduling mechanics to batch them and then synchronously execute along with rendering sequence. - Rewire iOS implementation to use of `InteropView` with `UIKitInteropViewControllerHolder`, `UIKitInteropViewHolder`, and `UIKitInteropElementHolder` containing common parts of former two. - Tie `UIViewController` containment calls with `place`/`unplace`. ## Release notes ### iOS - Breaking changes - Actual of expected `InteropView` on iOS is `UIResponder` now instead of `UIView`. It's the first common ancestor for `UIViewController` and `UIView`, both of which can be integrated using iOS interop APIs. --------- Co-authored-by: Ivan Matkov <ivan.matkov@jetbrains.com>
- Loading branch information
1 parent
4ec5441
commit 3b7bd8d
Showing
23 changed files
with
1,374 additions
and
737 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/FocusSwitcher.desktop.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
* Copyright 2024 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.compose.ui.awt | ||
|
||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.focus.FocusDirection | ||
import androidx.compose.ui.focus.FocusManager | ||
import androidx.compose.ui.focus.FocusRequester | ||
import androidx.compose.ui.focus.focusRequester | ||
import androidx.compose.ui.focus.focusTarget | ||
import androidx.compose.ui.focus.onFocusEvent | ||
import androidx.compose.ui.viewinterop.InteropViewGroup | ||
import java.awt.event.FocusEvent | ||
|
||
internal class InteropFocusSwitcher( | ||
private val group: InteropViewGroup, | ||
private val focusManager: FocusManager, | ||
) { | ||
private val backwardTracker = Tracker { | ||
val component = group.focusTraversalPolicy.getFirstComponent(group) | ||
if (component != null) { | ||
component.requestFocus(FocusEvent.Cause.TRAVERSAL_FORWARD) | ||
} else { | ||
moveForward() | ||
} | ||
} | ||
|
||
private val forwardTracker = Tracker { | ||
val component = group.focusTraversalPolicy.getLastComponent(group) | ||
if (component != null) { | ||
component.requestFocus(FocusEvent.Cause.TRAVERSAL_BACKWARD) | ||
} else { | ||
moveBackward() | ||
} | ||
} | ||
|
||
val backwardTrackerModifier: Modifier | ||
get() = backwardTracker.modifier | ||
|
||
val forwardTrackerModifier: Modifier | ||
get() = forwardTracker.modifier | ||
|
||
fun moveBackward() { | ||
backwardTracker.requestFocusWithoutEvent() | ||
focusManager.moveFocus(FocusDirection.Previous) | ||
} | ||
|
||
fun moveForward() { | ||
forwardTracker.requestFocusWithoutEvent() | ||
focusManager.moveFocus(FocusDirection.Next) | ||
} | ||
|
||
/** | ||
* A helper class that can help: | ||
* - to prevent recursive focus events | ||
* (a case when we focus the same element inside `onFocusEvent`) | ||
* - to prevent triggering `onFocusEvent` while requesting focus somewhere else | ||
*/ | ||
private class Tracker( | ||
private val onNonRecursiveFocused: () -> Unit | ||
) { | ||
private val requester = FocusRequester() | ||
|
||
private var isRequestingFocus = false | ||
private var isHandlingFocus = false | ||
|
||
fun requestFocusWithoutEvent() { | ||
try { | ||
isRequestingFocus = true | ||
requester.requestFocus() | ||
} finally { | ||
isRequestingFocus = false | ||
} | ||
} | ||
|
||
val modifier = Modifier | ||
.focusRequester(requester) | ||
.onFocusEvent { | ||
if (!isRequestingFocus && !isHandlingFocus && it.isFocused) { | ||
try { | ||
isHandlingFocus = true | ||
onNonRecursiveFocused() | ||
} finally { | ||
isHandlingFocus = false | ||
} | ||
} | ||
} | ||
.focusTarget() | ||
} | ||
} |
Oops, something went wrong.