about summary refs log tree commit diff
path: root/src/view/com/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/util')
-rw-r--r--src/view/com/util/gestures/SwipeAndZoom.tsx (renamed from src/view/com/util/gestures/Swipe.tsx)97
1 files changed, 78 insertions, 19 deletions
diff --git a/src/view/com/util/gestures/Swipe.tsx b/src/view/com/util/gestures/SwipeAndZoom.tsx
index f6d600d02..dc3a9f54c 100644
--- a/src/view/com/util/gestures/Swipe.tsx
+++ b/src/view/com/util/gestures/SwipeAndZoom.tsx
@@ -16,16 +16,19 @@ export enum Dir {
   Down,
   Left,
   Right,
+  Zoom,
 }
 
 interface Props {
   panX: Animated.Value
   panY: Animated.Value
+  zoom: Animated.Value
   canSwipeLeft?: boolean
   canSwipeRight?: boolean
   canSwipeUp?: boolean
   canSwipeDown?: boolean
   swipeEnabled?: boolean
+  zoomEnabled?: boolean
   hasPriority?: boolean // if has priority, will not release control of the gesture to another gesture
   horzDistThresholdDivisor?: number
   vertDistThresholdDivisor?: number
@@ -36,14 +39,16 @@ interface Props {
   children: React.ReactNode
 }
 
-export function Swipe({
+export function SwipeAndZoom({
   panX,
   panY,
+  zoom,
   canSwipeLeft = false,
   canSwipeRight = false,
   canSwipeUp = false,
   canSwipeDown = false,
-  swipeEnabled = true,
+  swipeEnabled = false,
+  zoomEnabled = false,
   hasPriority = false,
   horzDistThresholdDivisor = 1.75,
   vertDistThresholdDivisor = 1.75,
@@ -55,6 +60,9 @@ export function Swipe({
 }: Props) {
   const winDim = useWindowDimensions()
   const [dir, setDir] = useState<Dir>(Dir.None)
+  const [initialDistance, setInitialDistance] = useState<number | undefined>(
+    undefined,
+  )
 
   const swipeVelocityThreshold = 35
   const swipeHorzDistanceThreshold = winDim.width / horzDistThresholdDivisor
@@ -84,6 +92,7 @@ export function Swipe({
     if (d === Dir.Right) return canSwipeRight
     if (d === Dir.Up) return canSwipeUp
     if (d === Dir.Down) return canSwipeDown
+    if (d === Dir.Zoom) return zoomEnabled
     return false
   }
   const isHorz = (d: Dir) => d === Dir.Left || d === Dir.Right
@@ -93,34 +102,40 @@ export function Swipe({
     event: GestureResponderEvent,
     gestureState: PanResponderGestureState,
   ) => {
-    if (swipeEnabled === false) {
-      return false
+    if (zoomEnabled && gestureState.numberActiveTouches === 2) {
+      return true
+    } else if (swipeEnabled && gestureState.numberActiveTouches === 1) {
+      const dx = I18nManager.isRTL ? -gestureState.dx : gestureState.dx
+      const dy = gestureState.dy
+      const willHandle =
+        (isMovingHorizontally(event, gestureState) &&
+          ((dx > 0 && canSwipeLeft) || (dx < 0 && canSwipeRight))) ||
+        (isMovingVertically(event, gestureState) &&
+          ((dy > 0 && canSwipeUp) || (dy < 0 && canSwipeDown)))
+      return willHandle
     }
-
-    const dx = I18nManager.isRTL ? -gestureState.dx : gestureState.dx
-    const dy = gestureState.dy
-    const willHandle =
-      (isMovingHorizontally(event, gestureState) &&
-        ((dx > 0 && canSwipeLeft) || (dx < 0 && canSwipeRight))) ||
-      (isMovingVertically(event, gestureState) &&
-        ((dy > 0 && canSwipeUp) || (dy < 0 && canSwipeDown)))
-    return willHandle
+    return false
   }
 
   const startGesture = () => {
     setDir(Dir.None)
     onSwipeStart?.()
 
+    // reset all state
     panX.stopAnimation()
     // @ts-expect-error: _value is private, but docs use it as well
     panX.setOffset(panX._value)
     panY.stopAnimation()
     // @ts-expect-error: _value is private, but docs use it as well
     panY.setOffset(panY._value)
+    zoom.stopAnimation()
+    // @ts-expect-error: _value is private, but docs use it as well
+    zoom.setOffset(zoom._value)
+    setInitialDistance(undefined)
   }
 
   const respondToGesture = (
-    _: GestureResponderEvent,
+    e: GestureResponderEvent,
     gestureState: PanResponderGestureState,
   ) => {
     const dx = I18nManager.isRTL ? -gestureState.dx : gestureState.dx
@@ -128,8 +143,10 @@ export function Swipe({
 
     let newDir = Dir.None
     if (dir === Dir.None) {
-      // establish if the user is swiping horz or vert
-      if (Math.abs(dx) > Math.abs(dy)) {
+      // establish if the user is swiping horz or vert, or zooming
+      if (gestureState.numberActiveTouches === 2) {
+        newDir = Dir.Zoom
+      } else if (Math.abs(dx) > Math.abs(dy)) {
         newDir = dx > 0 ? Dir.Left : Dir.Right
       } else {
         newDir = dy > 0 ? Dir.Up : Dir.Down
@@ -140,9 +157,37 @@ export function Swipe({
     } else if (isVert(dir)) {
       // direction update
       newDir = dy > 0 ? Dir.Up : Dir.Down
+    } else {
+      newDir = dir
     }
 
-    if (isHorz(newDir)) {
+    if (newDir === Dir.Zoom) {
+      if (zoomEnabled) {
+        if (gestureState.numberActiveTouches === 2) {
+          // zoom in/out
+          const x0 = e.nativeEvent.touches[0].pageX
+          const x1 = e.nativeEvent.touches[1].pageX
+          const y0 = e.nativeEvent.touches[0].pageY
+          const y1 = e.nativeEvent.touches[1].pageY
+          const zoomDx = Math.abs(x0 - x1)
+          const zoomDy = Math.abs(y0 - y1)
+          const dist = Math.sqrt(zoomDx * zoomDx + zoomDy * zoomDy) / 100
+          if (
+            typeof initialDistance === 'undefined' ||
+            dist - initialDistance < 0
+          ) {
+            setInitialDistance(dist)
+          } else {
+            zoom.setValue(dist - initialDistance)
+          }
+        } else {
+          // pan around after zooming
+          panX.setValue(clamp(dx / winDim.width, -1, 1) * -1)
+          panY.setValue(clamp(dy / winDim.height, -1, 1) * -1)
+        }
+      }
+    } else if (isHorz(newDir)) {
+      // swipe left/right
       panX.setValue(
         clamp(
           dx / swipeHorzDistanceThreshold,
@@ -152,6 +197,7 @@ export function Swipe({
       )
       panY.setValue(0)
     } else if (isVert(newDir)) {
+      // swipe up/down
       panY.setValue(
         clamp(
           dy / swipeVertDistanceThreshold,
@@ -175,7 +221,7 @@ export function Swipe({
     _: GestureResponderEvent,
     gestureState: PanResponderGestureState,
   ) => {
-    const finish = (finalDir: dir) => () => {
+    const finish = (finalDir: Dir) => () => {
       if (finalDir !== Dir.None) {
         onSwipeEnd?.(finalDir)
       }
@@ -190,6 +236,7 @@ export function Swipe({
       (Math.abs(gestureState.dx) > swipeHorzDistanceThreshold / 4 ||
         Math.abs(gestureState.vx) > swipeVelocityThreshold)
     ) {
+      // horizontal swipe reset
       Animated.timing(panX, {
         toValue: dir === Dir.Left ? -1 : 1,
         duration: 100,
@@ -200,18 +247,30 @@ export function Swipe({
       (Math.abs(gestureState.dy) > swipeVertDistanceThreshold / 8 ||
         Math.abs(gestureState.vy) > swipeVelocityThreshold)
     ) {
+      // vertical swipe reset
       Animated.timing(panY, {
         toValue: dir === Dir.Up ? -1 : 1,
         duration: 100,
         useNativeDriver,
       }).start(finish(dir))
     } else {
+      // zoom (or no direction) reset
       onSwipeEnd?.(Dir.None)
       Animated.timing(panX, {
         toValue: 0,
         duration: 100,
         useNativeDriver,
-      }).start(finish(Dir.None))
+      }).start()
+      Animated.timing(panY, {
+        toValue: 0,
+        duration: 100,
+        useNativeDriver,
+      }).start()
+      Animated.timing(zoom, {
+        toValue: 0,
+        duration: 100,
+        useNativeDriver,
+      }).start()
     }
   }