Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt focus interop code to Compose 1.7 changes #1398

Merged
merged 5 commits into from
Jun 10, 2024

Conversation

igordmn
Copy link
Collaborator

@igordmn igordmn commented Jun 10, 2024

After https://android-review.googlesource.com/c/platform/frameworks/support/+/2813125, there are some changes that affected our code for focus interop with ComposePanel/SwingPanel:

  • the root node no longer focusable
  • FocusOwnerImpl now has iterop callbacks and our own modifications of this class (in jb-main, integration was reset) doesn't work anymore

Testing

  • added new tests
  • ComposeFocusTest now pass

Release notes

Fixes - Multiple platforms

  • Fix "ComposePanel. Focus moves to child after focusing/unfocusing the main window"

FocusEvent.Cause.UNKNOWN, FocusEvent.Cause.ACTIVATION -> {
focusManager.moveFocus(FocusDirection.Enter)
if (!focusManager.hasFocus) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This particular check fixes JetBrains/compose-multiplatform#4919

private val backwardRequester = FocusRequester()
private val forwardRequester = FocusRequester()
private var isRequesting = false
private val backwardTracker = FocusTracker {
Copy link
Collaborator Author

@igordmn igordmn Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New features of FocusSwitcher:

  • simple refactoring
  • avoiding recursive calls (can happen with empty panels with the new code from the upstream)

@igordmn igordmn force-pushed the igor.demin/integration-fix-focus branch from 6c6f035 to f003a4a Compare June 10, 2024 03:46
when (e.cause) {
FocusEvent.Cause.TRAVERSAL_FORWARD -> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is moved to ComposeSceneMediator, as it is valid for all containers using it

@@ -462,59 +467,4 @@ class ComposePanelTest {
window.dispose()
}
}

@Test
fun `requestFocus assigns focus to first focusable element`() = runApplicationTest {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this test to another test, and adapted to common logic there

@igordmn igordmn force-pushed the igor.demin/integration-fix-focus branch from f003a4a to bd67fde Compare June 10, 2024 04:17
FocusEvent.Cause.UNKNOWN, FocusEvent.Cause.ACTIVATION -> {
focusManager.moveFocus(FocusDirection.Enter)
if (!focusManager.hasFocus) {
focusManager.takeFocus(FocusDirection.Next)
Copy link
Collaborator Author

@igordmn igordmn Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FocusDirection.Enter doesn't work anymore (probably a commonMain bug), but there is no semantic difference with Next here

Fixes JetBrains/compose-multiplatform#2944
Fixes JetBrains/compose-multiplatform#4919
Fixes https://youtrack.jetbrains.com/issue/COMPOSE-1212/Integration.-Check-changes-in-FocusOwnerImpl

After https://android-review.googlesource.com/c/platform/frameworks/support/+/2813125, there are some changes that affected our code for focus interop with ComposePanel/SwingPanel:
- the root node no longer focusable
- FocusOwnerImpl now has iterop callbacks and our own modification of this file (in jb-main, integration was reset) doesn't work anymore

## Testing
- added new tests
- ComposeFocusTest now pass

## Release notes
### Fixes - Multiple platforms
- Fix "ComposePanel. Focus moves to child after focusing/unfocusing the main window"
@igordmn igordmn force-pushed the igor.demin/integration-fix-focus branch from bd67fde to 942bbfc Compare June 10, 2024 04:21
@@ -194,67 +207,77 @@ private class FocusSwitcher<T : Component>(
private val interopComponent: SwingInteropComponent<T>,
private val focusManager: FocusManager,
) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to extract this class into separate file (with name something like SwingInteropFocusSwitcher) at some point. Not a blocker

Copy link
Collaborator Author

@igordmn igordmn Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't move it out of this file, until the file size is manageable (<1000 lines). Because this logic is strictly bound to SwingPanel.

A thing I would extract is FocusTracker - it might be useful for other platforms. But only when it is needed - there is a chance that other implementations should use the Android way.

@@ -156,6 +156,19 @@ public fun <T : Component> SwingPanel(
}
}

/**
* true, if the event is handled by SwingPanel.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Returns whether the event is handled by SwingPanel"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// In that case, SwingPanel will take care of it.
if (!isParentOf(e.oppositeComponent)) {
focusManager.requestFocus()
if (!e.isTemporary && !e.isFocusGainedHandledBySwingPanel(this@ComposePanel)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit confusing that this is called from ComposePanel (the function is about "SwingPanel"). Is this correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is ugly, but this was the fastest way to implement SwingPanel back in the days. I didn't change the logic, just added a name. There is no other purpose besides using for SwingPanel.

A good alternative exists, but it needs investigation. I described it in the KDoc of this function.

private val rootModifier = EmptySemanticsElement(rootSemanticsNode)
.then(Modifier.focusProperties {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The then here could be omitted.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@igordmn igordmn merged commit a696ac0 into integration Jun 10, 2024
3 of 8 checks passed
@igordmn igordmn deleted the igor.demin/integration-fix-focus branch June 10, 2024 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants