You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
6.1 KiB

# Keyboard events handling
## Prerequisites
4 years ago
This tutorial expects that you have already set up the Compose project as described in the [Getting Started tutorial](../Getting_Started)
## What is covered
4 years ago
In this tutorial, we will look at two different ways of handling keyboard events in Compose for Desktop as well as the utilities that we have to do this.
## Event handlers
4 years ago
There are two ways to handle key events in Compose for Desktop:
4 years ago
- By setting up an event handler based on the element that is in focus
- By setting up an event handler in the scope of the window
## Focus related events
4 years ago
It works the same as Compose for Android, for details see [API Reference](https://developer.android.com/reference/kotlin/androidx/compose/ui/input/key/package-summary#keyinputfilter)
The most common use case is to define keyboard handlers for active controls like `TextField`. You can use both `onKeyEvent` and `onPreviewKeyEvent` but the last one is usually preferable to define shortcuts while it guarantees you that key events will not be consumed by children components. Here is an example:
```kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
3 years ago
import androidx.compose.material.Text
import androidx.compose.material.TextField
3 years ago
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
3 years ago
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.isCtrlPressed
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.input.key.KeyEventType
3 years ago
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
@OptIn(ExperimentalComposeUiApi::class)
3 years ago
fun main() = singleWindowApplication {
MaterialTheme {
var consumedText by remember { mutableStateOf(0) }
var text by remember { mutableStateOf("") }
Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) {
Text("Consumed text: $consumedText")
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier.onPreviewKeyEvent {
when {
(it.isCtrlPressed && it.key == Key.Minus && it.type == KeyEventType.KeyUp) -> {
4 years ago
consumedText -= text.length
text = ""
true
4 years ago
}
(it.isCtrlPressed && it.key == Key.Equals && it.type == KeyEventType.KeyUp) -> {
4 years ago
consumedText += text.length
text = ""
true
4 years ago
}
else -> false
4 years ago
}
}
)
}
}
}
```
3 years ago
Note the annotation `@OptIn(ExperimentalComposeUiApi::class)`. Some keys related APIs are still an experimental feature of Compose, and later API changes are possible. So it requires the use of a special annotation to emphasize the experimental nature of the code.
<img alt="keyInputFilter" src="keyInputFilter.gif" height="272" />
## Window-scoped events
3 years ago
`Window`, `singleWindowApplication` and `Dialog` functions have a `onPreviewKeyEvent` and a `onKeyEvent` properties. It is possible to use them to define keyboard event handlers that are always active in the current window. You possibly want to use `onPreviewKeyEvent` here to intercept events. Here is an example:
``` kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
3 years ago
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
3 years ago
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
3 years ago
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.isCtrlPressed
import androidx.compose.ui.input.key.isShiftPressed
import androidx.compose.ui.input.key.key
3 years ago
import androidx.compose.ui.input.key.type
import androidx.compose.ui.unit.dp
3 years ago
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.singleWindowApplication
private var cleared by mutableStateOf(false)
@OptIn(ExperimentalComposeUiApi::class)
3 years ago
fun main() = singleWindowApplication(
onKeyEvent = {
if (
it.isCtrlPressed &&
it.isShiftPressed &&
it.key == Key.C &&
it.type == KeyEventType.KeyDown
) {
cleared = true
true
} else {
false
}
3 years ago
}
) {
MaterialTheme {
if (cleared) {
Text("The App was cleared!")
} else {
App()
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun App() {
3 years ago
var isDialogOpen by remember { mutableStateOf(false) }
if (isDialogOpen) {
Dialog(
onCloseRequest = { isDialogOpen = false },
onPreviewKeyEvent = {
if (it.key == Key.Escape && it.type == KeyEventType.KeyDown) {
isDialogOpen = false
true
} else {
false
}
}) {
Text("I'm dialog!")
}
}
Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) {
Button(
modifier = Modifier.padding(4.dp),
3 years ago
onClick = { isDialogOpen = true }
) {
3 years ago
Text("Open dialog")
}
}
}
```
<img alt="window_keyboard" src="window_keyboard.gif" height="280" />