IOS7: Implement support for Apple TV remote

The Apple TV remote has a touch area which functions as a touch
controller. It also has a few buttons that can be programmed for
different actions.

The "touchpad mode" is not really relevant for the Apple TV remote.
However the pointer should be moved when swiping on the touch area
on the remote. Since we don't want to generate actions on touchBegan
and touchEnded for the remote, make sure these are only triggered if
the touch is made on direct contact with the screen, UITouchTypeDirect.

Implement the button handling by implement the microGamepad profile in
the GamepadController class. Only buttons A, X and the menu buttons are
relevant since the touch area is using the touch controller class.
The tvOS simulator however doesn't call the lambda functions defined for
the microGamepad buttons, hence the implementation of the "pressesBegan"
and "pressesEnded" which is called instead when running the simulator.

Implement common handling of the menu button. If the menu button is
pressed the soft keyboard will be shown. If pressing the menu button
again the soft keyboard will be hidden. If pressing the menu button
a third time the application will be suspended.

Implement new gesture recognizers that can be used with the Apple TV
remote. Up/down/left/right actions are triggered on press on the arrow
buttons, or tap on the edges of the touch area.

A long press, 5 seconds, of "Play/Pause" button toggles Mouse-click-and-
drag mode needed by some games.
This commit is contained in:
Lars Sundström 2023-01-06 20:17:29 +01:00 committed by Thierry Crozat
parent c24ee0d61e
commit 5aeef796bc
5 changed files with 130 additions and 36 deletions

View file

@ -27,10 +27,6 @@
#include "backends/platform/ios7/ios7_video.h"
#include <GameController/GameController.h>
// This value will be multiplied with the x and y values of a thumbstick
// value (-1, 1). Should ideally be configurable through ScummVM settings
#define GAMEPAD_SENSITIVITY 20
@implementation GamepadController {
GCController *_controller;
}
@ -50,10 +46,25 @@
}
- (void)controllerDidConnect:(NSNotification *)notification {
[self setIsConnected:YES];
_controller = (GCController*)notification.object;
#if TARGET_OS_TV
if (_controller.microGamepad != nil) {
[self setIsConnected:YES];
_controller.microGamepad.buttonA.valueChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[self handleJoystickButtonAction:Common::JOYSTICK_BUTTON_A isPressed:pressed];
};
_controller.microGamepad.buttonX.valueChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
// Map button X to button B because B is mapped to left button
[self handleJoystickButtonAction:Common::JOYSTICK_BUTTON_B isPressed:pressed];
};
}
#endif
if (_controller.extendedGamepad != nil) {
[self setIsConnected:YES];
_controller.extendedGamepad.leftThumbstick.valueChangedHandler = ^(GCControllerDirectionPad * _Nonnull dpad, float xValue, float yValue) {
// Convert the given axis values in float (-1 to 1) to ScummVM Joystick
// Axis value as integers (0 to int16_max)
@ -105,12 +116,6 @@
_controller.extendedGamepad.rightShoulder.valueChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[self handleJoystickButtonAction:Common::JOYSTICK_BUTTON_RIGHT_SHOULDER isPressed:pressed];
};
#ifdef __IPHONE_13_0
_controller.extendedGamepad.buttonMenu.valueChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
[[self view] addEvent:InternalEvent(kInputMainMenu, 0, 0)];
};
#endif
}
}

View file

@ -59,23 +59,21 @@
NSSet *allTouches = [event allTouches];
if (allTouches.count == 1) {
_firstTouch = [allTouches anyObject];
if (_firstTouch.type == UITouchTypeDirect) {
if (iOS7_touchpadModeEnabled()) {
// In touchpad mode the action should occur on the current pointer position
[self handleMouseButtonAction:kGameControllerMouseButtonLeft isPressed:YES at:[[self view] pointerPosition]];
} else {
// Only move the pointer to the new position if not in touchpadMode else it's very hard to click on items
[self handlePointerMoveTo:[_firstTouch locationInView: [self view]]];
[self handleMouseButtonAction:kGameControllerMouseButtonLeft isPressed:YES at:[_firstTouch locationInView:[self view]]];
}
if (iOS7_touchpadModeEnabled()) {
// In touchpad mode the action should occur on the current pointer position
[self handleMouseButtonAction:kGameControllerMouseButtonLeft isPressed:YES at:[[self view] pointerPosition]];
} else if (_firstTouch.type == UITouchTypeDirect) {
// Only move the pointer to the new position if not in touchpadMode else it's very hard to click on items
[self handlePointerMoveTo:[_firstTouch locationInView: [self view]]];
[self handleMouseButtonAction:kGameControllerMouseButtonLeft isPressed:YES at:[_firstTouch locationInView:[self view]]];
}
} else if (allTouches.count == 2) {
_secondTouch = [self secondTouchOtherTouchThan:_firstTouch in:allTouches];
if (_secondTouch && _secondTouch.type == UITouchTypeDirect) {
if (_secondTouch) {
if (iOS7_touchpadModeEnabled()) {
// In touchpad mode the action should occur on the current pointer position
[self handleMouseButtonAction:kGameControllerMouseButtonRight isPressed:YES at:[[self view] pointerPosition]];
} else {
} else if (_secondTouch.type == UITouchTypeDirect) {
[self handleMouseButtonAction:kGameControllerMouseButtonRight isPressed:YES at:[_secondTouch locationInView:[self view]]];
}
}
@ -87,18 +85,16 @@
for (UITouch *touch in allTouches) {
if (touch == _firstTouch ||
touch == _secondTouch) {
if (touch.type == UITouchTypeDirect) {
if (iOS7_touchpadModeEnabled()) {
// Calculate new position for the pointer based on delta of the current and previous location of the touch
CGPoint pointerLocation = [[self view] pointerPosition];
CGPoint touchLocation = [touch locationInView:[self view]];
CGPoint previousTouchLocation = [touch previousLocationInView:[self view]];
pointerLocation.y += touchLocation.y - previousTouchLocation.y;
pointerLocation.x += touchLocation.x - previousTouchLocation.x;
[self handlePointerMoveTo:pointerLocation];
} else {
[self handlePointerMoveTo:[touch locationInView: [self view]]];
}
if (iOS7_touchpadModeEnabled() || _firstTouch.type == UITouchTypeIndirect) {
// Calculate new position for the pointer based on delta of the current and previous location of the touch
CGPoint pointerLocation = [[self view] pointerPosition];
CGPoint touchLocation = [touch locationInView:[self view]];
CGPoint previousTouchLocation = [touch previousLocationInView:[self view]];
pointerLocation.y += touchLocation.y - previousTouchLocation.y;
pointerLocation.x += touchLocation.x - previousTouchLocation.x;
[self handlePointerMoveTo:pointerLocation];
} else if (_firstTouch.type == UITouchTypeDirect) {
[self handlePointerMoveTo:[touch locationInView: [self view]]];
}
}
}
@ -108,12 +104,16 @@
NSSet *allTouches = [event allTouches];
if (allTouches.count == 1) {
UITouch *touch = [allTouches anyObject];
if (touch.type == UITouchTypeDirect) {
if (iOS7_touchpadModeEnabled()) {
[self handleMouseButtonAction:kGameControllerMouseButtonLeft isPressed:NO at:[[self view] pointerPosition]];
} else if (touch.type == UITouchTypeDirect) {
[self handleMouseButtonAction:kGameControllerMouseButtonLeft isPressed:NO at:[touch locationInView:[self view]]];
}
} else if (allTouches.count == 2) {
UITouch *touch = [[allTouches allObjects] objectAtIndex:1];
if (touch.type == UITouchTypeDirect) {
if (iOS7_touchpadModeEnabled()) {
[self handleMouseButtonAction:kGameControllerMouseButtonRight isPressed:NO at:[[self view] pointerPosition]];
} else if (touch.type == UITouchTypeDirect) {
[self handleMouseButtonAction:kGameControllerMouseButtonRight isPressed:NO at:[touch locationInView:[self view]]];
}
}

View file

@ -22,6 +22,7 @@
// Disable symbol overrides so that we can use system headers.
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "common/events.h"
#include "backends/platform/ios7/ios7_video.h"
#include "backends/platform/ios7/ios7_touch_controller.h"
#include "backends/platform/ios7/ios7_mouse_controller.h"
@ -420,6 +421,34 @@ uint getSizeNextPOT(uint size) {
[swipeUp3 release];
[swipeDown3 release];
[doubleTapTwoFingers release];
#elif TARGET_OS_TV
UITapGestureRecognizer *tapUpGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeUp:)];
[tapUpGestureRecognizer setAllowedPressTypes:@[@(UIPressTypeUpArrow)]];
UITapGestureRecognizer *tapDownGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeDown:)];
[tapDownGestureRecognizer setAllowedPressTypes:@[@(UIPressTypeDownArrow)]];
UITapGestureRecognizer *tapLeftGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeLeft:)];
[tapLeftGestureRecognizer setAllowedPressTypes:@[@(UIPressTypeLeftArrow)]];
UITapGestureRecognizer *tapRightGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeRight:)];
[tapRightGestureRecognizer setAllowedPressTypes:@[@(UIPressTypeRightArrow)]];
UILongPressGestureRecognizer *longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(showKeyboard)];
[longPressGestureRecognizer setAllowedPressTypes:@[[NSNumber numberWithInteger:UIPressTypePlayPause]]];
[longPressGestureRecognizer setMinimumPressDuration:1.0];
[self addGestureRecognizer:tapUpGestureRecognizer];
[self addGestureRecognizer:tapDownGestureRecognizer];
[self addGestureRecognizer:tapLeftGestureRecognizer];
[self addGestureRecognizer:tapRightGestureRecognizer];
[self addGestureRecognizer:longPressGestureRecognizer];
[tapUpGestureRecognizer release];
[tapDownGestureRecognizer release];
[tapLeftGestureRecognizer release];
[tapRightGestureRecognizer release];
[longPressGestureRecognizer release];
#endif
}
@ -918,6 +947,58 @@ uint getSizeNextPOT(uint size) {
}
}
#if TARGET_OS_TV
// UIKit calls these methods when a button is pressed by the user.
// These methods are used to determine which button was pressed and
// to take any needed actions. The default implementation of these
// methods forwardsm the message up the responder chain.
// Button presses are already handled by the GameController class for
// connected game controllers (including the Apple TV remote).
// The Apple TV remote is not registered as a micro game controller
// when running the application in tvOS simulator, hence these methods
// only needs to be implemented for the tvOS simulator to handle presses
// on the Apple TV remote.
-(void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
#if TARGET_OS_SIMULATOR
UIPress *press = [presses anyObject];
if (press.type == UIPressTypeMenu) {
// Trigger on pressesEnded
} else if (press.type == UIPressTypeSelect || press.type == UIPressTypePlayPause) {
[self addEvent:InternalEvent(kInputJoystickButtonDown, press.type == UIPressTypeSelect ? Common::JOYSTICK_BUTTON_A : Common::JOYSTICK_BUTTON_B, 0)];
}
else {
[super pressesBegan:presses withEvent:event];
}
#endif
}
-(void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
#if TARGET_OS_SIMULATOR
UIPress *press = [presses anyObject];
if (press.type == UIPressTypeMenu) {
[self handleMainMenuKey];
} else if (press.type == UIPressTypeSelect || press.type == UIPressTypePlayPause) {
[self addEvent:InternalEvent(kInputJoystickButtonUp, press.type == UIPressTypeSelect ? Common::JOYSTICK_BUTTON_A : Common::JOYSTICK_BUTTON_B, 0)];
}
else {
[super pressesEnded:presses withEvent:event];
}
#endif
}
-(void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
#if TARGET_OS_SIMULATOR
[super pressesChanged:presses withEvent:event];
#endif
}
-(void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
#if TARGET_OS_SIMULATOR
[super pressesCancelled:presses withEvent:event];
#endif
}
#endif
#if TARGET_OS_IOS
- (void)keyboardPinch:(UIPinchGestureRecognizer *)recognizer {
if ([recognizer scale] < 0.8)