From 9e7dfc4cfcee8f60e31bc016f21fd09836033625 Mon Sep 17 00:00:00 2001 From: Alexander Maryanovsky Date: Thu, 9 May 2024 18:56:26 +0300 Subject: [PATCH] Fix ComposePanel.requestFocus --- .../compose/ui/awt/ComposePanel.desktop.kt | 5 +- .../compose/ui/awt/ComposePanelTest.kt | 62 ++++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposePanel.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposePanel.desktop.kt index 6ea597857be97..d726f2c01eba8 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposePanel.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposePanel.desktop.kt @@ -216,11 +216,12 @@ class ComposePanel @ExperimentalComposeUiApi constructor( FocusEvent.Cause.TRAVERSAL_FORWARD -> { focusManager.moveFocus(FocusDirection.Next) } - FocusEvent.Cause.TRAVERSAL_BACKWARD -> { focusManager.moveFocus(FocusDirection.Previous) } - + FocusEvent.Cause.UNKNOWN, FocusEvent.Cause.ACTIVATION -> { + focusManager.moveFocus(FocusDirection.Enter) + } else -> Unit } } diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposePanelTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposePanelTest.kt index dad5e503218ea..a9289d0299522 100644 --- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposePanelTest.kt +++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposePanelTest.kt @@ -15,7 +15,9 @@ */ package androidx.compose.ui.awt +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size @@ -29,6 +31,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.onPointerEvent @@ -36,7 +39,6 @@ import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.sendMouseEvent import androidx.compose.ui.unit.Constraints -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.toSize import androidx.compose.ui.util.ThrowUncaughtExceptionRule @@ -47,6 +49,7 @@ import java.awt.BorderLayout import java.awt.Dimension import java.awt.GraphicsEnvironment import java.awt.event.MouseEvent +import javax.swing.JButton import javax.swing.JFrame import javax.swing.JPanel import junit.framework.TestCase.assertTrue @@ -459,4 +462,59 @@ class ComposePanelTest { window.dispose() } } -} \ No newline at end of file + + @Test + fun `requestFocus assigns focus to first focusable element`() = runApplicationTest { + assumeFalse(GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadlessInstance) + + var focusedElement: String? = null + val composePanel = ComposePanel() + composePanel.setBounds(0, 25, 100, 100) + composePanel.setContent { + Column { + Box(Modifier + .size(10.dp) + .onFocusChanged { + focusedElement = if (it.isFocused) "first" else null + } + .focusable() + ) + Box(Modifier + .size(10.dp) + .onFocusChanged { + focusedElement = if (it.isFocused) "second" else null + } + .focusable() + ) + } + } + + val frame = JFrame() + try { + val button = JButton("Button") + frame.size = Dimension(500, 500) + frame.contentPane.add(button, BorderLayout.NORTH) + frame.contentPane.add(composePanel, BorderLayout.CENTER) + frame.isVisible = true + + assertEquals(null, focusedElement) + + // The first requestFocus sends a focusGained(Cause.ACTIVATION) event + composePanel.requestFocus() + awaitIdle() + assertEquals("first", focusedElement) + + // Switch focus back to Swing + button.requestFocus() + awaitIdle() + assertEquals(null, focusedElement) + + // The 2nd requestFocus sends a focusGained(Cause.UNKNOWN) event; we want to test both + composePanel.requestFocus() + awaitIdle() + assertEquals("first", focusedElement) + } finally { + frame.dispose() + } + } +}