Skip to content

Commit 7ebb7dc

Browse files
committed
Fix Input methods on JBR, disable input methods when we lose focus
1. Fixes JetBrains/compose-multiplatform#2628 2. Doesn't show input methods popup if there is no focused TextField (only on Windows for now, on macOs, Swing seems has a bug: JetBrains/compose-multiplatform#3839) ## Testing Tested manually on Windows/macOs/Linux, OpenJDK/JBR, Accessibility enabled/disabled
1 parent 54577e1 commit 7ebb7dc

File tree

3 files changed

+35
-2
lines changed

3 files changed

+35
-2
lines changed

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeBridge.desktop.kt

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ import org.jetbrains.skiko.*
5454
* Provides a base implementation for integrating a Compose scene with AWT/Swing.
5555
* It allows setting Compose content by [setContent], this content should be drawn on [component].
5656
*
57+
* This bridge contain 2 components that should be added to the view hirarachy:
58+
* [component] the main visible Swing component, on which Compose will be shown
59+
* [invisibleComponent] service component used to bypass Swing issues:
60+
* - for forcing refocus on input methods change
61+
*
5762
* Inheritors should call [attachComposeToComponent], so events that came to [component] will be transferred to [ComposeScene]
5863
*/
5964
internal abstract class ComposeBridge(
@@ -66,7 +71,10 @@ internal abstract class ComposeBridge(
6671
mainOwnerProvider = { scene.mainOwner }
6772
)
6873

74+
private val _invisibleComponent = InvisibleComponent()
75+
6976
abstract val component: JComponent
77+
val invisibleComponent: Component get() = _invisibleComponent
7078

7179
abstract val renderApi: GraphicsApi
7280

@@ -86,16 +94,30 @@ internal abstract class ComposeBridge(
8694

8795
private var window: Window? = null
8896

97+
private fun refocus() {
98+
if (component.isFocusOwner) {
99+
_invisibleComponent.requestFocusTemporary()
100+
component.requestFocus()
101+
}
102+
}
103+
89104
private val platformComponent: PlatformComponent = object : PlatformComponent {
90105
override fun enableInput(inputMethodRequests: InputMethodRequests) {
91106
currentInputMethodRequests = inputMethodRequests
92107
component.enableInputMethods(true)
93-
val focusGainedEvent = FocusEvent(focusComponentDelegate, FocusEvent.FOCUS_GAINED)
94-
component.inputContext.dispatchEvent(focusGainedEvent)
108+
// Without resetting the focus, Swing won't update the status (doesn't show/hide popup)
109+
// enableInputMethods is design to used per-Swing component level at init stage,
110+
// not dynamically
111+
refocus()
95112
}
96113

97114
override fun disableInput() {
98115
currentInputMethodRequests = null
116+
component.enableInputMethods(false)
117+
// Without resetting the focus, Swing won't update the status (doesn't show/hide popup)
118+
// enableInputMethods is design to used per-Swing component level at init stage,
119+
// not dynamically
120+
refocus()
99121
}
100122

101123
override val locationOnScreen: Point
@@ -181,6 +203,7 @@ internal abstract class ComposeBridge(
181203

182204
@OptIn(ExperimentalComposeUiApi::class)
183205
protected fun attachComposeToComponent() {
206+
component.enableInputMethods(false)
184207
component.addInputMethodListener(object : InputMethodListener {
185208
override fun caretPositionChanged(event: InputMethodEvent?) {
186209
if (isDisposed) return
@@ -393,6 +416,12 @@ internal abstract class ComposeBridge(
393416
override val touchSlop: Float get() = with(platformComponent.density) { 18.dp.toPx() }
394417
}
395418
}
419+
420+
private class InvisibleComponent : Component() {
421+
fun requestFocusTemporary(): Boolean {
422+
return super.requestFocus(true)
423+
}
424+
}
396425
}
397426

398427
private fun ComposeScene.onMouseEvent(

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposePanel.desktop.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class ComposePanel @ExperimentalComposeUiApi constructor(
113113
if (bridge != null) {
114114
bridge!!.dispose()
115115
super.remove(bridge!!.component)
116+
super.remove(bridge!!.invisibleComponent)
116117
bridge = null
117118
}
118119
}
@@ -187,6 +188,7 @@ class ComposePanel @ExperimentalComposeUiApi constructor(
187188
if (bridge == null) {
188189
bridge = createComposeBridge()
189190
initContent()
191+
super.add(bridge!!.invisibleComponent, Integer.valueOf(1))
190192
super.add(bridge!!.component, Integer.valueOf(1))
191193
}
192194
}

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindowDelegate.desktop.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,13 @@ internal class ComposeWindowDelegate(
9393

9494
init {
9595
layout = null
96+
super.add(bridge.invisibleComponent, 1)
9697
super.add(bridge.component, 1)
9798
}
9899

99100
fun dispose() {
100101
super.remove(bridge.component)
102+
super.remove(bridge.invisibleComponent)
101103
}
102104
}
103105

0 commit comments

Comments
 (0)