Skip to content

13. Input & gestures 🟠

Games need more than onClick. TTac uses Compose's low-level pointer API for taps, drags, flicks and path tracing.

Levels of the pointer API

API Use
Modifier.clickable Simple buttons (TTac avoids it to control the bounce)
detectTapGestures / detectDragGestures Common gestures — the board's taps, sliders
awaitEachGesture { awaitFirstDown(); awaitPointerEvent() … } Full control — Blocks, Word Hive, bouncy press

Blocks: one gesture, four actions

awaitEachGesture {
    val down = awaitFirstDown()
    val tracker = VelocityTracker()
    while (true) {
        val change = awaitPointerEvent().changes.first { it.id == down.id }
        if (!change.pressed) break
        accX += change.positionChange().x
        while (abs(accX) >= cellPx * 0.9f) { move(sign(accX)); accX -= sign(accX) * cellPx * 0.9f }  // drag = move
        while (accY >= cellPx) { softDrop(); accY -= cellPx }                                        // drag down = soft drop
        tracker.addPosition(change.uptimeMillis, change.position)
    }
    when {
        tracker.calculateVelocity().y > 2600f -> hardDrop()   // flick down = hard drop
        !moved -> rotate()                                     // tap = rotate
    }
}

Accumulating movement and converting whole cells at a time gives precise, cell-snapped control however fast the finger moves. VelocityTracker distinguishes a flick from a slow drag.

Word Hive: tracing a path through hexagons

Each pointer move finds the nearest hex centre; a cell only counts once the finger is well inside it (72% of the radius), so diagonal drags don't clip neighbours. Then:

  • a new cell adjacent to the last one → extend the path;
  • the second-to-last cell → backtrack (undo a step);
  • anything else → ignore.

Release submits the path. See Word Hive for the hex maths.

Going further

  • change.consume() stops parent scroll containers from stealing the gesture.
  • Semantics (Role.Button, contentDescription) keep Canvas UIs accessible and testable.