Skip to main content

Pointer input

Pointer handling is easy with Doodle; simply include the PointerModule when launching your app, and the underlying framework uses it to produce key events.

Module Required

You must include the PointerModule (Web, Desktop) in your application in order to use these features.

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

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

Hit detection

The framework relies on the View contains method to determine when the pointer is within a View's boundaries. This method gets a point in the view's parent reference frame (or the Display's for top-level Views)) and returns whether or not that point intersects the view. The default implementation just checks the point against bounds and accounts for the view's transform.

Support for transforms

Doodle also accounts for transformations applied to the View's ancestors when delivering pointer events. This means the View will receive the right notification whenever the pointer intersects its parent despite transformations. Hit detection logic in the View is then triggered as usual. The View still needs to take its own transformation into account though, since the given point used in hit detection is within the parent coordinate space. This is automatically handled if the View implements intersects or if toLocal is used when overriding contains.

tip

Override intersects instead of contains unless you want to factor in the view's transform into your hit detection logic.

Pointer listeners

Views are able to receive pointer events once the PointerModule (Web, Desktop) is loaded, they are visible and enabled. You can then attach a PointerListener to any View's pointerChanged property and get notified whenever a pointer does one of the following:

  • Enters the View
  • Pressed within the View
  • Released within the View
  • Clicked (Pressed then Released) within the View
  • Exits the View
package pointerinput import io.nacular.doodle.core.View import io.nacular.doodle.event.PointerEvent import io.nacular.doodle.event.PointerListener import io.nacular.doodle.event.PointerListener.Companion.on import io.nacular.doodle.event.PointerListener.Companion.pressed fun example(view: View) { //sampleStart // Listen to pressed/exit via interface override view.pointerChanged += object: PointerListener { override fun pressed(event: PointerEvent) { // .. } override fun exited(event: PointerEvent) { // .. } } // Listener to pressed via DSL view.pointerChanged += pressed { event -> /* .. */ } // Listen to pressed/exit via DSL view.pointerChanged += on( pressed = { event -> /* .. */ }, exited = { event -> /* .. */ }, ) //sampleEnd }
tip

PointerListener has no-op defaults for each event, so you only need to implement the ones you need.

info

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

Pointer events

The event provided to PointerListeners carries information about the View it originated from (target), the View it is sent to (source), various attributes about the state of the pointers--like buttons pressed--and their locations relative to the target View.

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

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

Calling consume during filter will prevent descendants (and the target) from receiving the event

Pointer motion events

Pointer motion events occur whenever a pointer moves within a View. They are treated separately from pointer events because of their high frequency. The PointerModule is also required to enable them. And hit detection follows the same rules as with pointer events.

Registration is different though. You use listen to pointerMotionChanged and implement PointerMotionListener.

Pointer motion listeners are notified whenever a pointer:

  • Moves within a View
  • Drags anywhere while pressed, if the press started in a View
package pointerinput import io.nacular.doodle.core.View import io.nacular.doodle.event.PointerEvent import io.nacular.doodle.event.PointerMotionListener import io.nacular.doodle.event.PointerMotionListener.Companion.moved import io.nacular.doodle.event.PointerMotionListener.Companion.on fun pointerMotion(view: View) { //sampleStart // Listen to moved/dragged via interface override view.pointerMotionChanged += object: PointerMotionListener { override fun moved(event: PointerEvent) { // .. } override fun dragged(event: PointerEvent) { // .. } } // Listener to moved via DSL view.pointerMotionChanged += moved { event -> /* .. */ } // Listen to moved/dragged via DSL view.pointerMotionChanged += on( moved = { event -> /* .. */ }, dragged = { event -> /* .. */ }, ) //sampleEnd }

Multi-touch

Pointer events support multiple, simultaneous inputs by default. This covers the multi-touch use-case on mobile and other similar scenarios. The PointerEvent class contains information about all active Interactions for the current event. This includes those directed at the event target. Apps are therefore able to incorporate this into their pointer handling.

package pointerinput import io.nacular.doodle.core.View import io.nacular.doodle.event.PointerMotionListener.Companion.moved fun multiTouch(view: View) { //sampleStart view.pointerMotionChanged += moved { event -> event.targetInteractions // the set of interactions with the target View event.changedInteractions // the interactions that changed (triggered) this event event.allInteractions // all active interactions for the app } //sampleEnd }

Doodle also does not limit simultaneous interactions to a single View. All active interactions will be sent to the appropriate Views and managed concurrently. This means it is possible to drag multiple items at the same time.

tip

Try moving both boxes at the same time if you are on a mobile device or have multiple pointers.

tip