Skip to content

Commit b7a1b4b

Browse files
igordmnmazunin-v-jb
authored andcommitted
Desktop. Fix input methods on JBR, disable input methods when we lose focus (#881)
### Rerequest focus on main component when we need to type using input methods Fixes JetBrains/compose-multiplatform#2628 The issue was because of 2 things: - we used a hack to force a focused event (`component.inputContext.dispatchEvent(focusGainedEvent)` - JBR added optimization to ignore focus on the same element (thanks @AiHMin for investigation [here](JetBrains/compose-multiplatform#2628 (comment))). In Compose we have only one element. Because optimization is correct, and the hack depends on the internals, it isn't right to fix it in JBR, we should fix it in Compose. Furthermore, even without JBR changes, this hack didn't complete work - we can't for example use it for disabling input methods (see the next point). In this PR we also use a hack unfortenutely - we refocus the root component, focusing on invisible component first. That leads to another issue with acccessibility, but we fix it [here](#885)). A proper fix should be switching to native code, or making an API in JBR (but other vendors still be unsupported). ### Don't show input methods popup if there is no focused TextField Previously we showed a popup, even if there are no focused textfield: ![image](https://github.com/JetBrains/compose-multiplatform-core/assets/5963351/82e3543f-11f4-4013-8da5-d782b824ed2c) Now we don't show it if we isn't in a textfield. P.S. only on Windows/Linux for now, on macOs Swing seems has [a bug](JetBrains/compose-multiplatform#3839) ## Testing Tested manually on: 1. Windows, Chinese/Korean/Japanese, OpenJDK/JBR 17, Accessibility enabled/disabled, ComposeWindow/ComposePanel 2. macOs, Chinese, OpenJDK/JBR 17, Accessibility enabled/disabled 4. Linux, Chinese layout, OpenJDK 17
1 parent b652766 commit b7a1b4b

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
@@ -119,6 +119,7 @@ class ComposePanel @ExperimentalComposeUiApi constructor(
119119
if (bridge != null) {
120120
bridge!!.dispose()
121121
super.remove(bridge!!.component)
122+
super.remove(bridge!!.invisibleComponent)
122123
bridge = null
123124
}
124125
}
@@ -193,6 +194,7 @@ class ComposePanel @ExperimentalComposeUiApi constructor(
193194
if (bridge == null) {
194195
bridge = createComposeBridge()
195196
initContent()
197+
super.add(bridge!!.invisibleComponent, Integer.valueOf(1))
196198
super.add(bridge!!.component, Integer.valueOf(1))
197199
}
198200
}

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)