-
Notifications
You must be signed in to change notification settings - Fork 81
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
Fix SwingPanel focus interop #1821
Conversation
Fixes https://youtrack.jetbrains.com/issue/CMP-7472/Fix-Swing-interop-focus-after-1.8.0-alpha08-merge The failing tests were a regression after https://android-review.googlesource.com/c/platform/frameworks/support/+/3417182 that added requesting focus on the owner at the end of a focus transaction: ``` if (ComposeUiFlags.isViewFocusFixEnabled && requireLayoutNode().getInteropView() == null) { // This isn't an AndroidView, so we should be focused on this ComposeView requireOwner().focusOwner.requestFocusForOwner(FocusDirection.Next, null) } ``` As we can see here, there is an exception if the node has `SwingPanel` (interop view), because when we focus on this node, it will redirect the focus to the panel itself. But this exception didn't work on desktop because we had 2 invisible nodes around `SwingPanel`: ``` backwardTrackerModifier // redirects focus to the component, but then it will be overridden by `requestFocusForOwner` interopViewModifier // getInteropView() != null Swing component forwardTrackerModifier ``` Now the logic is rewritten similar to Android inside [Modifier.focusInteropModifier]: ``` parentModifier // fake node to intercept `onEnter` to know the focus direction childModifier // redirects focus to the component, skip `requestFocusForOwner` interopViewModifier // getInteropView() != null Swing component ``` The recursive focus check inside `Tracker` is removed, because it is not possible to have recursive calls if we don't request focus inside `onFocusEvent` # Testing `ComposeFocusTest` passes, it covers all possible edge cases of focus interop # Release Notes N/A
update = { | ||
it.background = background.toAwtColor() | ||
update(it) | ||
} | ||
) | ||
|
||
EmptyLayout(focusSwitcher.forwardTrackerModifier) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glad to see that it's finally gone. As far as I remember it blocked some unification between platforms before. Please add some TODO for me - I'll check that refactor later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ComposeUiFlags.isViewFocusFixEnabled
is public common flag. Does it work in apps when manually disabled?
Just checked, when we disable it - the tests are passed with this PR (and without the PR) |
FocusDirection.Previous -> { | ||
focusPolicy.getLastComponent(group)?.requestFocus(TRAVERSAL_BACKWARD) | ||
} | ||
else -> null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
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 | ||
import java.awt.event.FocusEvent.Cause.TRAVERSAL_BACKWARD | ||
import java.awt.event.FocusEvent.Cause.TRAVERSAL_FORWARD | ||
|
||
internal class InteropFocusSwitcher( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a few lines about what this does and how it does it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
val modifier = Modifier | ||
.then(parentModifier) | ||
.then(childModifier) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the split to two modifiers just for ease-of-reading/cleanliness? It seems the only place the two are used is right here, so there's no technical need to have two.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, only for ease-of-reading. If we don't split, it is not obvious why we used focusTarget
2 times.
The current script contains JetBrains/compose-multiplatform-core#1821 because it started with #, not with ## # Testing ``` kotlin changelog.main.kts v1.8.0-alpha02..v1.8.0+dev2047 ``` Doesn't contain JetBrains/compose-multiplatform-core#1821 # Release Notes N/A
The current script contains JetBrains/compose-multiplatform-core#1821 because it started with #, not with ## # Testing ``` kotlin changelog.main.kts v1.8.0-alpha02..v1.8.0+dev2047 ``` Doesn't contain JetBrains/compose-multiplatform-core#1821 # Release Notes N/A
The current script contains JetBrains/compose-multiplatform-core#1821 because it started with #, not with ## # Testing ``` kotlin changelog.main.kts v1.8.0-alpha02..v1.8.0+dev2047 ``` Doesn't contain JetBrains/compose-multiplatform-core#1821 # Release Notes N/A
Fixes https://youtrack.jetbrains.com/issue/CMP-7472/Fix-Swing-interop-focus-after-1.8.0-alpha08-merge
The failing tests were a regression after https://android-review.googlesource.com/c/platform/frameworks/support/+/3417182 that added requesting focus on the owner at the end of a focus transaction:
As we can see here, there is an exception case if the node has
SwingPanel
(interop view), because when we focus on this node, it will redirect the focus to the panel itself.But this exception case didn't work on desktop because we had 2 invisible nodes around
SwingPanel
:Now the logic is rewritten similar to Android inside Modifier.focusInteropModifier:
Also, the recursive focus check inside
Tracker
is removed, because it is not possible to have recursive calls if we don't request focus insideonFocusEvent
Testing
ComposeFocusTest
passes, it covers all possible edge cases of focus interopRelease Notes
N/A