Frustrated with the pointerInput offerings, I deci...
# compose
t
Frustrated with the pointerInput offerings, I decided to write my own detectTwoTouch gesture that (hopefully) fits my needs a little better (does both pinch transaction(start, move, end, like drag) and just tap. I'm placing it in thread. I would appreciate any feedback. I'm currently doing nothing with the whole "consume" thing, because it's a little unclear to me how to properly accomodate that.
Copy code
suspend fun AwaitPointerEventScope.awaitTwoDown(
   requireUnconsumed: Boolean = true,
   pass: PointerEventPass = PointerEventPass.Main,
): Pair<PointerInputChange, PointerInputChange> {
   awaitFirstDown(requireUnconsumed, pass)
   var event: PointerEvent
   do {
      event = awaitPointerEvent(pass)
   } while (!(event.changes.size == 2 && event.changes.any { change -> !(requireUnconsumed && change.isConsumed) && change.pressed && change.previousPressed } && event.changes.any { change -> !(requireUnconsumed && change.isConsumed) && change.pressed && !change.previousPressed }))
   return event.changes[0] to event.changes[1]
}

suspend fun PointerInputScope.detectTwoTouch(
   onPinchStart: BinaryAction<Offset, Offset> = { _, _ -> },
   onPinchMove: BinaryAction<Offset, Offset> = { _, _ -> },
   onPinchEnd: BinaryAction<Offset, Offset> = { _, _ -> },
   onPinchCancel: NullaryAction = { },
   onTwoTap: BinaryAction<Offset, Offset> = { _, _ -> }
) {
   val pinchThreshold = 20f
   awaitEachGesture {
      val (start1, start2) = awaitTwoDown()
      val startPoints = listOf(start1.position, start2.position)
      var endPoints = mutableListOf<Offset>()
      var inPinch = false
      var canceled = false
      do {
         val event = awaitPointerEvent()
         when (event.type) {
            PointerEventType.Press -> {
               canceled = true // this means a 3rd tap came down
            }
            PointerEventType.Move -> {
               val dragPoints = event.changes.map { change -> change.position }
               if (dragPoints.size == 2) {
                  if (NOT(inPinch)) {
                     val maxTouchSlop = (startPoints.zip(dragPoints) { s, d -> (s - d).r }).max()
                     if (maxTouchSlop > pinchThreshold) {
                        onPinchStart(start1.position, start2.position)
                        inPinch = true
                     }
                  }
                  if (inPinch) {
                     onPinchMove(dragPoints[0], dragPoints[1])
                  }
               }
            }
            PointerEventType.Release -> {
               endPoints += event.changes.filter { change -> change.changedToUpIgnoreConsumed() }
                  .map { change -> change.position }
            }
         }
      } while (!canceled && event.changes.any { change -> change.pressed })
      if (canceled) {
         if (inPinch) {
            onPinchCancel()
         }
      } else {
         if (inPinch) {
            onPinchEnd(endPoints[0], endPoints[1])
         } else {
            onTwoTap(endPoints[0], endPoints[1])
         }
      }
   }
}