17
17
package androidx.compose.ui.platform
18
18
19
19
import androidx.compose.ui.geometry.Rect
20
+ import androidx.compose.ui.scene.ComposeSceneMediator
20
21
import androidx.compose.ui.text.TextRange
21
22
import androidx.compose.ui.text.input.ImeOptions
22
23
import androidx.compose.ui.text.input.PlatformTextInputService2
@@ -42,11 +43,14 @@ internal class DesktopTextInputService2(
42
43
43
44
private var inputMethodSession: InputMethodSession ? = null
44
45
46
+ private var receivedInputMethodEventsSinceStartInput = false
47
+
45
48
override fun startInput (
46
49
state : TextEditorState ,
47
50
imeOptions : ImeOptions ,
48
51
editText : (block: TextEditingScope .() -> Unit ) -> Unit
49
52
) {
53
+ receivedInputMethodEventsSinceStartInput = false
50
54
component.enableInput(
51
55
InputMethodSession (component, state, editText).also {
52
56
inputMethodSession = it
@@ -74,11 +78,39 @@ internal class DesktopTextInputService2(
74
78
}
75
79
76
80
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()
82
114
}
83
115
}
84
116
0 commit comments