Browse Source

iOS: added delay gesture recognizer

This gesture recognizer will prevent GodotView from processing unwanted gestures.
Emulates UIScrollView behavior.
Fires delayed touches on significant movement.
Sergey Minakov 5 years ago
parent
commit
e6d7e01a72

+ 1 - 0
platform/iphone/SCsub

@@ -18,6 +18,7 @@ iphone_lib = [
     "godot_view.mm",
     "godot_view.mm",
     "display_layer.mm",
     "display_layer.mm",
     "godot_view_renderer.mm",
     "godot_view_renderer.mm",
+    "godot_view_gesture_recognizer.m",
 ]
 ]
 
 
 env_ios = env.Clone()
 env_ios = env.Clone()

+ 13 - 12
platform/iphone/godot_view.mm

@@ -33,6 +33,7 @@
 #include "core/ustring.h"
 #include "core/ustring.h"
 #import "display_layer.h"
 #import "display_layer.h"
 #include "display_server_iphone.h"
 #include "display_server_iphone.h"
+#import "godot_view_gesture_recognizer.h"
 #import "godot_view_renderer.h"
 #import "godot_view_renderer.h"
 
 
 #import <CoreMotion/CoreMotion.h>
 #import <CoreMotion/CoreMotion.h>
@@ -57,6 +58,8 @@ static const int max_touches = 8;
 
 
 @property(strong, nonatomic) CMMotionManager *motionManager;
 @property(strong, nonatomic) CMMotionManager *motionManager;
 
 
+@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer;
+
 @end
 @end
 
 
 @implementation GodotView
 @implementation GodotView
@@ -139,6 +142,10 @@ static const int max_touches = 8;
 		self.animationTimer = nil;
 		self.animationTimer = nil;
 	}
 	}
 
 
+	if (self.delayGestureRecognizer) {
+		self.delayGestureRecognizer = nil;
+	}
+
 	[super dealloc];
 	[super dealloc];
 }
 }
 
 
@@ -157,6 +164,12 @@ static const int max_touches = 8;
 			self.motionManager = nil;
 			self.motionManager = nil;
 		}
 		}
 	}
 	}
+
+	// Initialize delay gesture recognizer
+	GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init];
+	self.delayGestureRecognizer = gestureRecognizer;
+	[self addGestureRecognizer:self.delayGestureRecognizer];
+	[gestureRecognizer release];
 }
 }
 
 
 - (void)stopRendering {
 - (void)stopRendering {
@@ -359,9 +372,6 @@ static const int max_touches = 8;
 	for (unsigned int i = 0; i < [tlist count]; i++) {
 	for (unsigned int i = 0; i < [tlist count]; i++) {
 		if ([touchesSet containsObject:[tlist objectAtIndex:i]]) {
 		if ([touchesSet containsObject:[tlist objectAtIndex:i]]) {
 			UITouch *touch = [tlist objectAtIndex:i];
 			UITouch *touch = [tlist objectAtIndex:i];
-			if (touch.phase != UITouchPhaseBegan) {
-				continue;
-			}
 			int tid = [self getTouchIDForTouch:touch];
 			int tid = [self getTouchIDForTouch:touch];
 			ERR_FAIL_COND(tid == -1);
 			ERR_FAIL_COND(tid == -1);
 			CGPoint touchPoint = [touch locationInView:self];
 			CGPoint touchPoint = [touch locationInView:self];
@@ -375,9 +385,6 @@ static const int max_touches = 8;
 	for (unsigned int i = 0; i < [tlist count]; i++) {
 	for (unsigned int i = 0; i < [tlist count]; i++) {
 		if ([touches containsObject:[tlist objectAtIndex:i]]) {
 		if ([touches containsObject:[tlist objectAtIndex:i]]) {
 			UITouch *touch = [tlist objectAtIndex:i];
 			UITouch *touch = [tlist objectAtIndex:i];
-			if (touch.phase != UITouchPhaseMoved) {
-				continue;
-			}
 			int tid = [self getTouchIDForTouch:touch];
 			int tid = [self getTouchIDForTouch:touch];
 			ERR_FAIL_COND(tid == -1);
 			ERR_FAIL_COND(tid == -1);
 			CGPoint touchPoint = [touch locationInView:self];
 			CGPoint touchPoint = [touch locationInView:self];
@@ -392,9 +399,6 @@ static const int max_touches = 8;
 	for (unsigned int i = 0; i < [tlist count]; i++) {
 	for (unsigned int i = 0; i < [tlist count]; i++) {
 		if ([touches containsObject:[tlist objectAtIndex:i]]) {
 		if ([touches containsObject:[tlist objectAtIndex:i]]) {
 			UITouch *touch = [tlist objectAtIndex:i];
 			UITouch *touch = [tlist objectAtIndex:i];
-			if (touch.phase != UITouchPhaseEnded) {
-				continue;
-			}
 			int tid = [self getTouchIDForTouch:touch];
 			int tid = [self getTouchIDForTouch:touch];
 			ERR_FAIL_COND(tid == -1);
 			ERR_FAIL_COND(tid == -1);
 			[self removeTouch:touch];
 			[self removeTouch:touch];
@@ -409,9 +413,6 @@ static const int max_touches = 8;
 	for (unsigned int i = 0; i < [tlist count]; i++) {
 	for (unsigned int i = 0; i < [tlist count]; i++) {
 		if ([touches containsObject:[tlist objectAtIndex:i]]) {
 		if ([touches containsObject:[tlist objectAtIndex:i]]) {
 			UITouch *touch = [tlist objectAtIndex:i];
 			UITouch *touch = [tlist objectAtIndex:i];
-			if (touch.phase != UITouchPhaseCancelled) {
-				continue;
-			}
 			int tid = [self getTouchIDForTouch:touch];
 			int tid = [self getTouchIDForTouch:touch];
 			ERR_FAIL_COND(tid == -1);
 			ERR_FAIL_COND(tid == -1);
 			DisplayServerIPhone::get_singleton()->touches_cancelled(tid);
 			DisplayServerIPhone::get_singleton()->touches_cancelled(tid);

+ 44 - 0
platform/iphone/godot_view_gesture_recognizer.h

@@ -0,0 +1,44 @@
+/*************************************************************************/
+/*  godot_view_gesture_recognizer.h                                      */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+// GLViewGestureRecognizer allows iOS gestures to work currectly by
+// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer.
+// It catches all gestures incoming to UIView and delays them for 150ms
+// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer)
+// If touch cancelation or end message is fired it fires delayed
+// begin touch immediately as well as last touch signal
+
+#import <UIKit/UIKit.h>
+
+@interface GodotViewGestureRecognizer : UIGestureRecognizer
+
+- (instancetype)init;
+
+@end

+ 171 - 0
platform/iphone/godot_view_gesture_recognizer.m

@@ -0,0 +1,171 @@
+/*************************************************************************/
+/*  godot_view_gesture_recognizer.m                                      */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#import "godot_view_gesture_recognizer.h"
+
+// Using same delay interval that is used for `UIScrollView`
+const NSTimeInterval kGLGestureDelayInterval = 0.150;
+
+// Minimum distance for touches to move to fire
+// a delay timer before scheduled time.
+// Should be the low enough to not cause issues with dragging
+// but big enough to allow click to work.
+const CGFloat kGLGestureMovementDistance = 0.5;
+
+@interface GodotViewGestureRecognizer ()
+
+// Timer used to delay begin touch message.
+// Should work as simple emulation of UIDelayedAction
+@property(strong, nonatomic) NSTimer *delayTimer;
+
+// Delayed touch parameters
+@property(strong, nonatomic) NSSet *delayedTouches;
+@property(strong, nonatomic) UIEvent *delayedEvent;
+
+@end
+
+@implementation GodotViewGestureRecognizer
+
+- (instancetype)init {
+	self = [super init];
+
+	self.cancelsTouchesInView = YES;
+	self.delaysTouchesBegan = YES;
+	self.delaysTouchesEnded = YES;
+
+	return self;
+}
+
+- (void)dealloc {
+	if (self.delayTimer) {
+		[self.delayTimer invalidate];
+		self.delayTimer = nil;
+	}
+
+	if (self.delayedTouches) {
+		self.delayedTouches = nil;
+	}
+
+	if (self.delayedEvent) {
+		self.delayedEvent = nil;
+	}
+
+	[super dealloc];
+}
+
+- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event {
+	[self.delayTimer fire];
+
+	self.delayedTouches = touches;
+	self.delayedEvent = event;
+
+	self.delayTimer = [NSTimer
+			scheduledTimerWithTimeInterval:kGLGestureDelayInterval
+									target:self
+								  selector:@selector(fireDelayedTouches:)
+								  userInfo:nil
+								   repeats:NO];
+}
+
+- (void)fireDelayedTouches:(id)timer {
+	[self.delayTimer invalidate];
+	self.delayTimer = nil;
+
+	if (self.delayedTouches) {
+		[self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent];
+	}
+
+	self.delayedTouches = nil;
+	self.delayedEvent = nil;
+}
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
+	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan];
+	[self delayTouches:cleared andEvent:event];
+	[cleared release];
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseMoved];
+
+	if (self.delayTimer) {
+		// We should check if movement was significant enough to fire an event
+		// for dragging to work correctly.
+		for (UITouch *touch in cleared) {
+			CGPoint from = [touch locationInView:self.view];
+			CGPoint to = [touch previousLocationInView:self.view];
+			CGFloat xDistance = from.x - to.x;
+			CGFloat yDistance = from.y - to.y;
+
+			CGFloat distance = sqrt(xDistance * xDistance + yDistance * yDistance);
+
+			// Early exit, since one of touches has moved enough to fire a drag event.
+			if (distance > kGLGestureMovementDistance) {
+				[self.delayTimer fire];
+				[self.view touchesMoved:cleared withEvent:event];
+				[cleared release];
+				return;
+			}
+		}
+
+		[cleared release];
+		return;
+	}
+
+	[self.view touchesMoved:cleared withEvent:event];
+	[cleared release];
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+	[self.delayTimer fire];
+
+	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
+	[self.view touchesEnded:cleared withEvent:event];
+	[cleared release];
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+	[self.delayTimer fire];
+	[self.view touchesCancelled:touches withEvent:event];
+};
+
+- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave {
+	NSMutableSet *cleared = [touches mutableCopy];
+
+	for (UITouch *touch in touches) {
+		if (touch.phase != phaseToSave) {
+			[cleared removeObject:touch];
+		}
+	}
+
+	return cleared;
+}
+
+@end