Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sending KeyEvents from IME #1297

Merged
merged 8 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
Expand Down Expand Up @@ -108,7 +109,7 @@ private fun TextBlock(
OutlinedTextField(
value = textState.value,
onValueChange = { textState.value = it },
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
modifier = Modifier.fillMaxWidth().padding(top = 8.dp).onKeyEvent { println("KEY EVENT $this"); false },
textStyle = TextStyle(fontSize = 12.sp, fontWeight = FontWeight.Normal),
keyboardOptions = KeyboardOptions(imeAction = imeActionName),
keyboardActions = keyboardActions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal fun KeyboardEvent.toComposeEvent(): KeyEvent {
nativeKeyEvent = InternalKeyEvent(
key = Key(keyCode.toLong(), location),
type = when (type) {
"keydown", "keypress" -> KeyEventType.KeyDown
"keydown" -> KeyEventType.KeyDown
"keyup" -> KeyEventType.KeyUp
else -> KeyEventType.Unknown
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,31 @@
package androidx.compose.ui.platform

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.toComposeEvent
import androidx.compose.ui.text.input.CommitTextCommand
import androidx.compose.ui.text.input.DeleteAllCommand
import androidx.compose.ui.text.input.EditCommand
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.SetComposingTextCommand
import androidx.compose.ui.text.input.TextFieldValue
import kotlinx.browser.document
import org.w3c.dom.HTMLTextAreaElement
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.KeyboardEventInit

/**
* The purpose of this entity is to isolate synchronization between a TextFieldValue
* and the DOM HTMLTextAreaElement we are actually listening events on in order to show
* the virtual keyboard.
*/
* The purpose of this entity is to isolate synchronization between a TextFieldValue
* and the DOM HTMLTextAreaElement we are actually listening events on in order to show
* the virtual keyboard.
*/
internal class BackingTextArea(
private val imeOptions: ImeOptions,
private val onEditCommand: (List<EditCommand>) -> Unit,
private val onImeActionPerformed: (ImeAction) -> Unit
private val onImeActionPerformed: (ImeAction) -> Unit,
private val sendKey: (evt: KeyEvent) -> Unit
) {
private val textArea: HTMLTextAreaElement = createHtmlInput()

Expand Down Expand Up @@ -95,62 +100,58 @@ internal class BackingTextArea(
setProperty("text-shadow", "none")
}

htmlInput.addEventListener("input", {
val text = htmlInput.value
val cursorPosition = htmlInput.selectionEnd
sendImeValueToCompose(onEditCommand, text, cursorPosition)
htmlInput.addEventListener("input", { evt ->
evt.preventDefault()
evt as InputEventExtended

when (evt.inputType) {
"insertLineBreak" -> {
if (imeOptions.singleLine) {
onImeActionPerformed(imeOptions.imeAction)
}
}

"insertCompositionText" -> {
val data = evt.data ?: return@addEventListener
onEditCommand(listOf(SetComposingTextCommand(data, 1)))
}

"insertText" -> {
val data = evt.data ?: return@addEventListener
if (data.length == 1) {
sendKey(
KeyboardEvent(
"keydown", KeyboardEventInit(key = data)
).toComposeEvent()
)
} else if (data.length > 1) {
onEditCommand(listOf(CommitTextCommand(data, 1)))
}
}

"deleteContentBackward" -> {
sendKey(
KeyboardEvent(
"keydown",
KeyboardEventInit(key = "Backspace", code = "Backspace").withKeyCode(Key.Backspace)
).toComposeEvent()
)
}
}
})

htmlInput.addEventListener("contextmenu", { evt ->
evt.preventDefault()
evt.stopPropagation()
})

// this done by analogy with KeyCommand.NEW_LINE processing in TextFieldKeyInput
if (imeOptions.singleLine) {
htmlInput.addEventListener("keydown", { evt ->
evt.preventDefault()
evt as KeyboardEvent
if (evt.key == "Enter" && evt.type == "keydown") {
onImeActionPerformed(imeOptions.imeAction)
}
})
}

return htmlInput
}

fun register() {
document.body?.appendChild(textArea)
}

private fun sendImeValueToCompose(
onEditCommand: (List<EditCommand>) -> Unit,
text: String,
newCursorPosition: Int? = null
) {
val value = if (text == "\n") {
""
} else {
text
}

if (newCursorPosition != null) {
onEditCommand(
listOf(
DeleteAllCommand(),
CommitTextCommand(value, newCursorPosition),
)
)
} else {
onEditCommand(
listOf(
CommitTextCommand(value, 1)
)
)
}
}

fun focus() {
textArea.focus()
}
Expand All @@ -174,4 +175,20 @@ internal class BackingTextArea(
fun dispose() {
textArea.remove()
}
}
}

private external interface InputEventExtended {
val inputType: String
val data: String?
}

// TODO: reuse in tests
private external interface KeyboardEventInitExtended : KeyboardEventInit {
var keyCode: Int?
}

@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
private fun KeyboardEventInit.withKeyCode(key: Key) =
(this as KeyboardEventInitExtended).apply {
this.keyCode = key.keyCode.toInt()
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ internal class WebImeInputService(parentInputService: InputAwareInputService) :
onEditCommand: (List<EditCommand>) -> Unit,
onImeActionPerformed: (ImeAction) -> Unit
) {
backingTextArea = BackingTextArea(imeOptions, onEditCommand, onImeActionPerformed)
backingTextArea =
BackingTextArea(imeOptions, onEditCommand, onImeActionPerformed, sendKey = { evt ->
sendKeyEvent(evt)
})
backingTextArea?.register()

showSoftwareKeyboard()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package androidx.compose.ui.platform

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.text.input.EditCommand
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
Expand All @@ -27,6 +27,7 @@ import androidx.compose.ui.text.input.TextFieldValue

internal interface InputAwareInputService {
fun getOffset(rect: Rect): Offset
fun sendKeyEvent(event: KeyEvent)
fun isVirtualKeyboard(): Boolean
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.compose.ui.events.EventTargetListener
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.input.InputModeManager
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.toComposeEvent
import androidx.compose.ui.input.pointer.BrowserCursor
import androidx.compose.ui.input.pointer.PointerEventType
Expand Down Expand Up @@ -108,10 +109,8 @@ internal interface ComposeWindowState {
}

private sealed interface KeyboardModeState {
companion object {
object Virtual: KeyboardModeState
object Hardware: KeyboardModeState
}
object Virtual: KeyboardModeState
object Hardware: KeyboardModeState
}

internal class DefaultWindowState(private val viewportContainer: Element) : ComposeWindowState {
Expand Down Expand Up @@ -168,22 +167,26 @@ internal class ComposeWindow(

private val canvasEvents = EventTargetListener(canvas)

private var keyboardModeState: KeyboardModeState = KeyboardModeState.Companion.Hardware
private var keyboardModeState: KeyboardModeState = KeyboardModeState.Hardware

private val platformContext: PlatformContext = object : PlatformContext {
override val windowInfo get() = _windowInfo

override val inputModeManager: InputModeManager = DefaultInputModeManager()

override val textInputService = object : WebTextInputService() {
override fun isVirtualKeyboard() = keyboardModeState == KeyboardModeState.Companion.Virtual
override fun isVirtualKeyboard() = keyboardModeState == KeyboardModeState.Virtual

override fun getOffset(rect: Rect): Offset {
val viewportRect = canvas.getBoundingClientRect()
val offsetX = viewportRect.left.toFloat().coerceAtLeast(0f) + (rect.left / density.density)
val offsetY = viewportRect.top.toFloat().coerceAtLeast(0f) + (rect.top / density.density)
return Offset(offsetX, offsetY)
}

override fun sendKeyEvent(event: KeyEvent) {
layer.onKeyboardEvent(event)
}
}

override val viewConfiguration =
Expand Down Expand Up @@ -354,7 +357,7 @@ internal class ComposeWindow(
event: TouchEvent,
offset: Offset,
) {
keyboardModeState = KeyboardModeState.Companion.Virtual
keyboardModeState = KeyboardModeState.Virtual
val eventType = when (event.type) {
"touchstart" -> PointerEventType.Press
"touchmove" -> PointerEventType.Move
Expand Down Expand Up @@ -386,7 +389,7 @@ internal class ComposeWindow(
private fun ComposeLayer.onMouseEvent(
event: MouseEvent,
) {
keyboardModeState = KeyboardModeState.Companion.Hardware
keyboardModeState = KeyboardModeState.Hardware
val eventType = when (event.type) {
"mousedown" -> PointerEventType.Press
"mousemove" -> PointerEventType.Move
Expand All @@ -413,7 +416,7 @@ internal class ComposeWindow(
private fun ComposeLayer.onWheelEvent(
event: WheelEvent,
) {
keyboardModeState = KeyboardModeState.Companion.Hardware
keyboardModeState = KeyboardModeState.Hardware
onMouseEvent(
eventType = PointerEventType.Scroll,
position = event.offset,
Expand Down
Loading