diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index e271b18c146..168d2acd5be 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -14,6 +14,7 @@ iphone_lib = [ "in_app_store.mm", "icloud.mm", "ios.mm", + "gl_view_gesture_recognizer.m", ] env_ios = env.Clone() diff --git a/platform/iphone/gl_view.h b/platform/iphone/gl_view.h index 975aa4b70a6..a51a43417b3 100644 --- a/platform/iphone/gl_view.h +++ b/platform/iphone/gl_view.h @@ -36,6 +36,7 @@ #import @protocol GLViewDelegate; +@class GLViewGestureRecognizer; @interface GLView : UIView { @private @@ -69,6 +70,9 @@ BOOL delegateSetup; BOOL active; float screen_scale; + + // Delay gesture recognizer + GLViewGestureRecognizer *delayGestureRecognizer; } @property(nonatomic, assign) id delegate; diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm index e8d737d9c3d..d03a1e64403 100644 --- a/platform/iphone/gl_view.mm +++ b/platform/iphone/gl_view.mm @@ -29,6 +29,7 @@ /*************************************************************************/ #import "gl_view.h" +#import "gl_view_gesture_recognizer.h" #include "core/os/keyboard.h" #include "core/project_settings.h" @@ -268,6 +269,7 @@ static void clear_touches() { active = FALSE; if ((self = [super initWithCoder:coder])) { self = [self initGLES]; + [self initGestureRecognizer]; } return self; } @@ -320,6 +322,11 @@ static void clear_touches() { return self; } +- (void)initGestureRecognizer { + delayGestureRecognizer = [[GLViewGestureRecognizer alloc] init]; + [self addGestureRecognizer:delayGestureRecognizer]; +} + - (id)delegate { return delegate; } @@ -503,8 +510,6 @@ static void clear_touches() { if ([touches containsObject:[tlist objectAtIndex:i]]) { UITouch *touch = [tlist objectAtIndex:i]; - if (touch.phase != UITouchPhaseBegan) - continue; int tid = get_touch_id(touch); ERR_FAIL_COND(tid == -1); CGPoint touchPoint = [touch locationInView:self]; @@ -521,8 +526,6 @@ static void clear_touches() { if ([touches containsObject:[tlist objectAtIndex:i]]) { UITouch *touch = [tlist objectAtIndex:i]; - if (touch.phase != UITouchPhaseMoved) - continue; int tid = get_touch_id(touch); ERR_FAIL_COND(tid == -1); CGPoint touchPoint = [touch locationInView:self]; @@ -539,8 +542,6 @@ static void clear_touches() { if ([touches containsObject:[tlist objectAtIndex:i]]) { UITouch *touch = [tlist objectAtIndex:i]; - if (touch.phase != UITouchPhaseEnded) - continue; int tid = get_touch_id(touch); ERR_FAIL_COND(tid == -1); remove_touch(touch); @@ -642,6 +643,7 @@ static void clear_touches() { if (self != nil) { self = [self initGLES]; printf("after init gles %p\n", self); + [self initGestureRecognizer]; } init_touches(); self.multipleTouchEnabled = YES; diff --git a/platform/iphone/gl_view_gesture_recognizer.h b/platform/iphone/gl_view_gesture_recognizer.h new file mode 100644 index 00000000000..abbd9094b34 --- /dev/null +++ b/platform/iphone/gl_view_gesture_recognizer.h @@ -0,0 +1,54 @@ +/*************************************************************************/ +/* gl_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 + +@interface GLViewGestureRecognizer : UIGestureRecognizer { +@private + + // Timer used to delay begin touch message. + // Should work as simple emulation of UIDelayedAction + NSTimer *delayTimer; + + // Delayed touch parameters + NSSet *delayedTouches; + UIEvent *delayedEvent; +} + +- (instancetype)init; + +@end diff --git a/platform/iphone/gl_view_gesture_recognizer.m b/platform/iphone/gl_view_gesture_recognizer.m new file mode 100644 index 00000000000..ff2610a235d --- /dev/null +++ b/platform/iphone/gl_view_gesture_recognizer.m @@ -0,0 +1,131 @@ +/*************************************************************************/ +/* gl_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 "gl_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; + +@implementation GLViewGestureRecognizer + +- (instancetype)init { + self = [super init]; + + self.cancelsTouchesInView = YES; + self.delaysTouchesBegan = YES; + self.delaysTouchesEnded = YES; + + return self; +} + +- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event { + [delayTimer fire]; + + delayedTouches = touches; + delayedEvent = event; + + delayTimer = [NSTimer scheduledTimerWithTimeInterval:kGLGestureDelayInterval target:self selector:@selector(fireDelayedTouches:) userInfo:nil repeats:NO]; +} + +- (void)fireDelayedTouches:(id)timer { + [delayTimer invalidate]; + delayTimer = nil; + + if (delayedTouches) { + [self.view touchesBegan:delayedTouches withEvent:delayedEvent]; + } + + delayedTouches = nil; + delayedEvent = nil; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + NSSet *cleared = [self clearTouches:touches phase:UITouchPhaseBegan]; + [self delayTouches:cleared andEvent:event]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + NSSet *cleared = [self clearTouches:touches phase:UITouchPhaseMoved]; + + if (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) { + [delayTimer fire]; + [self.view touchesMoved:cleared withEvent:event]; + return; + } + } + return; + } + + [self.view touchesMoved:cleared withEvent:event]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + [delayTimer fire]; + + NSSet *cleared = [self clearTouches:touches phase:UITouchPhaseEnded]; + [self.view touchesEnded:cleared withEvent:event]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + [delayTimer fire]; + [self.view touchesCancelled:touches withEvent:event]; +}; + +- (NSSet *)clearTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave { + NSMutableSet *cleared = [touches mutableCopy]; + + for (UITouch *touch in touches) { + if (touch.phase != phaseToSave) { + [cleared removeObject:touch]; + } + } + + return cleared; +} + +@end