Skip to content

Commit 82ccbd7

Browse files
committed
Ignore a commit composition event sent immediately after switching focus between two text fields.
1 parent 58e0cf2 commit 82ccbd7

File tree

1 file changed

+37
-5
lines changed

1 file changed

+37
-5
lines changed

Diff for: compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopTextInputService2.kt

+37-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package androidx.compose.ui.platform
1818

1919
import androidx.compose.ui.geometry.Rect
20+
import androidx.compose.ui.scene.ComposeSceneMediator
2021
import androidx.compose.ui.text.TextRange
2122
import androidx.compose.ui.text.input.ImeOptions
2223
import androidx.compose.ui.text.input.PlatformTextInputService2
@@ -42,11 +43,14 @@ internal class DesktopTextInputService2(
4243

4344
private var inputMethodSession: InputMethodSession? = null
4445

46+
private var receivedInputMethodEventsSinceStartInput = false
47+
4548
override fun startInput(
4649
state: TextEditorState,
4750
imeOptions: ImeOptions,
4851
editText: (block: TextEditingScope.() -> Unit) -> Unit
4952
) {
53+
receivedInputMethodEventsSinceStartInput = false
5054
component.enableInput(
5155
InputMethodSession(component, state, editText).also {
5256
inputMethodSession = it
@@ -74,11 +78,39 @@ internal class DesktopTextInputService2(
7478
}
7579

7680
fun inputMethodTextChanged(event: InputMethodEvent) {
77-
val inputMethodRequests = inputMethodSession ?: return
78-
if (!event.isConsumed) {
79-
inputMethodRequests.replaceInputMethodText(event)
80-
event.consume()
81-
}
81+
if (event.isConsumed) return
82+
val inputMethodSession = inputMethodSession ?: return
83+
84+
if (commitEventWorkaroundShouldIgnoreEvent(event)) return
85+
86+
inputMethodSession.replaceInputMethodText(event)
87+
event.consume()
88+
}
89+
90+
/**
91+
* Implements a workaround for https://youtrack.jetbrains.com/issue/CMP-7976; returns whether
92+
* the given event should be ignored.
93+
*
94+
* JBR sends an extra [InputMethodEvent] when focus moves away from a text field. This event
95+
* means to commit the current composition. Unfortunately, because we use a single actual Swing
96+
* component (see [ComposeSceneMediator]) as a source of [InputMethodEvent]s, this event gets
97+
* delivered to a new text session if the focus switches away to another text field.
98+
*
99+
* Regardless, Compose text fields commit their composition on focus loss (but not window focus
100+
* loss) themselves, so we don't need this event.
101+
*/
102+
private fun commitEventWorkaroundShouldIgnoreEvent(event: InputMethodEvent): Boolean {
103+
val isFirstEventAfterStartInput = !receivedInputMethodEventsSinceStartInput
104+
receivedInputMethodEventsSinceStartInput = true
105+
106+
// Note that we need to handle two cases:
107+
// - Focus moves between Compose text fields.
108+
// - Focus moves from a Swing text component into a Compose text field
109+
// The 2nd case is why we can't have a more surgical check here, i.e. check that the
110+
// previous composition matches the committed text.
111+
// But this check is hopefully good enough; the IME should not be asking to immediately
112+
// commit something without putting it into a composition first.
113+
return isFirstEventAfterStartInput && event.committedText.isNotEmpty()
82114
}
83115
}
84116

0 commit comments

Comments
 (0)