Nucleus
Tao backend

Multi-touch & gestes

Lisez les gestes trackpad et tactiles du backend Tao via les pointer events Compose, ou via le flux de gestes brut.

Le backend Tao relaie le flux de gestes du système vers les pointer events Compose : les pincements, rotations et scrolls trackpad atteignent votre UI via les APIs d'entrée Compose standard. Quand vous avez besoin des données de geste non traitées, TaoWindow expose aussi le flux trackpad brut.

Ajouter la dépendance

La prise en charge des gestes est incluse dans le backend Tao. Aucun artefact supplémentaire n'est nécessaire.

build.gradle.kts
dependencies {
    implementation("dev.nucleusframework:nucleus.decorated-window-tao:2.5.18")
}

Lire les gestes depuis Compose

La plupart du traitement des gestes passe par Compose. Les entrées trackpad et tactiles arrivent sous forme de PointerInputChange ; detectTransformGestures et les autres modifiers d'entrée fonctionnent donc sans code spécifique à Tao :

import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput

@Composable
fun ZoomCanvas() {
    var scale by remember { mutableStateOf(1f) }

    Box(
        Modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                detectTransformGestures { _, _, zoom, _ ->
                    scale *= zoom
                }
            },
    )
}

Chaque PointerInputChange porte aussi la source d'entrée et la pression, ce qui vous permet de réagir selon la manière dont l'événement a été produit :

import androidx.compose.ui.input.pointer.PointerType

Modifier.pointerInput(Unit) {
    awaitPointerEventScope {
        while (true) {
            val change = awaitPointerEvent().changes.first()
            val source = change.type          // PointerType.Mouse | Touch — Tao n'émet ni Stylus ni Eraser
            val pressure = change.pressure    // 1f si le digitaliseur n'en rapporte aucune
            // Réagir selon source / pression.
        }
    }
}

Lire les pans du trackpad depuis Compose

Depuis 2.5.15 sur macOS, les gestes trackpad à deux doigts arrivent comme PointerEventType.PanStart / PanMove / PanEnd avec panOffset en pixels. Les crans de molette restent des Scroll. Modifier.scrollable de Foundation gère les deux ; un pointerInput personnalisé qui n'écoute que Scroll ne verra pas le trackpad :

import androidx.compose.ui.input.pointer.PointerEventType

Modifier.pointerInput(Unit) {
    awaitPointerEventScope {
        while (true) {
            val event = awaitPointerEvent()
            val change = event.changes.first()
            when (event.type) {
                PointerEventType.PanMove -> {
                    val pan = change.panOffset  // pixels ; preciseWheelRotation AWT × 10.dp
                    // Appliquez le pan.
                }
                PointerEventType.Scroll -> {
                    val scroll = change.scrollDelta
                    // Cran de molette ou scroll précis sans phase.
                }
                else -> Unit
            }
        }
    }
}

Démarrez le processus avec -Dnucleus.tao.trackpadPanEvents=false pour recevoir des événements Scroll façon AWT à chaque pas.

Le pan reste ouvert pendant la queue d'inertie AppKit. Le fermer au lever des doigts empilerait le fling de Compose sur l'animation de la plateforme. Un PanEnd arrive après la queue, ou après un court délai de grâce s'il n'y a pas de queue.

Lire le flux de gestes brut

Pour distinguer directement le type et la phase d'un geste — par exemple séparer un pincement d'une rotation — enregistrez un TrackpadGestureListener sur le TaoWindow courant :

import dev.nucleusframework.window.tao.LocalTaoWindow
import dev.nucleusframework.window.tao.TaoTrackpadGesture
import dev.nucleusframework.window.tao.TaoTrackpadPhase

val window = LocalTaoWindow.current
window?.onTrackpadGesture { kind, phase, xFixed, yFixed, valueFixed ->
    if (kind == TaoTrackpadGesture.MAGNIFY && phase == TaoTrackpadPhase.CHANGED) {
        val ratio = valueFixed / 10_000f   // delta de magnify sous forme de ratio
        // Appliquez le delta de zoom.
    }
}

Le callback reçoit des entiers en virgule fixe : xFixed et yFixed sont des pixels physiques relatifs à la vue, multipliés par 1024, et valueFixed est le delta multiplié par 10000 (un ratio pour magnify, des degrés pour rotate).

onTrackpadGesture remplace le listener précédemment enregistré au lieu d'en ajouter un. Enregistrez un seul listener par fenêtre.

Fonctionnement

Sur chaque plateforme, Tao s'abonne à la source de gestes native haute résolution — NSEvent magnify et rotate sur macOS, l'entrée des touchpads de précision sur Windows, et le protocole de gestes du pointeur Wayland sur Linux. Le pont Rust remet ces événements en forme et les livre à la JVM via un format unique, que le scene host convertit en pointer events Compose.

Le TrackpadGestureListener brut se place sur ce même flux, avant la synthèse des pointer events. macOS et Linux émettent magnify, rotate et smart-magnify ; Windows n'émet que magnify. Il n'existe pas de source native sur les autres configurations, où le listener ne se déclenche donc pas.

Tao rapporte les positions du curseur au 1/1024 de pixel : depuis 2.5.6, un mouvement de moins de 1 dp par rapport à la dernière position dispatchée est supprimé avant d'atteindre Compose. Sans cette zone morte, un clic qui dérive d'une fraction de pixel entre l'appui et le relâchement démarre un geste de drag parent, qui consomme le changement et annule le tap. Appui, relâchement, sortie et scroll sont dispatchés à la dernière position dispatchée ; un mouvement au-delà de la zone morte garde sa position sous-pixel exacte. Depuis la même version, viewConfiguration.touchSlop est mis à l'échelle de la densité (18.dp — parité AWT et Android) au lieu de 18 pixels bruts, ce qui divisait par deux tous les seuils de drag et de scroll sur les écrans 2x.

Depuis 2.5.15, le scroll trackpad macOS correspond à AWT : AppKit suit déjà la convention MouseScrollDelta, donc la négation supplémentaire de scrollingDeltaX a disparu, et les deltas précis sont transmis en points logiques (AWT n'applique jamais le facteur d'échelle d'affichage). Les pas phasés passent par TaoTrackpadPanRouter et deviennent des événements Pan à 10.dp par unité de molette AWT — le même facteur que MacOSCocoaConfig de Compose Desktop applique à un cran. Les crans de molette et les scrolls précis sans phase restent des Scroll. Les vues natives consomment le pan et le transmettent pour que la vue défilante intégrée fasse son rubber-band ; les popups partagent le même routeur que la fenêtre.

Référence de l'API

Enregistrer un listener de gestes

// dev.nucleusframework.window.tao.TaoWindow
fun onTrackpadGesture(listener: TrackpadGestureListener)

fun interface TrackpadGestureListener {
    fun onGesture(
        kind: Int,        // valeur TaoTrackpadGesture
        phase: Int,       // valeur TaoTrackpadPhase
        xFixed: Int,      // pixels physiques × 1024, relatifs à la vue
        yFixed: Int,
        valueFixed: Int,  // delta × 10000 (ratio pour magnify, degrés pour rotate)
    )
}

Récupérez la fenêtre via LocalTaoWindow.current, qui vaut null en dehors d'une fenêtre Tao.

Constantes de gestes

object TaoTrackpadGesture {
    const val MAGNIFY: Int = 0
    const val ROTATE: Int = 1
    const val SMART_MAGNIFY: Int = 2
}

object TaoTrackpadPhase {
    const val BEGAN: Int = 0
    const val CHANGED: Int = 1
    const val ENDED: Int = 2
    const val CANCELLED: Int = 3
}

Infos pointer sur chaque changement

// androidx.compose.ui.input.pointer.PointerInputChange
val type: PointerType         // Mouse | Touch | Stylus | Eraser
val pressure: Float           // 0f si le périphérique ne rapporte pas de pression
val position: Offset          // position sub-pixel
val scrollDelta: Offset       // molette et scroll précis sans phase
val panOffset: Offset         // pan trackpad (macOS, 2.5.15+) ; pixels

Et ensuite

  • Stylet — pression et inclinaison des périphériques stylet.
  • Wayland — comportement d'entrée et de fenêtrage spécifique à Linux.
  • Fenêtre décorée — la fenêtre Tao et sa barre de titre.
  • Backend Tao — ce que fournit le backend Tao.