Nikolay Igotti
4 years ago
committed by
GitHub
3 changed files with 128 additions and 0 deletions
@ -0,0 +1,128 @@ |
|||||||
|
# Keyboard events handling |
||||||
|
|
||||||
|
## Prerequisites |
||||||
|
|
||||||
|
This tutorial expects that you have already set up the Compose project as described in the [Getting Started tutorial](../Getting_Started) |
||||||
|
|
||||||
|
## What is covered |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
There are two ways to handle key events in Compose for Desktop: |
||||||
|
|
||||||
|
- 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 |
||||||
|
|
||||||
|
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`. Here is an example: |
||||||
|
|
||||||
|
``` kotlin |
||||||
|
import androidx.compose.desktop.Window |
||||||
|
import androidx.compose.foundation.Text |
||||||
|
import androidx.compose.foundation.layout.Arrangement |
||||||
|
import androidx.compose.foundation.layout.Column |
||||||
|
import androidx.compose.foundation.layout.fillMaxSize |
||||||
|
import androidx.compose.material.MaterialTheme |
||||||
|
import androidx.compose.material.TextField |
||||||
|
import androidx.compose.runtime.mutableStateOf |
||||||
|
import androidx.compose.runtime.remember |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.input.key.* |
||||||
|
import androidx.compose.ui.unit.IntSize |
||||||
|
import androidx.compose.ui.unit.dp |
||||||
|
import androidx.compose.runtime.getValue |
||||||
|
import androidx.compose.runtime.setValue |
||||||
|
import androidx.compose.ui.input.key.shortcuts |
||||||
|
|
||||||
|
@OptIn(ExperimentalKeyInput::class) |
||||||
|
fun main() = Window(title = "Compose for Desktop", size = IntSize(300, 300)) { |
||||||
|
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.shortcuts { |
||||||
|
on(Key.CtrlLeft + Key.Minus) { |
||||||
|
consumedText -= text.length |
||||||
|
text = "" |
||||||
|
} |
||||||
|
on(Key.CtrlLeft + Key.Equals) { |
||||||
|
consumedText += text.length |
||||||
|
text = "" |
||||||
|
} |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
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. |
||||||
|
|
||||||
|
![keyInputFilter](keyInputFilter.gif) |
||||||
|
|
||||||
|
## 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: |
||||||
|
|
||||||
|
``` kotlin |
||||||
|
import androidx.compose.desktop.AppWindow |
||||||
|
import androidx.compose.desktop.Window |
||||||
|
import androidx.compose.foundation.Text |
||||||
|
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.ui.Modifier |
||||||
|
import androidx.compose.ui.input.key.ExperimentalKeyInput |
||||||
|
import androidx.compose.ui.input.key.Key |
||||||
|
import androidx.compose.ui.unit.IntSize |
||||||
|
import androidx.compose.ui.unit.dp |
||||||
|
|
||||||
|
@OptIn(ExperimentalKeyInput::class) |
||||||
|
fun main() = Window(title = "Compose for Desktop", size = IntSize(300, 300)) { |
||||||
|
MaterialTheme { |
||||||
|
Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) { |
||||||
|
Button( |
||||||
|
modifier = Modifier.padding(4.dp), |
||||||
|
onClick = { |
||||||
|
AppWindow(size = IntSize(200, 200)).also { |
||||||
|
it.keyboard.setShortcut(Key.Escape) { |
||||||
|
it.close() |
||||||
|
} |
||||||
|
}.show { |
||||||
|
Text("I'm popup!") |
||||||
|
} |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("Open popup") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
![window_keyboard](window_keyboard.gif) |
After Width: | Height: | Size: 1.3 MiB |
After Width: | Height: | Size: 486 KiB |
Loading…
Reference in new issue