Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add numberOfPointers to LongPress #3043

Merged
merged 15 commits into from
Aug 23, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@ class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestur
get() = (previousTime - startTime).toInt()
private val defaultMaxDistSq: Float
private var maxDistSq: Float
private var numberOfPointersRequired: Int
private var startX = 0f
private var startY = 0f
private var startTime: Long = 0
private var previousTime: Long = 0
private var handler: Handler? = null
private var currentPointers = 0

init {
setShouldCancelWhenOutside(true)

val defaultMaxDist = DEFAULT_MAX_DIST_DP * context.resources.displayMetrics.density
defaultMaxDistSq = defaultMaxDist * defaultMaxDist
maxDistSq = defaultMaxDistSq
numberOfPointersRequired = 1
}

override fun resetConfig() {
Expand All @@ -37,6 +40,37 @@ class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestur
return this
}

fun setNumberOfPointers(numberOfPointers: Int): LongPressGestureHandler {
numberOfPointersRequired = numberOfPointers
return this
}

private fun getAverageCoords(ev: MotionEvent, excludePointer: Boolean = false): Pair<Float, Float> {
if (!excludePointer) {
val x = (0 until ev.pointerCount).map { ev.getX(it) }.average().toFloat()
val y = (0 until ev.pointerCount).map { ev.getY(it) }.average().toFloat()

return Pair(x, y)
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved
}

var sumX = 0f
var sumY = 0f

for (i in 0 until ev.pointerCount) {
if (i == ev.actionIndex) {
continue
}

sumX += ev.getX(i)
sumY += ev.getY(i)
}

val x = sumX / (ev.pointerCount - 1)
val y = sumY / (ev.pointerCount - 1)

return Pair(x, y)
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved
}

override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
if (!shouldActivateWithMouse(sourceEvent)) {
return
Expand All @@ -46,8 +80,28 @@ class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestur
previousTime = SystemClock.uptimeMillis()
startTime = previousTime
begin()
startX = sourceEvent.rawX
startY = sourceEvent.rawY

val (x, y) = getAverageCoords(sourceEvent)
startX = x
startY = y

currentPointers = 1
}

if (sourceEvent.actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
currentPointers++

val (x, y) = getAverageCoords(sourceEvent)
startX = x
startY = y

if (currentPointers > numberOfPointersRequired) {
fail()
currentPointers = 0
}
}

if (state == STATE_BEGAN && currentPointers == numberOfPointersRequired && (sourceEvent.actionMasked == MotionEvent.ACTION_DOWN || sourceEvent.actionMasked == MotionEvent.ACTION_POINTER_DOWN)) {
handler = Handler(Looper.getMainLooper())
if (minDurationMs > 0) {
handler!!.postDelayed({ activate() }, minDurationMs)
Expand All @@ -56,20 +110,37 @@ class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestur
}
}
if (sourceEvent.actionMasked == MotionEvent.ACTION_UP || sourceEvent.actionMasked == MotionEvent.ACTION_BUTTON_RELEASE) {
currentPointers--

handler?.let {
it.removeCallbacksAndMessages(null)
handler = null
}

if (state == STATE_ACTIVE) {
end()
} else {
fail()
}
} else if (sourceEvent.actionMasked == MotionEvent.ACTION_POINTER_UP) {
currentPointers--

if (currentPointers < numberOfPointersRequired && state != STATE_ACTIVE) {
fail()
currentPointers = 0
} else {
val (x, y) = getAverageCoords(sourceEvent, true)
startX = x
startY = y
}
} else {
// calculate distance from start
val deltaX = sourceEvent.rawX - startX
val deltaY = sourceEvent.rawY - startY
val (x, y) = getAverageCoords(sourceEvent)

val deltaX = x - startX
val deltaY = y - startY
val distSq = deltaX * deltaX + deltaY * deltaY

if (distSq > maxDistSq) {
if (state == STATE_ACTIVE) {
cancel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
if (config.hasKey(KEY_LONG_PRESS_MAX_DIST)) {
handler.setMaxDist(PixelUtil.toPixelFromDIP(config.getDouble(KEY_LONG_PRESS_MAX_DIST)))
}
if (config.hasKey(KEY_NUMBER_OF_POINTERS)) {
handler.setNumberOfPointers(config.getInt(KEY_NUMBER_OF_POINTERS))
}
}

override fun createEventBuilder(handler: LongPressGestureHandler) = LongPressGestureHandlerEventDataBuilder(handler)
Expand Down
9 changes: 9 additions & 0 deletions apple/Handlers/RNLongPressHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ @interface RNBetterLongPressGestureRecognizer : NSGestureRecognizer {
#if TARGET_OS_OSX
@property (nonatomic, assign) double minimumPressDuration;
@property (nonatomic, assign) double allowableMovement;
@property (nonatomic, assign) double numberOfTouchesRequired;
#endif

- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
Expand Down Expand Up @@ -77,6 +78,8 @@ - (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
[super touchesBegan:touches withEvent:event];
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];

self.state = UIGestureRecognizerStatePossible;

_initPosition = [self locationInView:self.view];
startTime = CACurrentMediaTime();
[_gestureHandler reset];
Expand All @@ -89,6 +92,7 @@ - (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
[_gestureHandler.pointerTracker touchesMoved:touches withEvent:event];

if ([self shouldCancelGesture]) {
self.state = UIGestureRecognizerStateFailed;
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved
self.enabled = NO;
self.enabled = YES;
}
Expand Down Expand Up @@ -228,6 +232,11 @@ - (void)configure:(NSDictionary *)config
if (prop != nil) {
recognizer.allowableMovement = [RCTConvert CGFloat:prop];
}

prop = config[@"numberOfPointers"];
if (prop != nil) {
recognizer.numberOfTouchesRequired = [RCTConvert CGFloat:prop];
}
}

#if !TARGET_OS_OSX
Expand Down
6 changes: 6 additions & 0 deletions src/handlers/LongPressGestureHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
export const longPressGestureHandlerProps = [
'minDurationMs',
'maxDist',
'numberOfPointers',
] as const;

export interface LongPressGestureConfig {
Expand All @@ -24,6 +25,11 @@ export interface LongPressGestureConfig {
* will fail to recognize the gesture. The default value is 10.
*/
maxDist?: number;

/**
* Determine exact number of points required to handle the long press gesture.
*/
numberOfPointers?: number;
}

export interface LongPressGestureHandlerProps
Expand Down
9 changes: 9 additions & 0 deletions src/handlers/gestures/longPressGesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ export class LongPressGesture extends BaseGesture<LongPressGestureHandlerEventPa
this.config.maxDist = distance;
return this;
}

/**
* Determine exact number of points required to handle the long press gesture.
* @param pointers
*/
numberOfPointers(pointers: number) {
this.config.numberOfPointers = pointers;
return this;
}
}

export type LongPressGestureType = InstanceType<typeof LongPressGesture>;
54 changes: 44 additions & 10 deletions src/web/handlers/LongPressGestureHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default class LongPressGestureHandler extends GestureHandler {
private defaultMaxDistSq = DEFAULT_MAX_DIST_DP * SCALING_FACTOR;

private maxDistSq = this.defaultMaxDistSq;
private numberOfPointers = 1;
private startX = 0;
private startY = 0;

Expand Down Expand Up @@ -45,6 +46,10 @@ export default class LongPressGestureHandler extends GestureHandler {
if (this.config.maxDist !== undefined) {
this.maxDistSq = this.config.maxDist * this.config.maxDist;
}

if (this.config.numberOfPointers !== undefined) {
this.numberOfPointers = this.config.numberOfPointers;
}
}

protected resetConfig(): void {
Expand All @@ -64,17 +69,31 @@ export default class LongPressGestureHandler extends GestureHandler {

this.tracker.addToTracker(event);
super.onPointerDown(event);
this.tryBegin(event);

this.startX = event.x;
this.startY = event.y;

this.tryBegin();
this.tryActivate();
this.checkDistanceFail(event);

this.tryToSendTouchEvent(event);
}
protected onPointerAdd(event: AdaptedEvent): void {
super.onPointerAdd(event);
this.tracker.addToTracker(event);

const absoluteCoordsAverage = this.tracker.getAbsoluteCoordsAverage();

this.startX = absoluteCoordsAverage.x;
this.startY = absoluteCoordsAverage.y;

this.tryActivate();
}

protected onPointerMove(event: AdaptedEvent): void {
super.onPointerMove(event);
this.tracker.track(event);
this.checkDistanceFail(event);
this.checkDistanceFail();
}

protected onPointerUp(event: AdaptedEvent): void {
Expand All @@ -88,7 +107,19 @@ export default class LongPressGestureHandler extends GestureHandler {
}
}

private tryBegin(event: AdaptedEvent): void {
protected onPointerRemove(event: AdaptedEvent): void {
super.onPointerRemove(event);
this.tracker.removeFromTracker(event.pointerId);

if (
this.tracker.getTrackedPointersCount() < this.numberOfPointers &&
this.getState() !== State.ACTIVE
) {
this.fail();
}
}

private tryBegin(): void {
if (this.currentState !== State.UNDETERMINED) {
return;
}
Expand All @@ -97,12 +128,13 @@ export default class LongPressGestureHandler extends GestureHandler {
this.startTime = this.previousTime;

this.begin();

this.startX = event.x;
this.startY = event.y;
}

private tryActivate(): void {
if (this.tracker.getTrackedPointersCount() !== this.numberOfPointers) {
return;
}

if (this.minDurationMs > 0) {
this.activationTimeout = setTimeout(() => {
this.activate();
Expand All @@ -112,9 +144,11 @@ export default class LongPressGestureHandler extends GestureHandler {
}
}

private checkDistanceFail(event: AdaptedEvent): void {
const dx = event.x - this.startX;
const dy = event.y - this.startY;
private checkDistanceFail(): void {
const absoluteCoordsAverage = this.tracker.getAbsoluteCoordsAverage();

const dx = absoluteCoordsAverage.x - this.startX;
const dy = absoluteCoordsAverage.y - this.startY;
const distSq = dx * dx + dy * dy;

if (distSq <= this.maxDistSq) {
Expand Down
Loading