Browse Source

Updated Keyboard tutorial (#771)

pull/779/head
Andrew Rudenko 3 years ago committed by GitHub
parent
commit
603901b814
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 100
      tutorials/Keyboard/README.md
  2. BIN
      tutorials/Keyboard/window_keyboard.gif

100
tutorials/Keyboard/README.md

@ -8,16 +8,6 @@ This tutorial expects that you have already set up the Compose project as descri
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. 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.
## KeySets
Compose for Desktop has a few utilities to work with shortcuts:
`KeysSet` represents a set of keys that can be simultaneously pressed. You can construct a KeysSet using the Key's extension function:
``` kotlin
Key.CtrlLeft + Key.Enter
```
## Event handlers ## Event handlers
There are two ways to handle key events in Compose for Desktop: There are two ways to handle key events in Compose for Desktop:
@ -29,9 +19,7 @@ There are two ways to handle key events in Compose for Desktop:
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) 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)
`Modifier.shortcuts` is used to define one or multiple callbacks for `KeysSet`s. 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:
The most common use case is to define keyboard handlers for active controls like `TextField`. Here is an example:
```kotlin ```kotlin
import androidx.compose.desktop.Window import androidx.compose.desktop.Window
@ -49,8 +37,9 @@ import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.input.key.shortcuts import androidx.compose.ui.ExperimentalComposeUiApi
@OptIn(ExperimentalComposeUiApi::class)
fun main() = Window(title = "Compose for Desktop", size = IntSize(300, 300)) { fun main() = Window(title = "Compose for Desktop", size = IntSize(300, 300)) {
MaterialTheme { MaterialTheme {
var consumedText by remember { mutableStateOf(0) } var consumedText by remember { mutableStateOf(0) }
@ -58,18 +47,23 @@ fun main() = Window(title = "Compose for Desktop", size = IntSize(300, 300)) {
Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) { Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) {
Text("Consumed text: $consumedText") Text("Consumed text: $consumedText")
TextField( TextField(
value = text, value = text,
onValueChange = { text = it }, onValueChange = { text = it },
modifier = Modifier.shortcuts { modifier = Modifier.onPreviewKeyEvent {
on(Key.CtrlLeft + Key.Minus) { when {
(it.isMetaPressed && it.key == Key.Minus) -> {
consumedText -= text.length consumedText -= text.length
text = "" text = ""
true
} }
on(Key.CtrlLeft + Key.Equals) { (it.isMetaPressed && it.key == Key.Equals) -> {
consumedText += text.length consumedText += text.length
text = "" text = ""
true
} }
else -> false
} }
}
) )
} }
} }
@ -77,48 +71,82 @@ fun main() = Window(title = "Compose for Desktop", size = IntSize(300, 300)) {
``` ```
Note the annotation `@OptIn(ExperimentalKeyInput::class)`. Keyboard-related event handlers 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. Note the annotation `@OptIn(ExperimentalKeyInput::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.
![keyInputFilter](keyInputFilter.gif) ![keyInputFilter](keyInputFilter.gif)
## Window-scoped events ## Window-scoped events
`AppWindow` instances have a `keyboard` property. It is possible to use it to define keyboard shortcuts that are always active in the current window. Here is an example: `LocalAppWindow` instances have a `keyboard` property. It is possible to use it to define keyboard event handlers that are always active in the current window. You also can get window instance for popups. Again, you possibly want to use `onPreviewKeyEvent` here to intercept events. Here is an example:
``` kotlin ``` kotlin
import androidx.compose.desktop.AppWindow import androidx.compose.desktop.AppWindow
import androidx.compose.desktop.LocalAppWindow
import androidx.compose.desktop.Window import androidx.compose.desktop.Window
import androidx.compose.foundation.Text
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.ExperimentalKeyInput
import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.isMetaPressed
import androidx.compose.ui.input.key.isShiftPressed
import androidx.compose.ui.input.key.key
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
@OptIn(ExperimentalKeyInput::class) @OptIn(ExperimentalComposeUiApi::class)
fun main() = Window(title = "Compose for Desktop", size = IntSize(300, 300)) { fun main() = Window(title = "Compose for Desktop", size = IntSize(300, 300)) {
MaterialTheme { MaterialTheme {
Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) { var cleared by remember { mutableStateOf(false) }
Button( LocalAppWindow.current.keyboard.onKeyEvent = {
modifier = Modifier.padding(4.dp), if (it.isMetaPressed && it.isShiftPressed && it.key == Key.C) {
onClick = { cleared = true
AppWindow(size = IntSize(200, 200)).also { true
it.keyboard.setShortcut(Key.Escape) { } else {
it.close() false
} }
}.show { }
Text("I'm popup!")
if (cleared) {
Text("The App was cleared!")
} else {
App()
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun App() {
Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) {
Button(
modifier = Modifier.padding(4.dp),
onClick = {
AppWindow(size = IntSize(200, 200)).also { window ->
window.keyboard.onPreviewKeyEvent = {
if (it.key == Key.Escape) {
window.close()
true
} else {
false
} }
} }
) { }.show {
Text("Open popup") Text("I'm popup!")
}
} }
) {
Text("Open popup")
} }
} }
} }

BIN
tutorials/Keyboard/window_keyboard.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 486 KiB

After

Width:  |  Height:  |  Size: 493 KiB

Loading…
Cancel
Save