Skip to main content

Keyboard input

Key handling is simple with Doodle; simply include the KeyboardModule when launching your app, and the underlying framework uses it to produce key events.

Module Required

You must include the KeyboardModule in your application in order to use these features.

package keyboard import io.nacular.doodle.application.Application import io.nacular.doodle.application.Modules.Companion.KeyboardModule import io.nacular.doodle.application.application import io.nacular.doodle.core.Display import org.kodein.di.instance import rendering.MyApp //sampleStart fun main() { /** Include [KeyboardModule] when launching your app */ application(modules = listOf(KeyboardModule)) { MyApp(instance()) } } /** * Key events will fire for this app when launched with [KeyboardModule] */ class MyApp(display: Display): Application { override fun shutdown() {} } //sampleEnd

Doodle uses opt-in modules like this to improve bundle size.

A View must gain focus in order to begin receiving key events. This ensures that only a single View can receive key events at any time within the app.

Use the FocusManager to control focus. It is included in the KeyboardModule. Just inject it into your app to begin managing the focus.

package focus import io.nacular.doodle.application.Application import io.nacular.doodle.application.Modules.Companion.KeyboardModule import io.nacular.doodle.application.application import io.nacular.doodle.core.Display import io.nacular.doodle.core.view import io.nacular.doodle.focus.FocusManager import org.kodein.di.instance //sampleStart fun main() { application(modules = listOf(KeyboardModule)) { // FocusManager is available in the KeyboardModule MyApp(display = instance(), focusManager = instance()) } } class MyApp(display: Display, focusManager: FocusManager): Application { init { val view = view {} display += view // ... focusManager.requestFocus(view) // ... } override fun shutdown() {} } //sampleEnd
tip

Some controls (i.e. TextField) also manage their focus when styled in the native theme

Key Listeners

Views are able to receive key events once the KeyboardModule is loaded and they have focus. You can then attach a KeyListener to any View and get notified whenever it has focus and a key is:

  • Pressed
  • Released

You get these notifications by registering with a View's keyChanged property.

package keyboard import io.nacular.doodle.core.View import io.nacular.doodle.event.KeyEvent import io.nacular.doodle.event.KeyListener import io.nacular.doodle.event.KeyListener.Companion.on import io.nacular.doodle.event.KeyListener.Companion.pressed fun example(view: View) { //sampleStart // Listen to pressed/released via interface override view.keyChanged += object: KeyListener { override fun pressed(event: KeyEvent) { // .. } override fun released(event: KeyEvent) { // .. } } // Listener to pressed via DSL view.keyChanged += pressed { event -> /* .. */ } // Listen to pressed/released via DSL view.keyChanged += on( pressed = { event -> /* .. */ }, released = { event -> /* .. */ }, ) //sampleEnd }
tip

KeyListener has no-op defaults for the 2 events, so you only need to implement the ones you need.

info

Notice that keyChanged--like other observable properties--supports many observers and enables you to add/remove an observer any time.

Key events

The event provided to key listeners carries information about the View it originated from (source), and various attributes about the key that was pressed or released.

Key events are consumable. This means any observer can call consume on the event and prevent subsequent listeners from receiving it.

package keyboard import io.nacular.doodle.core.View import io.nacular.doodle.event.KeyListener.Companion.pressed fun consume(view: View) { //sampleStart view.keyChanged += pressed { event -> // ... take action based on event event.consume() // indicate that no other listeners should be notified } //sampleEnd }

Virtual keys and text

KeyEvent key is a layout independent identifier that tells you which "virtual key" was pressed or which text the key can be translated into. Most key handling use-cases should use this property to compare keys.

package keyboard import io.nacular.doodle.core.View import io.nacular.doodle.event.KeyListener.Companion.pressed import io.nacular.doodle.event.KeyText.Companion.Backspace import io.nacular.doodle.event.KeyText.Companion.Enter fun virtualKeys(view: View) { //sampleStart view.keyChanged += pressed { event -> when (event.key) { Enter -> { /* ... */ } Backspace -> { /* ... */ } // ... } } view.keyChanged += pressed { event -> // this will be user-appropriate text when the key pressed is not // one of the "named" keys (i.e. Tab, Shift, Enter, ...) event.key.text } //sampleEnd }

Physical keys

Some applications will require the use of "physical" keys instead of virtual ones. This makes sense for games or other apps where the key position on a physical keyboard matters.

This information comes from KeyEvent code.

package keyboard import io.nacular.doodle.core.View import io.nacular.doodle.event.KeyCode.Companion.AltLeft import io.nacular.doodle.event.KeyCode.Companion.AltRight import io.nacular.doodle.event.KeyCode.Companion.Backspace import io.nacular.doodle.event.KeyListener.Companion.pressed fun physicalKeys(view: View) { //sampleStart view.keyChanged += pressed { event -> when (event.code) { AltLeft -> { /* ... */ } AltRight -> { /* ... */ } Backspace -> { /* ... */ } // ... } } //sampleEnd }
caution

Physical keys do not take keyboard differences and locale into account; so avoid them if possible