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.
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+) ; pixelsEt 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.