From aaa4c144f690ce6abc4935397492a0460249665b Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 8 Mar 2024 10:59:32 +0100 Subject: [PATCH 01/22] fling now detects movement by velocity, first working version tested on web --- src/web/handlers/FlingGestureHandler.ts | 69 ++++++++++++++++++++----- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index 38a0a7aa38..f342be083e 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -5,7 +5,7 @@ import { AdaptedEvent, Config } from '../interfaces'; import GestureHandler from './GestureHandler'; const DEFAULT_MAX_DURATION_MS = 800; -const DEFAULT_MIN_ACCEPTABLE_DELTA = 32; +const DEFAULT_MIN_ACCEPTABLE_DELTA = 400; const DEFAULT_DIRECTION = Direction.RIGHT; const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1; @@ -51,22 +51,65 @@ export default class FlingGestureHandler extends GestureHandler { } private tryEndFling(): boolean { + type SimpleVector = { x: number; y: number }; + + const toSafeNumber = (unsafe: number): number => { + return Number.isNaN(unsafe) ? 0 : unsafe; + }; + + const toUnitVector = (vec: SimpleVector): SimpleVector => { + const magnitude = Math.abs(vec.x + vec.y); + // division by 0 may occur here + return { + x: toSafeNumber(vec.x / magnitude), + y: toSafeNumber(vec.y / magnitude), + }; + }; + + const compareSimilarity = ( + vecA: SimpleVector, + vecB: SimpleVector + ): number => { + const unitA = toUnitVector(vecA); + const unitB = toUnitVector(vecB); + // returns scalar on range from -1.0 to 1.0 + return unitA.x * unitB.x + unitA.y * unitB.y; + }; + + const directionVector: SimpleVector = { + x: + (this.direction & Direction.LEFT ? -1 : 0) + + (this.direction & Direction.RIGHT ? 1 : 0), + y: + (this.direction & Direction.UP ? -1 : 0) + + (this.direction & Direction.DOWN ? 1 : 0), + }; + + const velocityVector: SimpleVector = { + x: this.tracker.getVelocityX(this.keyPointer), + y: this.tracker.getVelocityY(this.keyPointer), + }; + + // hypot may be overkill for this simple function, simple addition would be sufficient + const totalVelocity = Math.hypot(velocityVector.x, velocityVector.y); + + const movementAlignment = compareSimilarity( + directionVector, + velocityVector + ); + const isAligned = movementAlignment > 0.8; + const isFast = totalVelocity > this.minAcceptableDelta; + + console.log('received', velocityVector, 'expected:', directionVector); + console.log('alignment:', movementAlignment, isAligned); + if ( this.maxNumberOfPointersSimultaneously === this.numberOfPointersRequired && - ((this.direction & Direction.RIGHT && - this.tracker.getLastX(this.keyPointer) - this.startX > - this.minAcceptableDelta) || - (this.direction & Direction.LEFT && - this.startX - this.tracker.getLastX(this.keyPointer) > - this.minAcceptableDelta) || - (this.direction & Direction.UP && - this.startY - this.tracker.getLastY(this.keyPointer) > - this.minAcceptableDelta) || - (this.direction & Direction.DOWN && - this.tracker.getLastY(this.keyPointer) - this.startY > - this.minAcceptableDelta)) + isAligned && + isFast ) { + console.log('activating'); clearTimeout(this.delayTimeout); this.activate(); From 618e1e3c813f5297d599203203b57ffd98cfcba5 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 8 Mar 2024 11:00:01 +0100 Subject: [PATCH 02/22] fix commit requirement error --- src/web/handlers/FlingGestureHandler.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index f342be083e..05e0f8d13d 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -17,9 +17,6 @@ export default class FlingGestureHandler extends GestureHandler { private minAcceptableDelta = DEFAULT_MIN_ACCEPTABLE_DELTA; private delayTimeout!: number; - private startX = 0; - private startY = 0; - private maxNumberOfPointersSimultaneously = 0; private keyPointer = NaN; @@ -40,9 +37,6 @@ export default class FlingGestureHandler extends GestureHandler { } private startFling(): void { - this.startX = this.tracker.getLastX(this.keyPointer); - this.startY = this.tracker.getLastY(this.keyPointer); - this.begin(); this.maxNumberOfPointersSimultaneously = 1; From 38e736a1438ae179ea19f2da90b1c17e48f7dcc3 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 8 Mar 2024 12:51:55 +0100 Subject: [PATCH 03/22] - applied fling changes from web to native android devices - cleanup web code --- .../core/FlingGestureHandler.kt | 92 +++++++++++++++---- src/web/handlers/FlingGestureHandler.ts | 6 +- 2 files changed, 74 insertions(+), 24 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 75acef796a..f9d4b2c810 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -3,6 +3,9 @@ package com.swmansion.gesturehandler.core import android.os.Handler import android.os.Looper import android.view.MotionEvent +import android.view.VelocityTracker +import kotlin.math.abs +import kotlin.math.hypot class FlingGestureHandler : GestureHandler() { var numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED @@ -35,26 +38,69 @@ class FlingGestureHandler : GestureHandler() { handler!!.postDelayed(failDelayed, maxDurationMs) } - private fun tryEndFling(event: MotionEvent) = if ( - maxNumberOfPointersSimultaneously == numberOfPointersRequired && - ( - direction and DIRECTION_RIGHT != 0 && - event.rawX - startX > minAcceptableDelta || - direction and DIRECTION_LEFT != 0 && - startX - event.rawX > minAcceptableDelta || - direction and DIRECTION_UP != 0 && - startY - event.rawY > minAcceptableDelta || - direction and DIRECTION_DOWN != 0 && - event.rawY - startY > minAcceptableDelta + private fun tryEndFling(event: MotionEvent): Boolean { + data class SimpleVector(val x: Double, val y: Double) + + fun toSafeNumber(unsafe: Double): Double { + // todo: convert NaN, Infinity and Exception to 0 + return unsafe + } + + fun toUnitVector(vec: SimpleVector): SimpleVector { + val magnitude = abs(vec.x + vec.y) + // division by 0 may occur here + return SimpleVector( + toSafeNumber(vec.x / magnitude), + toSafeNumber(vec.y / magnitude) ) - ) { - handler!!.removeCallbacksAndMessages(null) - activate() - true - } else { - false - } + } + + fun compareSimilarity( + vecA: SimpleVector, + vecB: SimpleVector + ): Double { + val unitA = toUnitVector(vecA) + val unitB = toUnitVector(vecB) + // returns scalar on range from -1.0 to 1.0 + return unitA.x * unitB.x + unitA.y * unitB.y + } + + val directionVector = SimpleVector( + (if (direction and DIRECTION_LEFT != 0) -1.0 else 0.0) + + (if (direction and DIRECTION_RIGHT != 0) 1.0 else 0.0), + (if (direction and DIRECTION_UP != 0) -1.0 else 0.0) + + (if (direction and DIRECTION_DOWN != 0) 1.0 else 0.0), + ) + + val velocityTracker = VelocityTracker.obtain() + addVelocityMovement(velocityTracker, event) + velocityTracker!!.computeCurrentVelocity(1000) + + val velocityVector: SimpleVector = SimpleVector( + velocityTracker.xVelocity.toDouble(), + velocityTracker.yVelocity.toDouble() + ) + velocityTracker.recycle() + + // hypot may be overkill for this simple function, simple addition would be sufficient + val totalVelocity = hypot(velocityVector.x, velocityVector.y) + + val movementAlignment = compareSimilarity(directionVector, velocityVector) + val isAligned = movementAlignment > 0.75 + val isFast = totalVelocity > this.minAcceptableDelta + + if ( + maxNumberOfPointersSimultaneously == numberOfPointersRequired && + isAligned && isFast + ) { + handler!!.removeCallbacksAndMessages(null) + activate() + return true + } else { + return false + } + } override fun activate(force: Boolean) { super.activate(force) end() @@ -95,9 +141,17 @@ class FlingGestureHandler : GestureHandler() { handler?.removeCallbacksAndMessages(null) } + private fun addVelocityMovement(tracker: VelocityTracker?, event: MotionEvent) { + val offsetX = event.rawX - event.x + val offsetY = event.rawY - event.y + event.offsetLocation(offsetX, offsetY) + tracker!!.addMovement(event) + event.offsetLocation(-offsetX, -offsetY) + } + companion object { private const val DEFAULT_MAX_DURATION_MS: Long = 800 - private const val DEFAULT_MIN_ACCEPTABLE_DELTA: Long = 160 + private const val DEFAULT_MIN_ACCEPTABLE_DELTA: Long = 2000 private const val DEFAULT_DIRECTION = DIRECTION_RIGHT private const val DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1 } diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index 05e0f8d13d..06708fb901 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -91,19 +91,15 @@ export default class FlingGestureHandler extends GestureHandler { directionVector, velocityVector ); - const isAligned = movementAlignment > 0.8; + const isAligned = movementAlignment > 0.75; const isFast = totalVelocity > this.minAcceptableDelta; - console.log('received', velocityVector, 'expected:', directionVector); - console.log('alignment:', movementAlignment, isAligned); - if ( this.maxNumberOfPointersSimultaneously === this.numberOfPointersRequired && isAligned && isFast ) { - console.log('activating'); clearTimeout(this.delayTimeout); this.activate(); From a8f0ed1c59580bffd3167826addfe735a4d8b7ff Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 8 Mar 2024 15:57:53 +0100 Subject: [PATCH 04/22] add missing function, other minor improvements --- .../core/FlingGestureHandler.kt | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index f9d4b2c810..217be4c1cd 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -42,13 +42,15 @@ class FlingGestureHandler : GestureHandler() { data class SimpleVector(val x: Double, val y: Double) fun toSafeNumber(unsafe: Double): Double { - // todo: convert NaN, Infinity and Exception to 0 - return unsafe + return if (unsafe.isFinite()) + unsafe + else + 0.0 } fun toUnitVector(vec: SimpleVector): SimpleVector { val magnitude = abs(vec.x + vec.y) - // division by 0 may occur here + // toSafeNumber protects against division by zero return SimpleVector( toSafeNumber(vec.x / magnitude), toSafeNumber(vec.y / magnitude) @@ -76,29 +78,29 @@ class FlingGestureHandler : GestureHandler() { addVelocityMovement(velocityTracker, event) velocityTracker!!.computeCurrentVelocity(1000) - val velocityVector: SimpleVector = SimpleVector( + val velocityVector = SimpleVector( velocityTracker.xVelocity.toDouble(), velocityTracker.yVelocity.toDouble() ) velocityTracker.recycle() - // hypot may be overkill for this simple function, simple addition would be sufficient + // hypot may be overkill for this simple function, dot product may be sufficient val totalVelocity = hypot(velocityVector.x, velocityVector.y) - val movementAlignment = compareSimilarity(directionVector, velocityVector) - val isAligned = movementAlignment > 0.75 + val isAligned = compareSimilarity(directionVector, velocityVector) > 0.75 val isFast = totalVelocity > this.minAcceptableDelta - if ( + return if ( maxNumberOfPointersSimultaneously == numberOfPointersRequired && - isAligned && isFast + isAligned && + isFast ) { handler!!.removeCallbacksAndMessages(null) activate() - return true + true } else { - return false + false } } override fun activate(force: Boolean) { From 17d7b226ff0de8cae88ee34fe391531e742942de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacy=20=C5=81=C4=85tka?= <74246391+LatekVo@users.noreply.github.com> Date: Mon, 11 Mar 2024 10:26:01 +0100 Subject: [PATCH 05/22] Update android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applied m-bert's changes. Co-authored-by: Michał Bert <63123542+m-bert@users.noreply.github.com> --- .../swmansion/gesturehandler/core/FlingGestureHandler.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 217be4c1cd..5dda895f0c 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -41,12 +41,7 @@ class FlingGestureHandler : GestureHandler() { private fun tryEndFling(event: MotionEvent): Boolean { data class SimpleVector(val x: Double, val y: Double) - fun toSafeNumber(unsafe: Double): Double { - return if (unsafe.isFinite()) - unsafe - else - 0.0 - } +fun toSafeNumber(unsafe: Double): Double = if (unsafe.isFinite()) unsafe else 0.0 fun toUnitVector(vec: SimpleVector): SimpleVector { val magnitude = abs(vec.x + vec.y) From fba0a2510a9a096359412788b0a57ac659d26872 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Mon, 11 Mar 2024 12:19:22 +0100 Subject: [PATCH 06/22] Applied changes from review. --- .../core/FlingGestureHandler.kt | 43 +++++++++--------- src/web/handlers/FlingGestureHandler.ts | 44 ++++++++++++------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 217be4c1cd..82f6056c72 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -4,7 +4,6 @@ import android.os.Handler import android.os.Looper import android.view.MotionEvent import android.view.VelocityTracker -import kotlin.math.abs import kotlin.math.hypot class FlingGestureHandler : GestureHandler() { @@ -12,9 +11,8 @@ class FlingGestureHandler : GestureHandler() { var direction = DEFAULT_DIRECTION private val maxDurationMs = DEFAULT_MAX_DURATION_MS - private val minAcceptableDelta = DEFAULT_MIN_ACCEPTABLE_DELTA - private var startX = 0f - private var startY = 0f + private val minVelocity = DEFAULT_MIN_ACCEPTABLE_DELTA + private val minDirectionAlignment = DEFAULT_MIN_DIRECTION_ALIGNMENT private var handler: Handler? = null private var maxNumberOfPointersSimultaneously = 0 private val failDelayed = Runnable { fail() } @@ -26,8 +24,6 @@ class FlingGestureHandler : GestureHandler() { } private fun startFling(event: MotionEvent) { - startX = event.rawX - startY = event.rawY begin() maxNumberOfPointersSimultaneously = 1 if (handler == null) { @@ -41,15 +37,10 @@ class FlingGestureHandler : GestureHandler() { private fun tryEndFling(event: MotionEvent): Boolean { data class SimpleVector(val x: Double, val y: Double) - fun toSafeNumber(unsafe: Double): Double { - return if (unsafe.isFinite()) - unsafe - else - 0.0 - } + fun toSafeNumber(unsafe: Double): Double = if (unsafe.isFinite()) unsafe else 0.0 fun toUnitVector(vec: SimpleVector): SimpleVector { - val magnitude = abs(vec.x + vec.y) + val magnitude = hypot(vec.x, vec.y) // toSafeNumber protects against division by zero return SimpleVector( toSafeNumber(vec.x / magnitude), @@ -67,12 +58,13 @@ class FlingGestureHandler : GestureHandler() { return unitA.x * unitB.x + unitA.y * unitB.y } - val directionVector = SimpleVector( - (if (direction and DIRECTION_LEFT != 0) -1.0 else 0.0) + - (if (direction and DIRECTION_RIGHT != 0) 1.0 else 0.0), - (if (direction and DIRECTION_UP != 0) -1.0 else 0.0) + - (if (direction and DIRECTION_DOWN != 0) 1.0 else 0.0), - ) + fun compareAlignment( + vec: SimpleVector, + directionVec: SimpleVector, + direction: Int + ): Boolean = + compareSimilarity(vec, directionVec) > minDirectionAlignment && + (this.direction and direction != 0) val velocityTracker = VelocityTracker.obtain() addVelocityMovement(velocityTracker, event) @@ -83,13 +75,19 @@ class FlingGestureHandler : GestureHandler() { velocityTracker.yVelocity.toDouble() ) + val alignmentList = arrayOf( + compareAlignment(velocityVector, SimpleVector(-1.0, 0.0), DIRECTION_LEFT), + compareAlignment(velocityVector, SimpleVector(1.0, 0.0), DIRECTION_RIGHT), + compareAlignment(velocityVector, SimpleVector(0.0, -1.0), DIRECTION_UP), + compareAlignment(velocityVector, SimpleVector(0.0, 1.0), DIRECTION_DOWN) + ) + velocityTracker.recycle() - // hypot may be overkill for this simple function, dot product may be sufficient val totalVelocity = hypot(velocityVector.x, velocityVector.y) - val isAligned = compareSimilarity(directionVector, velocityVector) > 0.75 - val isFast = totalVelocity > this.minAcceptableDelta + val isAligned = alignmentList.reduce { any, element -> any or element } + val isFast = totalVelocity > this.minVelocity return if ( maxNumberOfPointersSimultaneously == numberOfPointersRequired && @@ -154,6 +152,7 @@ class FlingGestureHandler : GestureHandler() { companion object { private const val DEFAULT_MAX_DURATION_MS: Long = 800 private const val DEFAULT_MIN_ACCEPTABLE_DELTA: Long = 2000 + private const val DEFAULT_MIN_DIRECTION_ALIGNMENT: Double = 0.75 private const val DEFAULT_DIRECTION = DIRECTION_RIGHT private const val DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1 } diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index 06708fb901..a2a2c8c794 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -5,16 +5,19 @@ import { AdaptedEvent, Config } from '../interfaces'; import GestureHandler from './GestureHandler'; const DEFAULT_MAX_DURATION_MS = 800; -const DEFAULT_MIN_ACCEPTABLE_DELTA = 400; +const DEFAULT_MIN_VELOCITY = 400; +const DEFAULT_MIN_DIRECTION_ALIGNMENT = 0.75; const DEFAULT_DIRECTION = Direction.RIGHT; const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1; export default class FlingGestureHandler extends GestureHandler { private numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED; private direction = DEFAULT_DIRECTION; + private shouldCancelWhenOutside = false; private maxDurationMs = DEFAULT_MAX_DURATION_MS; - private minAcceptableDelta = DEFAULT_MIN_ACCEPTABLE_DELTA; + private minVelocity = DEFAULT_MIN_VELOCITY; + private minDirectionAlignment = DEFAULT_MIN_DIRECTION_ALIGNMENT; private delayTimeout!: number; private maxNumberOfPointersSimultaneously = 0; @@ -22,6 +25,8 @@ export default class FlingGestureHandler extends GestureHandler { public init(ref: number, propsRef: React.RefObject): void { super.init(ref, propsRef); + + this.setShouldCancelWhenOutside(this.shouldCancelWhenOutside); } public updateGestureConfig({ enabled = true, ...props }: Config): void { @@ -52,7 +57,7 @@ export default class FlingGestureHandler extends GestureHandler { }; const toUnitVector = (vec: SimpleVector): SimpleVector => { - const magnitude = Math.abs(vec.x + vec.y); + const magnitude = Math.hypot(vec.x, vec.y); // division by 0 may occur here return { x: toSafeNumber(vec.x / magnitude), @@ -70,13 +75,15 @@ export default class FlingGestureHandler extends GestureHandler { return unitA.x * unitB.x + unitA.y * unitB.y; }; - const directionVector: SimpleVector = { - x: - (this.direction & Direction.LEFT ? -1 : 0) + - (this.direction & Direction.RIGHT ? 1 : 0), - y: - (this.direction & Direction.UP ? -1 : 0) + - (this.direction & Direction.DOWN ? 1 : 0), + const compareAlignment = ( + vec: SimpleVector, + directionVec: SimpleVector, + direction: number + ): boolean => { + return !!( + compareSimilarity(vec, directionVec) > this.minDirectionAlignment && + this.direction & direction + ); }; const velocityVector: SimpleVector = { @@ -84,15 +91,18 @@ export default class FlingGestureHandler extends GestureHandler { y: this.tracker.getVelocityY(this.keyPointer), }; - // hypot may be overkill for this simple function, simple addition would be sufficient + // list of alignments to all activated directions + const alignmentList = [ + compareAlignment(velocityVector, { x: -1, y: 0 }, Direction.LEFT), + compareAlignment(velocityVector, { x: 1, y: 0 }, Direction.RIGHT), + compareAlignment(velocityVector, { x: 0, y: -1 }, Direction.UP), + compareAlignment(velocityVector, { x: 0, y: 1 }, Direction.DOWN), + ]; + const totalVelocity = Math.hypot(velocityVector.x, velocityVector.y); - const movementAlignment = compareSimilarity( - directionVector, - velocityVector - ); - const isAligned = movementAlignment > 0.75; - const isFast = totalVelocity > this.minAcceptableDelta; + const isAligned = alignmentList.reduce((any, element) => any || element); + const isFast = totalVelocity > this.minVelocity; if ( this.maxNumberOfPointersSimultaneously === From 0e94b6f968227b5b2eb1dbc7446f15f52427027e Mon Sep 17 00:00:00 2001 From: LatekVo Date: Mon, 11 Mar 2024 12:27:52 +0100 Subject: [PATCH 07/22] Applied review suggestions. --- .../com/swmansion/gesturehandler/core/FlingGestureHandler.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 82f6056c72..35088fc31b 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -11,7 +11,7 @@ class FlingGestureHandler : GestureHandler() { var direction = DEFAULT_DIRECTION private val maxDurationMs = DEFAULT_MAX_DURATION_MS - private val minVelocity = DEFAULT_MIN_ACCEPTABLE_DELTA + private val minVelocity = DEFAULT_MIN_VELOCITY private val minDirectionAlignment = DEFAULT_MIN_DIRECTION_ALIGNMENT private var handler: Handler? = null private var maxNumberOfPointersSimultaneously = 0 @@ -151,7 +151,7 @@ class FlingGestureHandler : GestureHandler() { companion object { private const val DEFAULT_MAX_DURATION_MS: Long = 800 - private const val DEFAULT_MIN_ACCEPTABLE_DELTA: Long = 2000 + private const val DEFAULT_MIN_VELOCITY: Long = 2000 private const val DEFAULT_MIN_DIRECTION_ALIGNMENT: Double = 0.75 private const val DEFAULT_DIRECTION = DIRECTION_RIGHT private const val DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1 From adb8546e032e92ee4d1ee4f6d676aad3916fa920 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Mon, 11 Mar 2024 13:36:15 +0100 Subject: [PATCH 08/22] Fix pointer not being detected out of bounds on web. --- src/web/handlers/FlingGestureHandler.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index a2a2c8c794..e29ace8ec3 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -163,7 +163,7 @@ export default class FlingGestureHandler extends GestureHandler { } } - protected onPointerMove(event: AdaptedEvent): void { + protected pointerMove(event: AdaptedEvent): void { this.tracker.track(event); if (this.currentState !== State.BEGAN) { @@ -175,6 +175,14 @@ export default class FlingGestureHandler extends GestureHandler { super.onPointerMove(event); } + protected onPointerMove(event: AdaptedEvent): void { + this.pointerMove(event); + } + + protected onPointerOutOfBounds(event: AdaptedEvent): void { + this.pointerMove(event); + } + protected onPointerUp(event: AdaptedEvent): void { super.onPointerUp(event); this.onUp(event); From 555862e59f40c3d3bf74094ebd2b2c244504cc8c Mon Sep 17 00:00:00 2001 From: LatekVo Date: Mon, 11 Mar 2024 13:48:03 +0100 Subject: [PATCH 09/22] Simplify code. --- .../com/swmansion/gesturehandler/core/FlingGestureHandler.kt | 2 +- src/web/handlers/FlingGestureHandler.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 35088fc31b..5782352413 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -86,7 +86,7 @@ class FlingGestureHandler : GestureHandler() { val totalVelocity = hypot(velocityVector.x, velocityVector.y) - val isAligned = alignmentList.reduce { any, element -> any or element } + val isAligned = alignmentList.any { it } val isFast = totalVelocity > this.minVelocity return if ( diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index e29ace8ec3..e42fb8f15c 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -13,7 +13,6 @@ const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1; export default class FlingGestureHandler extends GestureHandler { private numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED; private direction = DEFAULT_DIRECTION; - private shouldCancelWhenOutside = false; private maxDurationMs = DEFAULT_MAX_DURATION_MS; private minVelocity = DEFAULT_MIN_VELOCITY; @@ -26,7 +25,7 @@ export default class FlingGestureHandler extends GestureHandler { public init(ref: number, propsRef: React.RefObject): void { super.init(ref, propsRef); - this.setShouldCancelWhenOutside(this.shouldCancelWhenOutside); + this.setShouldCancelWhenOutside(this.shouldCancellWhenOutside); } public updateGestureConfig({ enabled = true, ...props }: Config): void { @@ -101,7 +100,7 @@ export default class FlingGestureHandler extends GestureHandler { const totalVelocity = Math.hypot(velocityVector.x, velocityVector.y); - const isAligned = alignmentList.reduce((any, element) => any || element); + const isAligned = alignmentList.some((element) => element); const isFast = totalVelocity > this.minVelocity; if ( From 5344a7a88cf1668c3e77e1d6d06a9783647e93da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacy=20=C5=81=C4=85tka?= <74246391+LatekVo@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:07:09 +0100 Subject: [PATCH 10/22] Apply review suggestions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Bert <63123542+m-bert@users.noreply.github.com> --- src/web/handlers/FlingGestureHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index e42fb8f15c..bb01e85b0d 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -162,7 +162,7 @@ export default class FlingGestureHandler extends GestureHandler { } } - protected pointerMove(event: AdaptedEvent): void { + private pointerMove(event: AdaptedEvent): void { this.tracker.track(event); if (this.currentState !== State.BEGAN) { From 92e719109b2d2ced33d1b0a4e9112840d95c27c5 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Tue, 12 Mar 2024 09:55:57 +0100 Subject: [PATCH 11/22] Optimization --- src/web/handlers/FlingGestureHandler.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index e42fb8f15c..a82de5ac3f 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -68,10 +68,9 @@ export default class FlingGestureHandler extends GestureHandler { vecA: SimpleVector, vecB: SimpleVector ): number => { - const unitA = toUnitVector(vecA); - const unitB = toUnitVector(vecB); + // both inputs are required to be unit vectors // returns scalar on range from -1.0 to 1.0 - return unitA.x * unitB.x + unitA.y * unitB.y; + return vecA.x * vecB.x + vecA.y * vecB.y; }; const compareAlignment = ( @@ -90,12 +89,14 @@ export default class FlingGestureHandler extends GestureHandler { y: this.tracker.getVelocityY(this.keyPointer), }; + const velocityUnitVector = toUnitVector(velocityVector); + // list of alignments to all activated directions const alignmentList = [ - compareAlignment(velocityVector, { x: -1, y: 0 }, Direction.LEFT), - compareAlignment(velocityVector, { x: 1, y: 0 }, Direction.RIGHT), - compareAlignment(velocityVector, { x: 0, y: -1 }, Direction.UP), - compareAlignment(velocityVector, { x: 0, y: 1 }, Direction.DOWN), + compareAlignment(velocityUnitVector, { x: -1, y: 0 }, Direction.LEFT), + compareAlignment(velocityUnitVector, { x: 1, y: 0 }, Direction.RIGHT), + compareAlignment(velocityUnitVector, { x: 0, y: -1 }, Direction.UP), + compareAlignment(velocityUnitVector, { x: 0, y: 1 }, Direction.DOWN), ]; const totalVelocity = Math.hypot(velocityVector.x, velocityVector.y); From dd34b38e0704990fa51a4265227cadb191ecfc4e Mon Sep 17 00:00:00 2001 From: LatekVo Date: Tue, 12 Mar 2024 10:05:36 +0100 Subject: [PATCH 12/22] Apply changes to kotlin code --- .../gesturehandler/core/FlingGestureHandler.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 5782352413..675f0a3924 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -52,10 +52,9 @@ class FlingGestureHandler : GestureHandler() { vecA: SimpleVector, vecB: SimpleVector ): Double { - val unitA = toUnitVector(vecA) - val unitB = toUnitVector(vecB) + // inputs have to be unit vectors // returns scalar on range from -1.0 to 1.0 - return unitA.x * unitB.x + unitA.y * unitB.y + return vecA.x * vecB.x + vecA.y * vecB.y } fun compareAlignment( @@ -75,11 +74,13 @@ class FlingGestureHandler : GestureHandler() { velocityTracker.yVelocity.toDouble() ) + val velocityUnitVector = toUnitVector(velocityVector) + val alignmentList = arrayOf( - compareAlignment(velocityVector, SimpleVector(-1.0, 0.0), DIRECTION_LEFT), - compareAlignment(velocityVector, SimpleVector(1.0, 0.0), DIRECTION_RIGHT), - compareAlignment(velocityVector, SimpleVector(0.0, -1.0), DIRECTION_UP), - compareAlignment(velocityVector, SimpleVector(0.0, 1.0), DIRECTION_DOWN) + compareAlignment(velocityUnitVector, SimpleVector(-1.0, 0.0), DIRECTION_LEFT), + compareAlignment(velocityUnitVector, SimpleVector(1.0, 0.0), DIRECTION_RIGHT), + compareAlignment(velocityUnitVector, SimpleVector(0.0, -1.0), DIRECTION_UP), + compareAlignment(velocityUnitVector, SimpleVector(0.0, 1.0), DIRECTION_DOWN) ) velocityTracker.recycle() From 13138beff13d472b8aba4f6febfe17e685d8920a Mon Sep 17 00:00:00 2001 From: LatekVo Date: Tue, 12 Mar 2024 13:03:00 +0100 Subject: [PATCH 13/22] Move code into a separate Vector class. Decrease sensitivity. --- src/web/handlers/FlingGestureHandler.ts | 110 +++++++++++++----------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index f546110ed7..05d70dadee 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -1,22 +1,64 @@ import { State } from '../../State'; import { Direction } from '../constants'; import { AdaptedEvent, Config } from '../interfaces'; +import PointerTracker from '../tools/PointerTracker'; import GestureHandler from './GestureHandler'; const DEFAULT_MAX_DURATION_MS = 800; -const DEFAULT_MIN_VELOCITY = 400; +const DEFAULT_MIN_VELOCITY = 700; const DEFAULT_MIN_DIRECTION_ALIGNMENT = 0.75; const DEFAULT_DIRECTION = Direction.RIGHT; const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1; +class Vector { + x: number = 0; + y: number = 0; + uX: number = 0; + uY: number = 0; + + fromDirection(direction: number) { + [this.x, this.y] = [this.uX, this.uY] = new Map([ + [Direction.LEFT, [-1, 0]], + [Direction.RIGHT, [1, 0]], + [Direction.UP, [0, -1]], + [Direction.DOWN, [0, 1]], + ]).get(direction) ?? [0.0, 0.0]; + + return this; + } + + fromVelocity(tracker: PointerTracker, pointerId: number) { + this.x = tracker.getVelocityX(pointerId); + this.y = tracker.getVelocityY(pointerId); + + const magnitude = Math.hypot(this.x, this.y); + if (magnitude < 0.001) { + this.uX = this.uY = 0; + } + + this.uX = this.x / magnitude; + this.uY = this.y / magnitude; + + return this; + } + + computeSimilarity(vector: any) { + return this.uX * vector.uX + this.uY * vector.uY; + } + + computeMagnitude() { + return Math.hypot(this.x, this.y); + } +} + export default class FlingGestureHandler extends GestureHandler { private numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED; private direction = DEFAULT_DIRECTION; private maxDurationMs = DEFAULT_MAX_DURATION_MS; private minVelocity = DEFAULT_MIN_VELOCITY; - private minDirectionAlignment = DEFAULT_MIN_DIRECTION_ALIGNMENT; + private minDirectionalAlignment = DEFAULT_MIN_DIRECTION_ALIGNMENT; private delayTimeout!: number; private maxNumberOfPointersSimultaneously = 0; @@ -49,60 +91,26 @@ export default class FlingGestureHandler extends GestureHandler { } private tryEndFling(): boolean { - type SimpleVector = { x: number; y: number }; - - const toSafeNumber = (unsafe: number): number => { - return Number.isNaN(unsafe) ? 0 : unsafe; - }; - - const toUnitVector = (vec: SimpleVector): SimpleVector => { - const magnitude = Math.hypot(vec.x, vec.y); - // division by 0 may occur here - return { - x: toSafeNumber(vec.x / magnitude), - y: toSafeNumber(vec.y / magnitude), - }; - }; - - const compareSimilarity = ( - vecA: SimpleVector, - vecB: SimpleVector - ): number => { - // both inputs are required to be unit vectors - // returns scalar on range from -1.0 to 1.0 - return vecA.x * vecB.x + vecA.y * vecB.y; - }; - - const compareAlignment = ( - vec: SimpleVector, - directionVec: SimpleVector, - direction: number - ): boolean => { - return !!( - compareSimilarity(vec, directionVec) > this.minDirectionAlignment && - this.direction & direction - ); - }; - - const velocityVector: SimpleVector = { - x: this.tracker.getVelocityX(this.keyPointer), - y: this.tracker.getVelocityY(this.keyPointer), - }; - - const velocityUnitVector = toUnitVector(velocityVector); + const velocityVector = new Vector().fromVelocity( + this.tracker, + this.keyPointer + ); // list of alignments to all activated directions const alignmentList = [ - compareAlignment(velocityUnitVector, { x: -1, y: 0 }, Direction.LEFT), - compareAlignment(velocityUnitVector, { x: 1, y: 0 }, Direction.RIGHT), - compareAlignment(velocityUnitVector, { x: 0, y: -1 }, Direction.UP), - compareAlignment(velocityUnitVector, { x: 0, y: 1 }, Direction.DOWN), - ]; - - const totalVelocity = Math.hypot(velocityVector.x, velocityVector.y); + Direction.LEFT, + Direction.RIGHT, + Direction.UP, + Direction.DOWN, + ].map( + (direction) => + velocityVector.computeSimilarity( + new Vector().fromDirection(direction) + ) > this.minDirectionalAlignment && direction & this.direction + ); const isAligned = alignmentList.some((element) => element); - const isFast = totalVelocity > this.minVelocity; + const isFast = velocityVector.computeMagnitude() > this.minVelocity; if ( this.maxNumberOfPointersSimultaneously === From 63007f4c37f3e67340e6d5f6434bef54e0b88f0f Mon Sep 17 00:00:00 2001 From: LatekVo Date: Tue, 12 Mar 2024 13:53:04 +0100 Subject: [PATCH 14/22] Apply changes from JS to KT: separate logic into a separate class --- .../core/FlingGestureHandler.kt | 95 +++++++++++-------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 675f0a3924..9f84588edf 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -4,15 +4,62 @@ import android.os.Handler import android.os.Looper import android.view.MotionEvent import android.view.VelocityTracker +import com.swmansion.gesturehandler.core.GestureHandler.Companion import kotlin.math.hypot +class Vector { + var x: Double = 0.0 + var y: Double = 0.0 + var uX: Double = 0.0 + var uY: Double = 0.0 + + fun fromDirection(direction: Int) = also { + val (x, y) = when (direction) { + Companion.DIRECTION_LEFT -> Pair(-1.0, 0.0) + Companion.DIRECTION_RIGHT -> Pair(1.0, 0.0) + Companion.DIRECTION_UP -> Pair(0.0, -1.0) + Companion.DIRECTION_DOWN -> Pair(0.0, 1.0) + else -> Pair(0.0, 0.0) + } + + this.x = x + this.uX = x + this.y = y + this.uY = y + } + + fun fromVelocity(tracker: VelocityTracker) = also { + tracker.computeCurrentVelocity(1000) + + this.x = tracker.xVelocity.toDouble() + this.y = tracker.yVelocity.toDouble() + + val magnitude = hypot(this.x, this.y) + if (magnitude < 0.001) { + this.uX = 0.0 + this.uY = 0.0 + } + + this.uX = this.x / magnitude + this.uY = this.y / magnitude + } + + fun computeSimilarity(vector: Vector): Double { + return this.uX * vector.uX + this.uY * vector.uY + } + + fun computeMagnitude(): Double { + return hypot(this.x, this.y) + } +} + class FlingGestureHandler : GestureHandler() { var numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED var direction = DEFAULT_DIRECTION private val maxDurationMs = DEFAULT_MAX_DURATION_MS private val minVelocity = DEFAULT_MIN_VELOCITY - private val minDirectionAlignment = DEFAULT_MIN_DIRECTION_ALIGNMENT + private val minDirectionalAlignment = DEFAULT_MIN_DIRECTION_ALIGNMENT private var handler: Handler? = null private var maxNumberOfPointersSimultaneously = 0 private val failDelayed = Runnable { fail() } @@ -35,57 +82,31 @@ class FlingGestureHandler : GestureHandler() { } private fun tryEndFling(event: MotionEvent): Boolean { - data class SimpleVector(val x: Double, val y: Double) - - fun toSafeNumber(unsafe: Double): Double = if (unsafe.isFinite()) unsafe else 0.0 - - fun toUnitVector(vec: SimpleVector): SimpleVector { - val magnitude = hypot(vec.x, vec.y) - // toSafeNumber protects against division by zero - return SimpleVector( - toSafeNumber(vec.x / magnitude), - toSafeNumber(vec.y / magnitude) - ) - } - - fun compareSimilarity( - vecA: SimpleVector, - vecB: SimpleVector - ): Double { - // inputs have to be unit vectors - // returns scalar on range from -1.0 to 1.0 - return vecA.x * vecB.x + vecA.y * vecB.y - } fun compareAlignment( - vec: SimpleVector, - directionVec: SimpleVector, - direction: Int + vector: Vector, + direction: Int, + directionVec: Vector = Vector().fromDirection(direction), ): Boolean = - compareSimilarity(vec, directionVec) > minDirectionAlignment && + vector.computeSimilarity(directionVec) > minDirectionalAlignment && (this.direction and direction != 0) val velocityTracker = VelocityTracker.obtain() addVelocityMovement(velocityTracker, event) velocityTracker!!.computeCurrentVelocity(1000) - val velocityVector = SimpleVector( - velocityTracker.xVelocity.toDouble(), - velocityTracker.yVelocity.toDouble() - ) - - val velocityUnitVector = toUnitVector(velocityVector) + val velocityVector = Vector().fromVelocity(velocityTracker) val alignmentList = arrayOf( - compareAlignment(velocityUnitVector, SimpleVector(-1.0, 0.0), DIRECTION_LEFT), - compareAlignment(velocityUnitVector, SimpleVector(1.0, 0.0), DIRECTION_RIGHT), - compareAlignment(velocityUnitVector, SimpleVector(0.0, -1.0), DIRECTION_UP), - compareAlignment(velocityUnitVector, SimpleVector(0.0, 1.0), DIRECTION_DOWN) + compareAlignment(velocityVector, DIRECTION_LEFT), + compareAlignment(velocityVector, DIRECTION_RIGHT), + compareAlignment(velocityVector, DIRECTION_UP), + compareAlignment(velocityVector, DIRECTION_DOWN) ) velocityTracker.recycle() - val totalVelocity = hypot(velocityVector.x, velocityVector.y) + val totalVelocity = velocityVector.computeMagnitude() val isAligned = alignmentList.any { it } val isFast = totalVelocity > this.minVelocity From bf8693af2fb5ce1a582bc61e5ed35ba44528de98 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Tue, 12 Mar 2024 14:27:44 +0100 Subject: [PATCH 15/22] Minor cleanup. --- .../gesturehandler/core/FlingGestureHandler.kt | 13 +++++-------- src/web/handlers/FlingGestureHandler.ts | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 9f84588edf..3c49861a3d 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -10,8 +10,8 @@ import kotlin.math.hypot class Vector { var x: Double = 0.0 var y: Double = 0.0 - var uX: Double = 0.0 - var uY: Double = 0.0 + private var uX: Double = 0.0 + private var uY: Double = 0.0 fun fromDirection(direction: Int) = also { val (x, y) = when (direction) { @@ -93,10 +93,11 @@ class FlingGestureHandler : GestureHandler() { val velocityTracker = VelocityTracker.obtain() addVelocityMovement(velocityTracker, event) - velocityTracker!!.computeCurrentVelocity(1000) val velocityVector = Vector().fromVelocity(velocityTracker) + velocityTracker.recycle() + val alignmentList = arrayOf( compareAlignment(velocityVector, DIRECTION_LEFT), compareAlignment(velocityVector, DIRECTION_RIGHT), @@ -104,12 +105,8 @@ class FlingGestureHandler : GestureHandler() { compareAlignment(velocityVector, DIRECTION_DOWN) ) - velocityTracker.recycle() - - val totalVelocity = velocityVector.computeMagnitude() - val isAligned = alignmentList.any { it } - val isFast = totalVelocity > this.minVelocity + val isFast = velocityVector.computeMagnitude() > this.minVelocity return if ( maxNumberOfPointersSimultaneously == numberOfPointersRequired && diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index 05d70dadee..e59f8c0547 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -23,7 +23,7 @@ class Vector { [Direction.RIGHT, [1, 0]], [Direction.UP, [0, -1]], [Direction.DOWN, [0, 1]], - ]).get(direction) ?? [0.0, 0.0]; + ]).get(direction) ?? [0, 0]; return this; } From 774bdb1a56f2b7472930020716fc38a7717bcc09 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Wed, 13 Mar 2024 11:35:08 +0100 Subject: [PATCH 16/22] - moved Vector class to separate file - changed to static and getter where possible - removed Direction from constants.ts - applied other review suggestions --- .../core/FlingGestureHandler.kt | 77 ++++-------------- .../swmansion/gesturehandler/core/Vector.kt | 76 ++++++++++++++++++ src/web/constants.ts | 7 -- src/web/handlers/FlingGestureHandler.ts | 78 ++++--------------- src/web/tools/Vector.ts | 58 ++++++++++++++ 5 files changed, 165 insertions(+), 131 deletions(-) create mode 100644 android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt create mode 100644 src/web/tools/Vector.ts diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 3c49861a3d..7178966ea7 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -4,54 +4,6 @@ import android.os.Handler import android.os.Looper import android.view.MotionEvent import android.view.VelocityTracker -import com.swmansion.gesturehandler.core.GestureHandler.Companion -import kotlin.math.hypot - -class Vector { - var x: Double = 0.0 - var y: Double = 0.0 - private var uX: Double = 0.0 - private var uY: Double = 0.0 - - fun fromDirection(direction: Int) = also { - val (x, y) = when (direction) { - Companion.DIRECTION_LEFT -> Pair(-1.0, 0.0) - Companion.DIRECTION_RIGHT -> Pair(1.0, 0.0) - Companion.DIRECTION_UP -> Pair(0.0, -1.0) - Companion.DIRECTION_DOWN -> Pair(0.0, 1.0) - else -> Pair(0.0, 0.0) - } - - this.x = x - this.uX = x - this.y = y - this.uY = y - } - - fun fromVelocity(tracker: VelocityTracker) = also { - tracker.computeCurrentVelocity(1000) - - this.x = tracker.xVelocity.toDouble() - this.y = tracker.yVelocity.toDouble() - - val magnitude = hypot(this.x, this.y) - if (magnitude < 0.001) { - this.uX = 0.0 - this.uY = 0.0 - } - - this.uX = this.x / magnitude - this.uY = this.y / magnitude - } - - fun computeSimilarity(vector: Vector): Double { - return this.uX * vector.uX + this.uY * vector.uY - } - - fun computeMagnitude(): Double { - return hypot(this.x, this.y) - } -} class FlingGestureHandler : GestureHandler() { var numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED @@ -82,15 +34,6 @@ class FlingGestureHandler : GestureHandler() { } private fun tryEndFling(event: MotionEvent): Boolean { - - fun compareAlignment( - vector: Vector, - direction: Int, - directionVec: Vector = Vector().fromDirection(direction), - ): Boolean = - vector.computeSimilarity(directionVec) > minDirectionalAlignment && - (this.direction and direction != 0) - val velocityTracker = VelocityTracker.obtain() addVelocityMovement(velocityTracker, event) @@ -98,15 +41,23 @@ class FlingGestureHandler : GestureHandler() { velocityTracker.recycle() - val alignmentList = arrayOf( - compareAlignment(velocityVector, DIRECTION_LEFT), - compareAlignment(velocityVector, DIRECTION_RIGHT), - compareAlignment(velocityVector, DIRECTION_UP), - compareAlignment(velocityVector, DIRECTION_DOWN) + fun getVelocityAlignment( + direction: Int, + directionVec: Vector = Vector.fromDirection(direction), + ): Boolean = ( + this.direction and direction != 0 && + velocityVector.isSimilar(directionVec, minDirectionalAlignment) ) + val alignmentList = arrayOf( + DIRECTION_LEFT, + DIRECTION_RIGHT, + DIRECTION_UP, + DIRECTION_DOWN, + ).map { direction -> getVelocityAlignment(direction) } + val isAligned = alignmentList.any { it } - val isFast = velocityVector.computeMagnitude() > this.minVelocity + val isFast = velocityVector.magnitude > this.minVelocity return if ( maxNumberOfPointersSimultaneously == numberOfPointersRequired && diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt b/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt new file mode 100644 index 0000000000..433c3dffd3 --- /dev/null +++ b/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt @@ -0,0 +1,76 @@ +package com.swmansion.gesturehandler.core + +import android.view.VelocityTracker +import com.swmansion.gesturehandler.core.GestureHandler.Companion.DIRECTION_DOWN +import com.swmansion.gesturehandler.core.GestureHandler.Companion.DIRECTION_LEFT +import com.swmansion.gesturehandler.core.GestureHandler.Companion.DIRECTION_RIGHT +import com.swmansion.gesturehandler.core.GestureHandler.Companion.DIRECTION_UP +import kotlin.math.hypot + +class Vector { + var x: Double = 0.0 + var y: Double = 0.0 + private var unitX: Double = 0.0 + private var unitY: Double = 0.0 + + fun fromDirection(direction: Int) = also { + val (x, y) = when (direction) { + DIRECTION_LEFT -> Pair(-1.0, 0.0) + DIRECTION_RIGHT -> Pair(1.0, 0.0) + DIRECTION_UP -> Pair(0.0, -1.0) + DIRECTION_DOWN -> Pair(0.0, 1.0) + else -> Pair(0.0, 0.0) + } + + this.x = x + this.y = y + this.unitX = x + this.unitY = y + } + + fun fromVelocity(tracker: VelocityTracker) = also { + tracker.computeCurrentVelocity(1000) + + this.x = tracker.xVelocity.toDouble() + this.y = tracker.yVelocity.toDouble() + + val magnitude = this.magnitude + if (magnitude < 1) { + this.unitX = 0.0 + this.unitY = 0.0 + } else { + this.unitX = this.x / magnitude + this.unitY = this.y / magnitude + } + + } + + fun computeSimilarity(vector: Vector): Double { + return this.unitX * vector.unitX + this.unitY * vector.unitY + } + + fun isSimilar(vector: Vector, threshold: Double): Boolean { + return this.computeSimilarity(vector) > threshold + } + + val magnitude + get() = hypot(this.x, this.y) + + companion object { + val VECTOR_LEFT: Vector = Vector().fromDirection(DIRECTION_LEFT) + val VECTOR_RIGHT: Vector = Vector().fromDirection(DIRECTION_RIGHT) + val VECTOR_UP: Vector = Vector().fromDirection(DIRECTION_UP) + val VECTOR_DOWN: Vector = Vector().fromDirection(DIRECTION_DOWN) + val VECTOR_ZERO: Vector = Vector() + + fun fromDirection(direction: Int): Vector = + when (direction) { + DIRECTION_LEFT -> VECTOR_LEFT + DIRECTION_RIGHT -> VECTOR_RIGHT + DIRECTION_UP -> VECTOR_UP + DIRECTION_DOWN -> VECTOR_DOWN + else -> VECTOR_ZERO + } + + } +} diff --git a/src/web/constants.ts b/src/web/constants.ts index 51aba8a237..9f514ba224 100644 --- a/src/web/constants.ts +++ b/src/web/constants.ts @@ -1,8 +1 @@ export const DEFAULT_TOUCH_SLOP = 15; - -export const Direction = { - RIGHT: 1, - LEFT: 2, - UP: 4, - DOWN: 8, -}; diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index e59f8c0547..3eecdf28cd 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -1,60 +1,19 @@ import { State } from '../../State'; -import { Direction } from '../constants'; +import { Directions } from '../../Directions'; import { AdaptedEvent, Config } from '../interfaces'; -import PointerTracker from '../tools/PointerTracker'; import GestureHandler from './GestureHandler'; +import Vector, { DirectionTypeVectorMappings } from '../tools/Vector'; const DEFAULT_MAX_DURATION_MS = 800; const DEFAULT_MIN_VELOCITY = 700; const DEFAULT_MIN_DIRECTION_ALIGNMENT = 0.75; -const DEFAULT_DIRECTION = Direction.RIGHT; +const DEFAULT_DIRECTION = Directions.RIGHT; const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1; -class Vector { - x: number = 0; - y: number = 0; - uX: number = 0; - uY: number = 0; - - fromDirection(direction: number) { - [this.x, this.y] = [this.uX, this.uY] = new Map([ - [Direction.LEFT, [-1, 0]], - [Direction.RIGHT, [1, 0]], - [Direction.UP, [0, -1]], - [Direction.DOWN, [0, 1]], - ]).get(direction) ?? [0, 0]; - - return this; - } - - fromVelocity(tracker: PointerTracker, pointerId: number) { - this.x = tracker.getVelocityX(pointerId); - this.y = tracker.getVelocityY(pointerId); - - const magnitude = Math.hypot(this.x, this.y); - if (magnitude < 0.001) { - this.uX = this.uY = 0; - } - - this.uX = this.x / magnitude; - this.uY = this.y / magnitude; - - return this; - } - - computeSimilarity(vector: any) { - return this.uX * vector.uX + this.uY * vector.uY; - } - - computeMagnitude() { - return Math.hypot(this.x, this.y); - } -} - export default class FlingGestureHandler extends GestureHandler { private numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED; - private direction = DEFAULT_DIRECTION; + private direction: Directions = DEFAULT_DIRECTION; private maxDurationMs = DEFAULT_MAX_DURATION_MS; private minVelocity = DEFAULT_MIN_VELOCITY; @@ -66,8 +25,6 @@ export default class FlingGestureHandler extends GestureHandler { public init(ref: number, propsRef: React.RefObject): void { super.init(ref, propsRef); - - this.setShouldCancelWhenOutside(this.shouldCancellWhenOutside); } public updateGestureConfig({ enabled = true, ...props }: Config): void { @@ -96,21 +53,20 @@ export default class FlingGestureHandler extends GestureHandler { this.keyPointer ); + const getAlignment = (direction: Directions) => { + const directionVector = DirectionTypeVectorMappings.get(direction); + return ( + directionVector && + direction & this.direction && + velocityVector.isSimilar(directionVector, this.minDirectionalAlignment) + ); + }; + // list of alignments to all activated directions - const alignmentList = [ - Direction.LEFT, - Direction.RIGHT, - Direction.UP, - Direction.DOWN, - ].map( - (direction) => - velocityVector.computeSimilarity( - new Vector().fromDirection(direction) - ) > this.minDirectionalAlignment && direction & this.direction - ); + const alignmentList = Object.values(Directions).map(getAlignment); const isAligned = alignmentList.some((element) => element); - const isFast = velocityVector.computeMagnitude() > this.minVelocity; + const isFast = velocityVector.magnitude > this.minVelocity; if ( this.maxNumberOfPointersSimultaneously === @@ -179,16 +135,16 @@ export default class FlingGestureHandler extends GestureHandler { } this.tryEndFling(); - - super.onPointerMove(event); } protected onPointerMove(event: AdaptedEvent): void { this.pointerMove(event); + super.onPointerMove(event); } protected onPointerOutOfBounds(event: AdaptedEvent): void { this.pointerMove(event); + super.onPointerOutOfBounds(event); } protected onPointerUp(event: AdaptedEvent): void { diff --git a/src/web/tools/Vector.ts b/src/web/tools/Vector.ts new file mode 100644 index 0000000000..05ed552874 --- /dev/null +++ b/src/web/tools/Vector.ts @@ -0,0 +1,58 @@ +import { Directions } from '../../Directions'; +import PointerTracker from './PointerTracker'; + +export default class Vector { + x: number = 0; + y: number = 0; + unitX: number = 0; + unitY: number = 0; + + fromDirection(direction: Directions) { + [this.x, this.y] = [this.unitX, this.unitY] = DirectionTypeMappings.get( + direction + ) ?? [0, 0]; + + return this; + } + + fromVelocity(tracker: PointerTracker, pointerId: number) { + this.x = tracker.getVelocityX(pointerId); + this.y = tracker.getVelocityY(pointerId); + + const magnitude = Math.hypot(this.x, this.y); + if (magnitude < 1) { + this.unitX = this.unitY = 0; + } else { + this.unitX = this.x / magnitude; + this.unitY = this.y / magnitude; + } + + return this; + } + + computeSimilarity(vector: Vector) { + return this.unitX * vector.unitX + this.unitY * vector.unitY; + } + + isSimilar(vector: Vector, threshold: number) { + return this.computeSimilarity(vector) > threshold; + } + + get magnitude() { + return Math.hypot(this.x, this.y); + } +} + +const DirectionTypeMappings = new Map>([ + [Directions.LEFT, [-1, 0]], + [Directions.RIGHT, [1, 0]], + [Directions.UP, [0, -1]], + [Directions.DOWN, [0, 1]], +]); + +export const DirectionTypeVectorMappings = new Map( + Object.values(Directions).map((direction) => [ + direction, + new Vector().fromDirection(direction), + ]) +); From d3cb42cee70d0fc8da95b4c437d40cff20ebb870 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Wed, 13 Mar 2024 13:06:05 +0100 Subject: [PATCH 17/22] Applied cosmetic review suggestions. --- .../core/FlingGestureHandler.kt | 4 +-- .../swmansion/gesturehandler/core/Vector.kt | 33 +++++++++---------- src/web/handlers/FlingGestureHandler.ts | 11 +++---- src/web/tools/Vector.ts | 20 +++++------ 4 files changed, 30 insertions(+), 38 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 7178966ea7..6e8588b1b3 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -43,10 +43,10 @@ class FlingGestureHandler : GestureHandler() { fun getVelocityAlignment( direction: Int, - directionVec: Vector = Vector.fromDirection(direction), + directionVector: Vector = Vector.fromDirection(direction) ): Boolean = ( this.direction and direction != 0 && - velocityVector.isSimilar(directionVec, minDirectionalAlignment) + velocityVector.isSimilar(directionVector, minDirectionalAlignment) ) val alignmentList = arrayOf( diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt b/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt index 433c3dffd3..03076601c4 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt @@ -14,7 +14,7 @@ class Vector { private var unitY: Double = 0.0 fun fromDirection(direction: Int) = also { - val (x, y) = when (direction) { + val (newX, newY) = when (direction) { DIRECTION_LEFT -> Pair(-1.0, 0.0) DIRECTION_RIGHT -> Pair(1.0, 0.0) DIRECTION_UP -> Pair(0.0, -1.0) @@ -22,39 +22,36 @@ class Vector { else -> Pair(0.0, 0.0) } - this.x = x - this.y = y - this.unitX = x - this.unitY = y + x = newX + y = newY + unitX = newX + unitY = newY } fun fromVelocity(tracker: VelocityTracker) = also { tracker.computeCurrentVelocity(1000) - this.x = tracker.xVelocity.toDouble() - this.y = tracker.yVelocity.toDouble() + x = tracker.xVelocity.toDouble() + y = tracker.yVelocity.toDouble() - val magnitude = this.magnitude - if (magnitude < 1) { - this.unitX = 0.0 - this.unitY = 0.0 - } else { - this.unitX = this.x / magnitude - this.unitY = this.y / magnitude - } + val minimalMagnitude = 1 + val isMagnitudeSufficient = magnitude > minimalMagnitude + + unitX = if (isMagnitudeSufficient) x / magnitude else 0.0 + unitY = if (isMagnitudeSufficient) y / magnitude else 0.0 } fun computeSimilarity(vector: Vector): Double { - return this.unitX * vector.unitX + this.unitY * vector.unitY + return unitX * vector.unitX + unitY * vector.unitY } fun isSimilar(vector: Vector, threshold: Double): Boolean { - return this.computeSimilarity(vector) > threshold + return computeSimilarity(vector) > threshold } val magnitude - get() = hypot(this.x, this.y) + get() = hypot(x, y) companion object { val VECTOR_LEFT: Vector = Vector().fromDirection(DIRECTION_LEFT) diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index 3eecdf28cd..79a70c628f 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -3,7 +3,7 @@ import { Directions } from '../../Directions'; import { AdaptedEvent, Config } from '../interfaces'; import GestureHandler from './GestureHandler'; -import Vector, { DirectionTypeVectorMappings } from '../tools/Vector'; +import Vector, { DirectionToVectorMappings } from '../tools/Vector'; const DEFAULT_MAX_DURATION_MS = 800; const DEFAULT_MIN_VELOCITY = 700; @@ -54,9 +54,8 @@ export default class FlingGestureHandler extends GestureHandler { ); const getAlignment = (direction: Directions) => { - const directionVector = DirectionTypeVectorMappings.get(direction); + const directionVector = DirectionToVectorMappings.get(direction)!; return ( - directionVector && direction & this.direction && velocityVector.isSimilar(directionVector, this.minDirectionalAlignment) ); @@ -127,7 +126,7 @@ export default class FlingGestureHandler extends GestureHandler { } } - private pointerMove(event: AdaptedEvent): void { + private pointerMoveAction(event: AdaptedEvent): void { this.tracker.track(event); if (this.currentState !== State.BEGAN) { @@ -138,12 +137,12 @@ export default class FlingGestureHandler extends GestureHandler { } protected onPointerMove(event: AdaptedEvent): void { - this.pointerMove(event); + this.pointerMoveAction(event); super.onPointerMove(event); } protected onPointerOutOfBounds(event: AdaptedEvent): void { - this.pointerMove(event); + this.pointerMoveAction(event); super.onPointerOutOfBounds(event); } diff --git a/src/web/tools/Vector.ts b/src/web/tools/Vector.ts index 05ed552874..a203517ebc 100644 --- a/src/web/tools/Vector.ts +++ b/src/web/tools/Vector.ts @@ -8,9 +8,7 @@ export default class Vector { unitY: number = 0; fromDirection(direction: Directions) { - [this.x, this.y] = [this.unitX, this.unitY] = DirectionTypeMappings.get( - direction - ) ?? [0, 0]; + [this.x, this.y] = [this.unitX, this.unitY] = DirectionMappings.get(direction)!; return this; } @@ -19,13 +17,11 @@ export default class Vector { this.x = tracker.getVelocityX(pointerId); this.y = tracker.getVelocityY(pointerId); - const magnitude = Math.hypot(this.x, this.y); - if (magnitude < 1) { - this.unitX = this.unitY = 0; - } else { - this.unitX = this.x / magnitude; - this.unitY = this.y / magnitude; - } + const minimalVelocity = 1; + const isMagnitudeSufficient = this.magnitude > minimalVelocity; + + this.unitX = isMagnitudeSufficient ? this.x / this.magnitude : 0; + this.unitY = isMagnitudeSufficient ? this.y / this.magnitude : 0; return this; } @@ -43,14 +39,14 @@ export default class Vector { } } -const DirectionTypeMappings = new Map>([ +const DirectionMappings = new Map([ [Directions.LEFT, [-1, 0]], [Directions.RIGHT, [1, 0]], [Directions.UP, [0, -1]], [Directions.DOWN, [0, 1]], ]); -export const DirectionTypeVectorMappings = new Map( +export const DirectionToVectorMappings = new Map( Object.values(Directions).map((direction) => [ direction, new Vector().fromDirection(direction), From 534050e4132fc131a23e914ee6f421d7dfc64cdf Mon Sep 17 00:00:00 2001 From: LatekVo Date: Wed, 13 Mar 2024 16:58:08 +0100 Subject: [PATCH 18/22] Fling and Vector code cleanup. --- .../core/FlingGestureHandler.kt | 2 +- .../swmansion/gesturehandler/core/Vector.kt | 57 +++++++------------ 2 files changed, 23 insertions(+), 36 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 6e8588b1b3..22c656c96e 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -37,7 +37,7 @@ class FlingGestureHandler : GestureHandler() { val velocityTracker = VelocityTracker.obtain() addVelocityMovement(velocityTracker, event) - val velocityVector = Vector().fromVelocity(velocityTracker) + val velocityVector = Vector.fromVelocity(velocityTracker) velocityTracker.recycle() diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt b/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt index 03076601c4..65f1714508 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt @@ -7,42 +7,19 @@ import com.swmansion.gesturehandler.core.GestureHandler.Companion.DIRECTION_RIGH import com.swmansion.gesturehandler.core.GestureHandler.Companion.DIRECTION_UP import kotlin.math.hypot -class Vector { - var x: Double = 0.0 - var y: Double = 0.0 - private var unitX: Double = 0.0 - private var unitY: Double = 0.0 +class Vector(var x: Double, var y: Double) { + private var unitX: Double = x + private var unitY: Double = y - fun fromDirection(direction: Int) = also { - val (newX, newY) = when (direction) { - DIRECTION_LEFT -> Pair(-1.0, 0.0) - DIRECTION_RIGHT -> Pair(1.0, 0.0) - DIRECTION_UP -> Pair(0.0, -1.0) - DIRECTION_DOWN -> Pair(0.0, 1.0) - else -> Pair(0.0, 0.0) - } - - x = newX - y = newY - unitX = newX - unitY = newY - } - - fun fromVelocity(tracker: VelocityTracker) = also { - tracker.computeCurrentVelocity(1000) - - x = tracker.xVelocity.toDouble() - y = tracker.yVelocity.toDouble() - - val minimalMagnitude = 1 - val isMagnitudeSufficient = magnitude > minimalMagnitude + init { + val magnitude = hypot(x, y) + val isMagnitudeSufficient = magnitude > MINIMAL_MAGNITUDE unitX = if (isMagnitudeSufficient) x / magnitude else 0.0 unitY = if (isMagnitudeSufficient) y / magnitude else 0.0 - } - fun computeSimilarity(vector: Vector): Double { + private fun computeSimilarity(vector: Vector): Double { return unitX * vector.unitX + unitY * vector.unitY } @@ -54,11 +31,12 @@ class Vector { get() = hypot(x, y) companion object { - val VECTOR_LEFT: Vector = Vector().fromDirection(DIRECTION_LEFT) - val VECTOR_RIGHT: Vector = Vector().fromDirection(DIRECTION_RIGHT) - val VECTOR_UP: Vector = Vector().fromDirection(DIRECTION_UP) - val VECTOR_DOWN: Vector = Vector().fromDirection(DIRECTION_DOWN) - val VECTOR_ZERO: Vector = Vector() + val VECTOR_LEFT: Vector = Vector(-1.0, 0.0) + val VECTOR_RIGHT: Vector = Vector(1.0, 0.0) + val VECTOR_UP: Vector = Vector(0.0, -1.0) + val VECTOR_DOWN: Vector = Vector(0.0, 1.0) + val VECTOR_ZERO: Vector = Vector(0.0, 0.0) + const val MINIMAL_MAGNITUDE = 1 fun fromDirection(direction: Int): Vector = when (direction) { @@ -69,5 +47,14 @@ class Vector { else -> VECTOR_ZERO } + fun fromVelocity(tracker: VelocityTracker): Vector { + tracker.computeCurrentVelocity(1000) + + val velocityX = tracker.xVelocity.toDouble() + val velocityY = tracker.yVelocity.toDouble() + + return Vector(velocityX, velocityY) + } + } } From b4711c64fc726c3f9893dc09e1ce7622a95dbf37 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Thu, 14 Mar 2024 14:49:41 +0100 Subject: [PATCH 19/22] - Velocity tracker now tracks the entire user movement. - Fixed issues on android. - Applied uniform formatting. - Cleanup. --- .../core/FlingGestureHandler.kt | 13 ++- .../swmansion/gesturehandler/core/Vector.kt | 94 +++++++++---------- src/web/constants.ts | 1 + src/web/handlers/FlingGestureHandler.ts | 2 +- src/web/tools/Vector.ts | 7 +- 5 files changed, 57 insertions(+), 60 deletions(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 22c656c96e..5761692095 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -15,6 +15,7 @@ class FlingGestureHandler : GestureHandler() { private var handler: Handler? = null private var maxNumberOfPointersSimultaneously = 0 private val failDelayed = Runnable { fail() } + private var velocityTracker: VelocityTracker? = null override fun resetConfig() { super.resetConfig() @@ -23,6 +24,7 @@ class FlingGestureHandler : GestureHandler() { } private fun startFling(event: MotionEvent) { + velocityTracker = VelocityTracker.obtain() begin() maxNumberOfPointersSimultaneously = 1 if (handler == null) { @@ -34,20 +36,16 @@ class FlingGestureHandler : GestureHandler() { } private fun tryEndFling(event: MotionEvent): Boolean { - val velocityTracker = VelocityTracker.obtain() addVelocityMovement(velocityTracker, event) - val velocityVector = Vector.fromVelocity(velocityTracker) - - velocityTracker.recycle() + val velocityVector = Vector.fromVelocity(velocityTracker!!) fun getVelocityAlignment( direction: Int, - directionVector: Vector = Vector.fromDirection(direction) ): Boolean = ( this.direction and direction != 0 && - velocityVector.isSimilar(directionVector, minDirectionalAlignment) - ) + velocityVector.isSimilar(Vector.fromDirection(direction), minDirectionalAlignment) + ) val alignmentList = arrayOf( DIRECTION_LEFT, @@ -108,6 +106,7 @@ class FlingGestureHandler : GestureHandler() { } override fun onReset() { + velocityTracker!!.recycle() handler?.removeCallbacksAndMessages(null) } diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt b/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt index 65f1714508..f21d992498 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/Vector.kt @@ -7,54 +7,50 @@ import com.swmansion.gesturehandler.core.GestureHandler.Companion.DIRECTION_RIGH import com.swmansion.gesturehandler.core.GestureHandler.Companion.DIRECTION_UP import kotlin.math.hypot -class Vector(var x: Double, var y: Double) { - private var unitX: Double = x - private var unitY: Double = y - - init { - val magnitude = hypot(x, y) - val isMagnitudeSufficient = magnitude > MINIMAL_MAGNITUDE - - unitX = if (isMagnitudeSufficient) x / magnitude else 0.0 - unitY = if (isMagnitudeSufficient) y / magnitude else 0.0 - } - - private fun computeSimilarity(vector: Vector): Double { - return unitX * vector.unitX + unitY * vector.unitY - } - - fun isSimilar(vector: Vector, threshold: Double): Boolean { - return computeSimilarity(vector) > threshold - } - - val magnitude - get() = hypot(x, y) - - companion object { - val VECTOR_LEFT: Vector = Vector(-1.0, 0.0) - val VECTOR_RIGHT: Vector = Vector(1.0, 0.0) - val VECTOR_UP: Vector = Vector(0.0, -1.0) - val VECTOR_DOWN: Vector = Vector(0.0, 1.0) - val VECTOR_ZERO: Vector = Vector(0.0, 0.0) - const val MINIMAL_MAGNITUDE = 1 - - fun fromDirection(direction: Int): Vector = - when (direction) { - DIRECTION_LEFT -> VECTOR_LEFT - DIRECTION_RIGHT -> VECTOR_RIGHT - DIRECTION_UP -> VECTOR_UP - DIRECTION_DOWN -> VECTOR_DOWN - else -> VECTOR_ZERO - } - - fun fromVelocity(tracker: VelocityTracker): Vector { - tracker.computeCurrentVelocity(1000) - - val velocityX = tracker.xVelocity.toDouble() - val velocityY = tracker.yVelocity.toDouble() - - return Vector(velocityX, velocityY) - } - +class Vector(val x: Double, val y: Double) { + private val unitX: Double + private val unitY: Double + val magnitude = hypot(x, y) + + init { + val isMagnitudeSufficient = magnitude > MINIMAL_MAGNITUDE + + unitX = if (isMagnitudeSufficient) x / magnitude else 0.0 + unitY = if (isMagnitudeSufficient) y / magnitude else 0.0 + } + + private fun computeSimilarity(vector: Vector): Double { + return unitX * vector.unitX + unitY * vector.unitY + } + + fun isSimilar(vector: Vector, threshold: Double): Boolean { + return computeSimilarity(vector) > threshold + } + + companion object { + private val VECTOR_LEFT: Vector = Vector(-1.0, 0.0) + private val VECTOR_RIGHT: Vector = Vector(1.0, 0.0) + private val VECTOR_UP: Vector = Vector(0.0, -1.0) + private val VECTOR_DOWN: Vector = Vector(0.0, 1.0) + private val VECTOR_ZERO: Vector = Vector(0.0, 0.0) + const val MINIMAL_MAGNITUDE = 0.1 + + fun fromDirection(direction: Int): Vector = + when (direction) { + DIRECTION_LEFT -> VECTOR_LEFT + DIRECTION_RIGHT -> VECTOR_RIGHT + DIRECTION_UP -> VECTOR_UP + DIRECTION_DOWN -> VECTOR_DOWN + else -> VECTOR_ZERO + } + + fun fromVelocity(tracker: VelocityTracker): Vector { + tracker.computeCurrentVelocity(1000) + + val velocityX = tracker.xVelocity.toDouble() + val velocityY = tracker.yVelocity.toDouble() + + return Vector(velocityX, velocityY) } + } } diff --git a/src/web/constants.ts b/src/web/constants.ts index 9f514ba224..00c7413684 100644 --- a/src/web/constants.ts +++ b/src/web/constants.ts @@ -1 +1,2 @@ export const DEFAULT_TOUCH_SLOP = 15; +export const MINIMAL_FLING_VELOCITY = 0.1; diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index 79a70c628f..9767c6a14c 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -64,7 +64,7 @@ export default class FlingGestureHandler extends GestureHandler { // list of alignments to all activated directions const alignmentList = Object.values(Directions).map(getAlignment); - const isAligned = alignmentList.some((element) => element); + const isAligned = alignmentList.some(Boolean); const isFast = velocityVector.magnitude > this.minVelocity; if ( diff --git a/src/web/tools/Vector.ts b/src/web/tools/Vector.ts index a203517ebc..24d3fb70eb 100644 --- a/src/web/tools/Vector.ts +++ b/src/web/tools/Vector.ts @@ -1,4 +1,5 @@ import { Directions } from '../../Directions'; +import { MINIMAL_FLING_VELOCITY } from '../constants'; import PointerTracker from './PointerTracker'; export default class Vector { @@ -8,7 +9,8 @@ export default class Vector { unitY: number = 0; fromDirection(direction: Directions) { - [this.x, this.y] = [this.unitX, this.unitY] = DirectionMappings.get(direction)!; + [this.x, this.y] = [this.unitX, this.unitY] = + DirectionMappings.get(direction)!; return this; } @@ -17,8 +19,7 @@ export default class Vector { this.x = tracker.getVelocityX(pointerId); this.y = tracker.getVelocityY(pointerId); - const minimalVelocity = 1; - const isMagnitudeSufficient = this.magnitude > minimalVelocity; + const isMagnitudeSufficient = this.magnitude > MINIMAL_FLING_VELOCITY; this.unitX = isMagnitudeSufficient ? this.x / this.magnitude : 0; this.unitY = isMagnitudeSufficient ? this.y / this.magnitude : 0; From 93a238d38764c41b3dd03accda047ab7137f7b96 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Thu, 14 Mar 2024 16:02:49 +0100 Subject: [PATCH 20/22] Create alternative static constructors for Vector, simplified code, ran formatting and prettier. --- src/web/handlers/FlingGestureHandler.ts | 13 +++--- src/web/tools/Vector.ts | 55 +++++++++++-------------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/web/handlers/FlingGestureHandler.ts b/src/web/handlers/FlingGestureHandler.ts index 9767c6a14c..850cb01346 100644 --- a/src/web/handlers/FlingGestureHandler.ts +++ b/src/web/handlers/FlingGestureHandler.ts @@ -3,7 +3,7 @@ import { Directions } from '../../Directions'; import { AdaptedEvent, Config } from '../interfaces'; import GestureHandler from './GestureHandler'; -import Vector, { DirectionToVectorMappings } from '../tools/Vector'; +import Vector from '../tools/Vector'; const DEFAULT_MAX_DURATION_MS = 800; const DEFAULT_MIN_VELOCITY = 700; @@ -48,16 +48,15 @@ export default class FlingGestureHandler extends GestureHandler { } private tryEndFling(): boolean { - const velocityVector = new Vector().fromVelocity( - this.tracker, - this.keyPointer - ); + const velocityVector = Vector.fromVelocity(this.tracker, this.keyPointer); const getAlignment = (direction: Directions) => { - const directionVector = DirectionToVectorMappings.get(direction)!; return ( direction & this.direction && - velocityVector.isSimilar(directionVector, this.minDirectionalAlignment) + velocityVector.isSimilar( + Vector.fromDirection(direction), + this.minDirectionalAlignment + ) ); }; diff --git a/src/web/tools/Vector.ts b/src/web/tools/Vector.ts index 24d3fb70eb..0d55edcd30 100644 --- a/src/web/tools/Vector.ts +++ b/src/web/tools/Vector.ts @@ -3,28 +3,32 @@ import { MINIMAL_FLING_VELOCITY } from '../constants'; import PointerTracker from './PointerTracker'; export default class Vector { - x: number = 0; - y: number = 0; - unitX: number = 0; - unitY: number = 0; + x = 0; + y = 0; + unitX = 0; + unitY = 0; + magnitude = 0; - fromDirection(direction: Directions) { - [this.x, this.y] = [this.unitX, this.unitY] = - DirectionMappings.get(direction)!; - - return this; - } - - fromVelocity(tracker: PointerTracker, pointerId: number) { - this.x = tracker.getVelocityX(pointerId); - this.y = tracker.getVelocityY(pointerId); + constructor(x: number, y: number) { + this.x = x; + this.y = y; + this.magnitude = Math.hypot(this.x, this.y); const isMagnitudeSufficient = this.magnitude > MINIMAL_FLING_VELOCITY; this.unitX = isMagnitudeSufficient ? this.x / this.magnitude : 0; this.unitY = isMagnitudeSufficient ? this.y / this.magnitude : 0; + } - return this; + static fromDirection(direction: Directions) { + return DirectionToVectorMappings.get(direction)!; + } + + static fromVelocity(tracker: PointerTracker, pointerId: number) { + return new Vector( + tracker.getVelocityX(pointerId), + tracker.getVelocityY(pointerId) + ); } computeSimilarity(vector: Vector) { @@ -34,22 +38,11 @@ export default class Vector { isSimilar(vector: Vector, threshold: number) { return this.computeSimilarity(vector) > threshold; } - - get magnitude() { - return Math.hypot(this.x, this.y); - } } -const DirectionMappings = new Map([ - [Directions.LEFT, [-1, 0]], - [Directions.RIGHT, [1, 0]], - [Directions.UP, [0, -1]], - [Directions.DOWN, [0, 1]], +const DirectionToVectorMappings = new Map([ + [Directions.LEFT, new Vector(-1, 0)], + [Directions.RIGHT, new Vector(1, 0)], + [Directions.UP, new Vector(0, -1)], + [Directions.DOWN, new Vector(0, 1)], ]); - -export const DirectionToVectorMappings = new Map( - Object.values(Directions).map((direction) => [ - direction, - new Vector().fromDirection(direction), - ]) -); From 1af78a7a960348576a8fd080e7e0f30e45b6c1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacy=20=C5=81=C4=85tka?= <74246391+LatekVo@users.noreply.github.com> Date: Fri, 15 Mar 2024 10:50:32 +0100 Subject: [PATCH 21/22] Remove potential edge-case crash Co-authored-by: Jakub Piasecki --- .../com/swmansion/gesturehandler/core/FlingGestureHandler.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 5761692095..3f136555f0 100644 --- a/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -106,7 +106,8 @@ class FlingGestureHandler : GestureHandler() { } override fun onReset() { - velocityTracker!!.recycle() + velocityTracker?.recycle() + velocityTracker = null handler?.removeCallbacksAndMessages(null) } From b84adae03b326eca2eb9fedade0337c544410921 Mon Sep 17 00:00:00 2001 From: LatekVo Date: Fri, 15 Mar 2024 11:41:28 +0100 Subject: [PATCH 22/22] Make Vector members private readonly, make magnitude a getter of private _magnitude. --- src/web/tools/Vector.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/web/tools/Vector.ts b/src/web/tools/Vector.ts index 0d55edcd30..782d73d214 100644 --- a/src/web/tools/Vector.ts +++ b/src/web/tools/Vector.ts @@ -3,21 +3,21 @@ import { MINIMAL_FLING_VELOCITY } from '../constants'; import PointerTracker from './PointerTracker'; export default class Vector { - x = 0; - y = 0; - unitX = 0; - unitY = 0; - magnitude = 0; + private readonly x; + private readonly y; + private readonly unitX; + private readonly unitY; + private readonly _magnitude; constructor(x: number, y: number) { this.x = x; this.y = y; - this.magnitude = Math.hypot(this.x, this.y); - const isMagnitudeSufficient = this.magnitude > MINIMAL_FLING_VELOCITY; + this._magnitude = Math.hypot(this.x, this.y); + const isMagnitudeSufficient = this._magnitude > MINIMAL_FLING_VELOCITY; - this.unitX = isMagnitudeSufficient ? this.x / this.magnitude : 0; - this.unitY = isMagnitudeSufficient ? this.y / this.magnitude : 0; + this.unitX = isMagnitudeSufficient ? this.x / this._magnitude : 0; + this.unitY = isMagnitudeSufficient ? this.y / this._magnitude : 0; } static fromDirection(direction: Directions) { @@ -31,6 +31,10 @@ export default class Vector { ); } + get magnitude() { + return this._magnitude; + } + computeSimilarity(vector: Vector) { return this.unitX * vector.unitX + this.unitY * vector.unitY; }