Refactor relative touch handling into a separate class

This commit is contained in:
Cameron Gutman
2020-11-01 10:19:32 -06:00
parent b650119fe9
commit 17d4079a5a
4 changed files with 253 additions and 174 deletions

View File

@@ -0,0 +1,19 @@
//
// RelativeTouchHandler.h
// Moonlight
//
// Created by Cameron Gutman on 11/1/20.
// Copyright © 2020 Moonlight Game Streaming Project. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RelativeTouchHandler : UIResponder
-(id)initWithView:(UIView*)view;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,196 @@
//
// RelativeTouchHandler.m
// Moonlight
//
// Created by Cameron Gutman on 11/1/20.
// Copyright © 2020 Moonlight Game Streaming Project. All rights reserved.
//
#import "RelativeTouchHandler.h"
#include <Limelight.h>
static const int REFERENCE_WIDTH = 1280;
static const int REFERENCE_HEIGHT = 720;
@implementation RelativeTouchHandler {
CGPoint touchLocation, originalLocation;
BOOL touchMoved;
BOOL isDragging;
NSTimer* dragTimer;
#if TARGET_OS_TV
UIGestureRecognizer* remotePressRecognizer;
UIGestureRecognizer* remoteLongPressRecognizer;
#endif
UIView* view;
}
- (id)initWithView:(UIView*)view {
self = [self init];
self->view = view;
#if TARGET_OS_TV
remotePressRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(remoteButtonPressed:)];
remotePressRecognizer.allowedPressTypes = @[@(UIPressTypeSelect)];
remoteLongPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(remoteButtonLongPressed:)];
remoteLongPressRecognizer.allowedPressTypes = @[@(UIPressTypeSelect)];
[self->view addGestureRecognizer:remotePressRecognizer];
[self->view addGestureRecognizer:remoteLongPressRecognizer];
#endif
return self;
}
- (BOOL)isConfirmedMove:(CGPoint)currentPoint from:(CGPoint)originalPoint {
// Movements of greater than 5 pixels are considered confirmed
return hypotf(originalPoint.x - currentPoint.x, originalPoint.y - currentPoint.y) >= 5;
}
- (void)onDragStart:(NSTimer*)timer {
if (!touchMoved && !isDragging){
isDragging = true;
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
originalLocation = touchLocation = [touch locationInView:view];
touchMoved = false;
if ([[event allTouches] count] == 1 && !isDragging) {
dragTimer = [NSTimer scheduledTimerWithTimeInterval:0.650
target:self
selector:@selector(onDragStart:)
userInfo:nil
repeats:NO];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if ([[event allTouches] count] == 1) {
UITouch *touch = [[event allTouches] anyObject];
CGPoint currentLocation = [touch locationInView:view];
if (touchLocation.x != currentLocation.x ||
touchLocation.y != currentLocation.y)
{
int deltaX = (currentLocation.x - touchLocation.x) * (REFERENCE_WIDTH / view.bounds.size.width);
int deltaY = (currentLocation.y - touchLocation.y) * (REFERENCE_HEIGHT / view.bounds.size.height);
if (deltaX != 0 || deltaY != 0) {
LiSendMouseMoveEvent(deltaX, deltaY);
touchLocation = currentLocation;
// If we've moved far enough to confirm this wasn't just human/machine error,
// mark it as such.
if ([self isConfirmedMove:touchLocation from:originalLocation]) {
touchMoved = true;
}
}
}
} else if ([[event allTouches] count] == 2) {
CGPoint firstLocation = [[[[event allTouches] allObjects] objectAtIndex:0] locationInView:view];
CGPoint secondLocation = [[[[event allTouches] allObjects] objectAtIndex:1] locationInView:view];
CGPoint avgLocation = CGPointMake((firstLocation.x + secondLocation.x) / 2, (firstLocation.y + secondLocation.y) / 2);
if (touchLocation.y != avgLocation.y) {
LiSendScrollEvent(avgLocation.y - touchLocation.y);
}
// If we've moved far enough to confirm this wasn't just human/machine error,
// mark it as such.
if ([self isConfirmedMove:firstLocation from:originalLocation]) {
touchMoved = true;
}
touchLocation = avgLocation;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[dragTimer invalidate];
dragTimer = nil;
if (isDragging) {
isDragging = false;
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
} else if (!touchMoved) {
if ([[event allTouches] count] == 2) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
Log(LOG_D, @"Sending right mouse button press");
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
// Wait 100 ms to simulate a real button press
usleep(100 * 1000);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
});
} else if ([[event allTouches] count] == 1) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
if (!self->isDragging){
Log(LOG_D, @"Sending left mouse button press");
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
// Wait 100 ms to simulate a real button press
usleep(100 * 1000);
}
self->isDragging = false;
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
});
}
}
// We we're moving from 2+ touches to 1. Synchronize the current position
// of the active finger so we don't jump unexpectedly on the next touchesMoved
// callback when finger 1 switches on us.
if ([[event allTouches] count] - [touches count] == 1) {
NSMutableSet *activeSet = [[NSMutableSet alloc] initWithCapacity:[[event allTouches] count]];
[activeSet unionSet:[event allTouches]];
[activeSet minusSet:touches];
touchLocation = [[activeSet anyObject] locationInView:view];
// Mark this touch as moved so we don't send a left mouse click if the user
// right clicks without moving their other finger.
touchMoved = true;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[dragTimer invalidate];
dragTimer = nil;
if (isDragging) {
isDragging = false;
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
}
}
#if TARGET_OS_TV
- (void)remoteButtonPressed:(id)sender {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
Log(LOG_D, @"Sending left mouse button press");
// Mark this as touchMoved to avoid a duplicate press on touch up
self->touchMoved = true;
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
// Wait 100 ms to simulate a real button press
usleep(100 * 1000);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
});
}
- (void)remoteButtonLongPressed:(id)sender {
Log(LOG_D, @"Holding left mouse button");
isDragging = true;
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
#endif
@end

View File

@@ -11,20 +11,14 @@
#import "DataManager.h"
#import "ControllerSupport.h"
#import "KeyboardSupport.h"
#import "RelativeTouchHandler.h"
static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
static const int REFERENCE_WIDTH = 1280;
static const int REFERENCE_HEIGHT = 720;
@implementation StreamView {
CGPoint touchLocation, originalLocation;
BOOL touchMoved;
OnScreenControls* onScreenControls;
BOOL isInputingText;
BOOL isDragging;
NSTimer* dragTimer;
float streamAspectRatio;
@@ -38,10 +32,7 @@ static const int REFERENCE_HEIGHT = 720;
double accumulatedMouseDeltaX;
double accumulatedMouseDeltaY;
#if TARGET_OS_TV
UIGestureRecognizer* remotePressRecognizer;
UIGestureRecognizer* remoteLongPressRecognizer;
#endif
RelativeTouchHandler* touchHandler;
id<UserInteractionDelegate> interactionDelegate;
NSTimer* interactionTimer;
@@ -57,18 +48,11 @@ static const int REFERENCE_HEIGHT = 720;
self->interactionDelegate = interactionDelegate;
self->streamAspectRatio = (float)streamConfig.width / (float)streamConfig.height;
self->touchHandler = [[RelativeTouchHandler alloc] initWithView:self];
TemporarySettings* settings = [[[DataManager alloc] init] getSettings];
#if TARGET_OS_TV
remotePressRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(remoteButtonPressed:)];
remotePressRecognizer.allowedPressTypes = @[@(UIPressTypeSelect)];
remoteLongPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(remoteButtonLongPressed:)];
remoteLongPressRecognizer.allowedPressTypes = @[@(UIPressTypeSelect)];
[self addGestureRecognizer:remotePressRecognizer];
[self addGestureRecognizer:remoteLongPressRecognizer];
#else
#if !TARGET_OS_TV
onScreenControls = [[OnScreenControls alloc] initWithView:self controllerSup:controllerSupport swipeDelegate:swipeDelegate];
OnScreenControlsLevel level = (OnScreenControlsLevel)[settings.onscreenControls integerValue];
if (level == OnScreenControlsLevelAuto) {
@@ -149,11 +133,6 @@ static const int REFERENCE_HEIGHT = 720;
}
}
- (BOOL)isConfirmedMove:(CGPoint)currentPoint from:(CGPoint)originalPoint {
// Movements of greater than 5 pixels are considered confirmed
return hypotf(originalPoint.x - currentPoint.x, originalPoint.y - currentPoint.y) >= 5;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self handleMouseButtonEvent:BUTTON_ACTION_PRESS
forTouches:touches
@@ -168,23 +147,28 @@ static const int REFERENCE_HEIGHT = 720;
[self startInteractionTimer];
if (![onScreenControls handleTouchDownEvent:touches]) {
UITouch *touch = [[event allTouches] anyObject];
originalLocation = touchLocation = [touch locationInView:self];
touchMoved = false;
if ([[event allTouches] count] == 1 && !isDragging) {
dragTimer = [NSTimer scheduledTimerWithTimeInterval:0.650
target:self
selector:@selector(onDragStart:)
userInfo:nil
repeats:NO];
if ([[event allTouches] count] == 3) {
if (isInputingText) {
Log(LOG_D, @"Closing the keyboard");
[_keyInputField resignFirstResponder];
isInputingText = false;
} else {
Log(LOG_D, @"Opening the keyboard");
// Prepare the textbox used to capture keyboard events.
_keyInputField.delegate = self;
_keyInputField.text = @"0";
[_keyInputField becomeFirstResponder];
[_keyInputField addTarget:self action:@selector(onKeyboardPressed:) forControlEvents:UIControlEventEditingChanged];
// Undo causes issues for our state management, so turn it off
[_keyInputField.undoManager disableUndoRegistration];
isInputingText = true;
}
}
else {
[touchHandler touchesBegan:touches withEvent:event];
}
}
}
- (void)onDragStart:(NSTimer*)timer {
if (!touchMoved && !isDragging){
isDragging = true;
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
}
@@ -276,46 +260,8 @@ static const int REFERENCE_HEIGHT = 720;
hasUserInteracted = YES;
if (![onScreenControls handleTouchMovedEvent:touches]) {
if ([[event allTouches] count] == 1) {
UITouch *touch = [[event allTouches] anyObject];
CGPoint currentLocation = [touch locationInView:self];
if (touchLocation.x != currentLocation.x ||
touchLocation.y != currentLocation.y)
{
int deltaX = (currentLocation.x - touchLocation.x) * (REFERENCE_WIDTH / self.bounds.size.width);
int deltaY = (currentLocation.y - touchLocation.y) * (REFERENCE_HEIGHT / self.bounds.size.height);
if (deltaX != 0 || deltaY != 0) {
LiSendMouseMoveEvent(deltaX, deltaY);
touchLocation = currentLocation;
// If we've moved far enough to confirm this wasn't just human/machine error,
// mark it as such.
if ([self isConfirmedMove:touchLocation from:originalLocation]) {
touchMoved = true;
}
}
}
} else if ([[event allTouches] count] == 2) {
CGPoint firstLocation = [[[[event allTouches] allObjects] objectAtIndex:0] locationInView:self];
CGPoint secondLocation = [[[[event allTouches] allObjects] objectAtIndex:1] locationInView:self];
CGPoint avgLocation = CGPointMake((firstLocation.x + secondLocation.x) / 2, (firstLocation.y + secondLocation.y) / 2);
if (touchLocation.y != avgLocation.y) {
LiSendScrollEvent(avgLocation.y - touchLocation.y);
}
// If we've moved far enough to confirm this wasn't just human/machine error,
// mark it as such.
if ([self isConfirmedMove:firstLocation from:originalLocation]) {
touchMoved = true;
}
touchLocation = avgLocation;
}
[touchHandler touchesMoved:touches withEvent:event];
}
}
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
@@ -369,108 +315,18 @@ static const int REFERENCE_HEIGHT = 720;
hasUserInteracted = YES;
if (![onScreenControls handleTouchUpEvent:touches]) {
[dragTimer invalidate];
dragTimer = nil;
if (isDragging) {
isDragging = false;
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
} else if (!touchMoved) {
if ([[event allTouches] count] == 3) {
if (isInputingText) {
Log(LOG_D, @"Closing the keyboard");
[_keyInputField resignFirstResponder];
isInputingText = false;
} else {
Log(LOG_D, @"Opening the keyboard");
// Prepare the textbox used to capture keyboard events.
_keyInputField.delegate = self;
_keyInputField.text = @"0";
[_keyInputField becomeFirstResponder];
[_keyInputField addTarget:self action:@selector(onKeyboardPressed:) forControlEvents:UIControlEventEditingChanged];
// Undo causes issues for our state management, so turn it off
[_keyInputField.undoManager disableUndoRegistration];
isInputingText = true;
}
} else if ([[event allTouches] count] == 2) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
Log(LOG_D, @"Sending right mouse button press");
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
// Wait 100 ms to simulate a real button press
usleep(100 * 1000);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
});
} else if ([[event allTouches] count] == 1) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
if (!self->isDragging){
Log(LOG_D, @"Sending left mouse button press");
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
// Wait 100 ms to simulate a real button press
usleep(100 * 1000);
}
self->isDragging = false;
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
});
}
}
// We we're moving from 2+ touches to 1. Synchronize the current position
// of the active finger so we don't jump unexpectedly on the next touchesMoved
// callback when finger 1 switches on us.
if ([[event allTouches] count] - [touches count] == 1) {
NSMutableSet *activeSet = [[NSMutableSet alloc] initWithCapacity:[[event allTouches] count]];
[activeSet unionSet:[event allTouches]];
[activeSet minusSet:touches];
touchLocation = [[activeSet anyObject] locationInView:self];
// Mark this touch as moved so we don't send a left mouse click if the user
// right clicks without moving their other finger.
touchMoved = true;
}
[touchHandler touchesEnded:touches withEvent:event];
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[dragTimer invalidate];
dragTimer = nil;
if (isDragging) {
isDragging = false;
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
}
[touchHandler touchesCancelled:touches withEvent:event];
[self handleMouseButtonEvent:BUTTON_ACTION_RELEASE
forTouches:touches
withEvent:event];
}
#if TARGET_OS_TV
- (void)remoteButtonPressed:(id)sender {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
Log(LOG_D, @"Sending left mouse button press");
// Mark this as touchMoved to avoid a duplicate press on touch up
self->touchMoved = true;
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
// Wait 100 ms to simulate a real button press
usleep(100 * 1000);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
});
}
- (void)remoteButtonLongPressed:(id)sender {
Log(LOG_D, @"Holding left mouse button");
isDragging = true;
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
#else
#if !TARGET_OS_TV
- (void) updateCursorLocation:(CGPoint)location {
// These are now relative to the StreamView, however we need to scale them
// further to make them relative to the actual video portion.

View File

@@ -8,6 +8,8 @@
/* Begin PBXBuildFile section */
693B3A9B218638CD00982F7B /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 693B3A9A218638CD00982F7B /* Settings.bundle */; };
9819CC14254F107A008A7C8E /* RelativeTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9819CC13254F107A008A7C8E /* RelativeTouchHandler.m */; };
9819CC1D254F1730008A7C8E /* RelativeTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9819CC13254F107A008A7C8E /* RelativeTouchHandler.m */; };
9827E7A32514366900F25707 /* HapticContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 9827E7A22514366900F25707 /* HapticContext.m */; };
9827E7A42514366900F25707 /* HapticContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 9827E7A22514366900F25707 /* HapticContext.m */; };
9832D1361BBCD5C50036EF48 /* TemporaryApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 9832D1351BBCD5C50036EF48 /* TemporaryApp.m */; };
@@ -159,6 +161,8 @@
/* Begin PBXFileReference section */
693B3A9A218638CD00982F7B /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
98132E8C20BC9A62007A053F /* Moonlight v1.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.1.xcdatamodel"; sourceTree = "<group>"; };
9819CC12254F107A008A7C8E /* RelativeTouchHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RelativeTouchHandler.h; sourceTree = "<group>"; };
9819CC13254F107A008A7C8E /* RelativeTouchHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RelativeTouchHandler.m; sourceTree = "<group>"; };
9827E7A22514366900F25707 /* HapticContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HapticContext.m; sourceTree = "<group>"; };
9827E7A7251436EA00F25707 /* HapticContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HapticContext.h; sourceTree = "<group>"; };
9832D1341BBCD5C50036EF48 /* TemporaryApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TemporaryApp.h; path = Database/TemporaryApp.h; sourceTree = "<group>"; };
@@ -539,6 +543,8 @@
9897B6A32212610800966419 /* Controller.h */,
9827E7A22514366900F25707 /* HapticContext.m */,
9827E7A7251436EA00F25707 /* HapticContext.h */,
9819CC12254F107A008A7C8E /* RelativeTouchHandler.h */,
9819CC13254F107A008A7C8E /* RelativeTouchHandler.m */,
);
path = Input;
sourceTree = "<group>";
@@ -988,6 +994,7 @@
FB1A67B7213245D500507771 /* ServerInfoResponse.m in Sources */,
FB1A67B9213245D500507771 /* AppAssetResponse.m in Sources */,
FB1A67BB213245D500507771 /* AppListResponse.m in Sources */,
9819CC1D254F1730008A7C8E /* RelativeTouchHandler.m in Sources */,
FB1A67AB213245C500507771 /* CryptoManager.m in Sources */,
FB1A67AF213245C500507771 /* IdManager.m in Sources */,
FB1A67A3213245BD00507771 /* Connection.m in Sources */,
@@ -1003,6 +1010,7 @@
buildActionMask = 2147483647;
files = (
FB290D0719B2C406004C83CF /* Limelight.xcdatamodeld in Sources */,
9819CC14254F107A008A7C8E /* RelativeTouchHandler.m in Sources */,
FB1A674D2131E65900507771 /* KeyboardSupport.m in Sources */,
FB89463219F646E200339C8A /* VideoDecoderRenderer.m in Sources */,
FB290D0419B2C406004C83CF /* AppDelegate.m in Sources */,