/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
// Disable symbol overrides so that we can use system headers.
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "common/events.h"
#include "common/config-manager.h"
#include "backends/platform/ios7/ios7_video.h"
#include "backends/platform/ios7/ios7_touch_controller.h"
#include "backends/platform/ios7/ios7_mouse_controller.h"
#include "backends/platform/ios7/ios7_gamepad_controller.h"
#include "backends/platform/ios7/ios7_app_delegate.h"
#ifdef __IPHONE_14_0
#include
#endif
#if 0
static long g_lastTick = 0;
static int g_frames = 0;
#endif
void printError(const char *error_message) {
NSString *messageString = [NSString stringWithUTF8String:error_message];
NSLog(@"%@", messageString);
fprintf(stderr, "%s\n", error_message);
}
#define printOpenGLError() printOglError(__FILE__, __LINE__)
void printOglError(const char *file, int line) {
GLenum glErr = glGetError();
while (glErr != GL_NO_ERROR) {
Common::String error = Common::String::format("glError: %u (%s: %d)", glErr, file, line);
printError(error.c_str());
glErr = glGetError();
}
}
bool iOS7_isBigDevice() {
#if TARGET_OS_IOS
return UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
#elif TARGET_OS_TV
return false;
#endif
}
static inline void execute_on_main_thread(void (^block)(void)) {
if ([NSThread currentThread] == [NSThread mainThread]) {
block();
}
else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
bool iOS7_fetchEvent(InternalEvent *event) {
__block bool fetched;
execute_on_main_thread(^{
fetched = [[iOS7AppDelegate iPhoneView] fetchEvent:event];
});
return fetched;
}
@implementation iPhoneView {
#if TARGET_OS_IOS
UIButton *_menuButton;
UIButton *_toggleTouchModeButton;
UITapGestureRecognizer *oneFingerTapGesture;
UITapGestureRecognizer *twoFingerTapGesture;
UITapGestureRecognizer *pencilThreeTapGesture;
UILongPressGestureRecognizer *oneFingerLongPressGesture;
UILongPressGestureRecognizer *twoFingerLongPressGesture;
UILongPressGestureRecognizer *pencilTwoTapLongTouchGesture;
CGPoint touchesBegan;
#endif
}
+ (Class)layerClass {
return [CAEAGLLayer class];
}
// According to Apple doc layoutSublayersOfLayer: is supported from iOS 10.0.
// This doesn't seem to be correct since the instance method layoutSublayers,
// supported from iOS 2.0, default calls the layoutSublayersOfLayer: method
// of the layer’s delegate object. It's been verified that this function is
// called in at least iOS 9.3.5.
- (void)layoutSublayersOfLayer:(CAEAGLLayer *)layer {
if (layer == self.layer) {
[self addEvent:InternalEvent(kInputScreenChanged, 0, 0)];
}
[super layoutSublayersOfLayer:layer];
}
- (void)createContext {
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
eaglLayer.opaque = YES;
eaglLayer.drawableProperties = @{
kEAGLDrawablePropertyRetainedBacking: @NO,
kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8,
};
_mainContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
// Not all iDevices support OpenGLES 3, fallback to OpenGLES 2
if (_mainContext == nil) {
_mainContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
}
// In case creating the OpenGL ES context failed, we will error out here.
if (_mainContext == nil) {
printError("Could not create OpenGL ES context.");
abort();
}
// main thread will always use _mainContext
[EAGLContext setCurrentContext:_mainContext];
}
- (uint)createOpenGLContext {
// Create OpenGL context with the sharegroup from the context
// connected to the Apple Core Animation layer
if (!_openGLContext && _mainContext) {
_openGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3 sharegroup:_mainContext.sharegroup];
// Not all iDevices support OpenGLES 3, fallback to OpenGLES 2
if (_openGLContext == nil) {
_openGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:_mainContext.sharegroup];
}
if (_openGLContext == nil) {
printError("Could not create OpenGL ES context using sharegroup");
abort();
}
// background thread will always use _openGLContext
if ([EAGLContext setCurrentContext:_openGLContext]) {
[self setupRenderBuffer];
}
}
return _viewRenderbuffer;
}
- (void)destroyOpenGLContext {
[_openGLContext release];
_openGLContext = nil;
}
- (void)refreshScreen {
glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer);
[_openGLContext presentRenderbuffer:GL_RENDERBUFFER];
}
- (int)getScreenWidth {
return _renderBufferWidth;
}
- (int)getScreenHeight {
return _renderBufferHeight;
}
- (void)setupRenderBuffer {
execute_on_main_thread(^{
if (!_viewRenderbuffer) {
glGenRenderbuffers(1, &_viewRenderbuffer);
printOpenGLError();
}
glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer);
printOpenGLError();
if (![_mainContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id ) self.layer]) {
printError("Failed renderbufferStorage");
}
// Retrieve the render buffer size. This *should* match the frame size,
// i.e. g_fullWidth and g_fullHeight.
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_renderBufferWidth);
printOpenGLError();
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_renderBufferHeight);
printOpenGLError();
});
}
- (void)deleteRenderbuffer {
glDeleteRenderbuffers(1, &_viewRenderbuffer);
}
- (void)setupGestureRecognizers {
#if TARGET_OS_IOS
UILongPressGestureRecognizer *longPressKeyboard = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressKeyboard:)];
[_toggleTouchModeButton addGestureRecognizer:longPressKeyboard];
[longPressKeyboard setNumberOfTouchesRequired:1];
[longPressKeyboard release];
oneFingerTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(oneFingerTap:)];
[oneFingerTapGesture setNumberOfTapsRequired:1];
[oneFingerTapGesture setNumberOfTouchesRequired:1];
[oneFingerTapGesture setAllowedTouchTypes:@[@(UITouchTypeDirect),@(UITouchTypePencil)]];
[oneFingerTapGesture setDelaysTouchesBegan:NO];
[oneFingerTapGesture setDelaysTouchesEnded:NO];
twoFingerTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingerTap:)];
[twoFingerTapGesture setNumberOfTapsRequired:1];
[twoFingerTapGesture setNumberOfTouchesRequired:2];
[twoFingerTapGesture setAllowedTouchTypes:@[@(UITouchTypeDirect)]];
[twoFingerTapGesture setDelaysTouchesBegan:NO];
[twoFingerTapGesture setDelaysTouchesEnded:NO];
pencilThreeTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pencilThreeTap:)];
[pencilThreeTapGesture setNumberOfTapsRequired:3];
[pencilThreeTapGesture setNumberOfTouchesRequired:1];
[pencilThreeTapGesture setAllowedTouchTypes:@[@(UITouchTypePencil)]];
[pencilThreeTapGesture setDelaysTouchesBegan:NO];
[pencilThreeTapGesture setDelaysTouchesEnded:NO];
// Default long press duration is 0.5 seconds which suits us well
oneFingerLongPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(oneFingerLongPress:)];
[oneFingerLongPressGesture setNumberOfTouchesRequired:1];
[oneFingerLongPressGesture setAllowedTouchTypes:@[@(UITouchTypeDirect),@(UITouchTypePencil)]];
[oneFingerLongPressGesture setDelaysTouchesBegan:NO];
[oneFingerLongPressGesture setDelaysTouchesEnded:NO];
[oneFingerLongPressGesture setCancelsTouchesInView:NO];
[oneFingerLongPressGesture canPreventGestureRecognizer:oneFingerTapGesture];
twoFingerLongPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingerLongPress:)];
[twoFingerLongPressGesture setNumberOfTouchesRequired:2];
[twoFingerLongPressGesture setAllowedTouchTypes:@[@(UITouchTypeDirect)]];
[twoFingerLongPressGesture setDelaysTouchesBegan:NO];
[twoFingerLongPressGesture setDelaysTouchesEnded:NO];
[twoFingerLongPressGesture setCancelsTouchesInView:NO];
[twoFingerLongPressGesture canPreventGestureRecognizer:twoFingerTapGesture];
pencilTwoTapLongTouchGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(pencilTwoTapLongTouch:)];
[pencilTwoTapLongTouchGesture setNumberOfTouchesRequired:1];
[pencilTwoTapLongTouchGesture setNumberOfTapsRequired:2];
[pencilTwoTapLongTouchGesture setAllowedTouchTypes:@[@(UITouchTypePencil)]];
[pencilTwoTapLongTouchGesture setMinimumPressDuration:0.5];
[pencilTwoTapLongTouchGesture setDelaysTouchesBegan:NO];
[pencilTwoTapLongTouchGesture setDelaysTouchesEnded:NO];
[pencilTwoTapLongTouchGesture setCancelsTouchesInView:NO];
UIPinchGestureRecognizer *pinchKeyboard = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(keyboardPinch:)];
UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeRight:)];
swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
swipeRight.numberOfTouchesRequired = 2;
swipeRight.delaysTouchesBegan = NO;
swipeRight.delaysTouchesEnded = NO;
UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeLeft:)];
swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
swipeLeft.numberOfTouchesRequired = 2;
swipeLeft.delaysTouchesBegan = NO;
swipeLeft.delaysTouchesEnded = NO;
UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeUp:)];
swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
swipeUp.numberOfTouchesRequired = 2;
swipeUp.delaysTouchesBegan = NO;
swipeUp.delaysTouchesEnded = NO;
UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeDown:)];
swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
swipeDown.numberOfTouchesRequired = 2;
swipeDown.delaysTouchesBegan = NO;
swipeDown.delaysTouchesEnded = NO;
UISwipeGestureRecognizer *swipeRight3 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeRight:)];
swipeRight3.direction = UISwipeGestureRecognizerDirectionRight;
swipeRight3.numberOfTouchesRequired = 3;
swipeRight3.delaysTouchesBegan = NO;
swipeRight3.delaysTouchesEnded = NO;
UISwipeGestureRecognizer *swipeLeft3 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeLeft:)];
swipeLeft3.direction = UISwipeGestureRecognizerDirectionLeft;
swipeLeft3.numberOfTouchesRequired = 3;
swipeLeft3.delaysTouchesBegan = NO;
swipeLeft3.delaysTouchesEnded = NO;
UISwipeGestureRecognizer *swipeUp3 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeUp:)];
swipeUp3.direction = UISwipeGestureRecognizerDirectionUp;
swipeUp3.numberOfTouchesRequired = 3;
swipeUp3.delaysTouchesBegan = NO;
swipeUp3.delaysTouchesEnded = NO;
UISwipeGestureRecognizer *swipeDown3 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeDown:)];
swipeDown3.direction = UISwipeGestureRecognizerDirectionDown;
swipeDown3.numberOfTouchesRequired = 3;
swipeDown3.delaysTouchesBegan = NO;
swipeDown3.delaysTouchesEnded = NO;
UITapGestureRecognizer *doubleTapTwoFingers = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersDoubleTap:)];
doubleTapTwoFingers.numberOfTapsRequired = 2;
doubleTapTwoFingers.numberOfTouchesRequired = 2;
doubleTapTwoFingers.delaysTouchesBegan = NO;
doubleTapTwoFingers.delaysTouchesEnded = NO;
[self addGestureRecognizer:pinchKeyboard];
[self addGestureRecognizer:swipeRight];
[self addGestureRecognizer:swipeLeft];
[self addGestureRecognizer:swipeUp];
[self addGestureRecognizer:swipeDown];
[self addGestureRecognizer:swipeRight3];
[self addGestureRecognizer:swipeLeft3];
[self addGestureRecognizer:swipeUp3];
[self addGestureRecognizer:swipeDown3];
[self addGestureRecognizer:doubleTapTwoFingers];
[self addGestureRecognizer:oneFingerTapGesture];
[self addGestureRecognizer:twoFingerTapGesture];
[self addGestureRecognizer:oneFingerLongPressGesture];
[self addGestureRecognizer:twoFingerLongPressGesture];
[self addGestureRecognizer:pencilThreeTapGesture];
[self addGestureRecognizer:pencilTwoTapLongTouchGesture];
[pinchKeyboard release];
[swipeRight release];
[swipeLeft release];
[swipeUp release];
[swipeDown release];
[swipeRight3 release];
[swipeLeft3 release];
[swipeUp3 release];
[swipeDown3 release];
[doubleTapTwoFingers release];
[oneFingerTapGesture release];
[twoFingerTapGesture release];
[oneFingerLongPressGesture release];
[twoFingerLongPressGesture release];
[pencilThreeTapGesture release];
[pencilTwoTapLongTouchGesture 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
}
- (id)initWithFrame:(struct CGRect)frame {
self = [super initWithFrame: frame];
_backgroundSaveStateTask = UIBackgroundTaskInvalid;
#if TARGET_OS_IOS
_currentOrientation = UIInterfaceOrientationUnknown;
// On-screen control buttons
UIImage *menuBtnImage = [UIImage imageNamed:@"ic_action_menu"];
_menuButton = [[UIButton alloc] initWithFrame:CGRectMake(self.frame.size.width - menuBtnImage.size.width, 0, menuBtnImage.size.width, menuBtnImage.size.height)];
[_menuButton setImage:menuBtnImage forState:UIControlStateNormal];
[_menuButton setAlpha:0.5];
[_menuButton addTarget:self action:@selector(handleMainMenuKey) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_menuButton];
// The mode will be updated when OSystem has loaded its presets
UIImage *toggleTouchModeBtnImage = [UIImage imageNamed:@"ic_action_touchpad"];
_toggleTouchModeButton = [[UIButton alloc] initWithFrame:CGRectMake(self.frame.size.width - menuBtnImage.size.width - toggleTouchModeBtnImage.size.width, 0, toggleTouchModeBtnImage.size.width, toggleTouchModeBtnImage.size.height)];
[_toggleTouchModeButton setImage:toggleTouchModeBtnImage forState:UIControlStateNormal];
[_toggleTouchModeButton setAlpha:0.5];
[_toggleTouchModeButton addTarget:self action:@selector(triggerTouchModeChanged) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_toggleTouchModeButton];
#endif
[self setupGestureRecognizers];
if (@available(iOS 14.0, tvOS 14.0, *)) {
_controllers.push_back([[MouseController alloc] initWithView:self]);
_controllers.push_back([[GamepadController alloc] initWithView:self]);
}
_controllers.push_back([[TouchController alloc] initWithView:self]);
[self setContentScaleFactor:[[UIScreen mainScreen] scale]];
_keyboardView = nil;
_eventLock = [[NSLock alloc] init];
// Initialize the OpenGL ES context
[self createContext];
return self;
}
#if TARGET_OS_IOS
- (void)triggerTouchModeChanged {
BOOL hwKeyboardConnected = NO;
#ifdef __IPHONE_14_0
if (@available(iOS 14.0, *)) {
if (GCKeyboard.coalescedKeyboard != nil) {
hwKeyboardConnected = YES;
}
}
#endif
if ([self isKeyboardShown] && !hwKeyboardConnected) {
[self hideKeyboard];
} else {
[self addEvent:InternalEvent(kInputTouchModeChanged, 0, 0)];
}
}
- (void)updateTouchMode {
UIImage *btnImage;
TouchMode currentTouchMode = iOS7_getCurrentTouchMode();
bool isEnabled = ConfMan.getBool("onscreen_control");
if (currentTouchMode == kTouchModeDirect) {
btnImage = [UIImage imageNamed:@"ic_action_mouse"];
} else if (currentTouchMode == kTouchModeTouchpad) {
btnImage = [UIImage imageNamed:@"ic_action_touchpad"];
} else {
return;
}
[_toggleTouchModeButton setImage: btnImage forState:UIControlStateNormal];
[_toggleTouchModeButton setEnabled:isEnabled];
[_toggleTouchModeButton setHidden:!isEnabled];
[_menuButton setEnabled:isEnabled && [self isInGame]];
[_menuButton setHidden:!isEnabled || ![self isInGame]];
}
- (BOOL)isiOSAppOnMac {
#ifdef __IPHONE_14_0
if (@available(iOS 14.0, *)) {
return [NSProcessInfo processInfo].isiOSAppOnMac;
}
#endif
return NO;
}
#endif
- (void)dealloc {
[_keyboardView release];
[_eventLock release];
[super dealloc];
}
- (void)initSurface {
[self setupRenderBuffer];
if (_keyboardView == nil) {
_keyboardView = [[SoftKeyboard alloc] initWithFrame:CGRectZero];
[_keyboardView setInputDelegate:self];
[self addSubview:[_keyboardView inputView]];
[self addSubview: _keyboardView];
}
[self adjustViewFrameForSafeArea];
#if TARGET_OS_IOS
// The virtual controller does not fit in portrait orientation on iPhones
// Make sure to disconnect the virtual controller in those cases
if (!iOS7_isBigDevice() &&
(_currentOrientation == UIInterfaceOrientationPortrait ||
_currentOrientation == UIInterfaceOrientationPortraitUpsideDown)) {
[self virtualController:false];
} else {
// Connect or disconnect the virtual controller
[self virtualController:ConfMan.getBool("gamepad_controller")];
}
#endif
}
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
-(void)adjustViewFrameForSafeArea {
// The code below does not quite compile with SDKs older than 11.0.
// warning: instance method '-safeAreaInsets' not found (return type defaults to 'id')
// error: no viable conversion from 'id' to 'UIEdgeInsets'
// So for now disable this code when compiled with an older SDK, which means it is only
// available when running on iOS 11+ if it has been compiled on iOS 11+
#ifdef __IPHONE_11_0
if ( @available(iOS 11, tvOS 11, *) ) {
#if TARGET_OS_IOS
UIEdgeInsets inset = [[[UIApplication sharedApplication] keyWindow] safeAreaInsets];
// Skip the safe area inset at the bottom.
// We want to utilize as much screen area as possible and few
// games and launcher elements are put at the very bottom of
// the screen.
// The insets seem to be expressed in logical coordinate space
// (measured in points) while we expect the device coordinate
// space (measured in pixels), so we scale it using contentScaleFactor
CGFloat scale = [self contentScaleFactor];
iOS7_setSafeAreaInsets(inset.left * scale, inset.right * scale, inset.top * scale, 0);
// Add a margin to the on-screen control buttons. The shape of the iPhone
// screen corners on the later models have the shape of squircles rather
// than circles which cause button images to become cropped if placed too
// close to the corner. iPads have 90 degrees screen corners and does have
// the same problem with cropped images. However add the same margin for
// consistency.
const CGFloat margin = 20;
// Touch mode button on top
[_toggleTouchModeButton setFrame:CGRectMake(self.frame.size.width - _toggleTouchModeButton.imageView.image.size.width - margin, margin, _toggleTouchModeButton.imageView.image.size.width, _toggleTouchModeButton.imageView.image.size.height)];
// Burger menu button below
[_menuButton setFrame:CGRectMake(self.frame.size.width - _menuButton.imageView.image.size.width - margin, _toggleTouchModeButton.imageView.image.size.height + margin, _menuButton.imageView.image.size.width, _menuButton.imageView.image.size.height)];
#endif
}
#endif
}
#ifdef __IPHONE_11_0
// This delegate method is called when the safe area of the view changes
-(void)safeAreaInsetsDidChange {
[super safeAreaInsetsDidChange];
[self adjustViewFrameForSafeArea];
}
#endif
- (void)addEvent:(InternalEvent)event {
[_eventLock lock];
_events.push_back(event);
[_eventLock unlock];
}
- (bool)fetchEvent:(InternalEvent *)event {
[_eventLock lock];
if (_events.empty()) {
[_eventLock unlock];
return false;
}
*event = *_events.begin();
_events.pop_front();
[_eventLock unlock];
return true;
}
- (bool)isGamepadControllerSupported {
if (@available(iOS 14.0, tvOS 14.0, *)) {
// Both virtual and hardware controllers are supported
return true;
} else {
return false;
}
}
#if TARGET_OS_IOS
- (void)enableGestures:(BOOL)enabled {
[oneFingerTapGesture setEnabled:enabled];
[twoFingerTapGesture setEnabled:enabled];
[oneFingerLongPressGesture setEnabled:enabled];
[twoFingerLongPressGesture setEnabled:enabled];
[pencilThreeTapGesture setEnabled:enabled];
[pencilTwoTapLongTouchGesture setEnabled:enabled];
}
#endif
- (void)virtualController:(bool)connect {
if (@available(iOS 15.0, *)) {
for (GameController *c : _controllers) {
if ([c isKindOfClass:GamepadController.class]) {
[(GamepadController*)c virtualController:connect];
#if TARGET_OS_IOS
if (connect) {
[self enableGestures:NO];
} else {
[self enableGestures:YES];
}
#endif
}
}
}
}
#if TARGET_OS_IOS
- (void)interfaceOrientationChanged:(UIInterfaceOrientation)orientation {
ScreenOrientation screenOrientation;
if (orientation == UIInterfaceOrientationPortrait) {
screenOrientation = kScreenOrientationPortrait;
} else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
screenOrientation = kScreenOrientationFlippedPortrait;
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
screenOrientation = kScreenOrientationLandscape;
} else { // UIInterfaceOrientationLandscapeLeft
screenOrientation = kScreenOrientationFlippedLandscape;
}
// This function is executed also on older device models. The screens
// have sharp corners and no safe area insets. Use a constant margin.
const CGFloat margin = 10;
// Touch mode button on top
[_toggleTouchModeButton setFrame:CGRectMake(self.frame.size.width - _toggleTouchModeButton.imageView.image.size.width - margin, margin, _toggleTouchModeButton.imageView.image.size.width, _toggleTouchModeButton.imageView.image.size.height)];
// Burger menu button below
[_menuButton setFrame:CGRectMake(self.frame.size.width - _menuButton.imageView.image.size.width - margin, _toggleTouchModeButton.imageView.image.size.height + margin, _menuButton.imageView.image.size.width, _menuButton.imageView.image.size.height)];
[self addEvent:InternalEvent(kInputOrientationChanged, screenOrientation, 0)];
if (UIInterfaceOrientationIsLandscape(orientation)) {
[self hideKeyboard];
} else {
// Automatically open the keyboard if changing orientation in a game
if ([self isInGame])
[self showKeyboard];
}
}
#endif
- (void)showKeyboard {
[_keyboardView showKeyboard];
#if TARGET_OS_IOS
[_toggleTouchModeButton setImage:[UIImage imageNamed:@"ic_action_keyboard"] forState:UIControlStateNormal];
#endif
}
- (void)hideKeyboard {
[_keyboardView hideKeyboard];
#if TARGET_OS_IOS
[self updateTouchMode];
#endif
}
- (BOOL)isKeyboardShown {
return [_keyboardView isKeyboardShown];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
#if TARGET_OS_IOS
UITouch *touch = [touches anyObject];
touchesBegan = [touch locationInView:self];
#endif
for (GameController *c : _controllers) {
if ([c isKindOfClass:TouchController.class]) {
[(TouchController *)c touchesBegan:touches withEvent:event];
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
#if TARGET_OS_IOS
UITouch *touch = [touches anyObject];
CGPoint touchesMoved = [touch locationInView:self];
int allowedPencilMovement = 10;
switch (touch.type) {
// This prevents touches automatically clicking things after
// moving around the screen
case UITouchTypePencil:
// Apple Pencil touches are much more precise, so this
// allows some pixels of movement before invalidating the gesture.
if (abs(touchesBegan.x - touchesMoved.x) > allowedPencilMovement ||
abs(touchesBegan.y - touchesMoved.y) > allowedPencilMovement) {
[oneFingerTapGesture setState:UIGestureRecognizerStateCancelled];
[pencilThreeTapGesture setState:UIGestureRecognizerStateCancelled];
}
break;
default:
if (touchesBegan.x != touchesMoved.x ||
touchesBegan.y != touchesMoved.y) {
[oneFingerTapGesture setState:UIGestureRecognizerStateCancelled];
[twoFingerTapGesture setState:UIGestureRecognizerStateCancelled];
}
break;
}
#endif
for (GameController *c : _controllers) {
if ([c isKindOfClass:TouchController.class]) {
[(TouchController *)c touchesMoved:touches withEvent:event];
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (GameController *c : _controllers) {
if ([c isKindOfClass:TouchController.class]) {
[(TouchController *)c touchesEnded:touches withEvent:event];
}
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
for (GameController *c : _controllers) {
if ([c isKindOfClass:TouchController.class]) {
[(TouchController *)c touchesCancelled:touches withEvent:event];
}
}
}
#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 *)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 *)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 *)presses withEvent:(UIPressesEvent *)event {
#if TARGET_OS_SIMULATOR
[super pressesChanged:presses withEvent:event];
#endif
}
-(void)pressesCancelled:(NSSet *)presses withEvent:(UIPressesEvent *)event {
#if TARGET_OS_SIMULATOR
[super pressesCancelled:presses withEvent:event];
#endif
}
#endif
#if TARGET_OS_IOS
- (void)longPressKeyboard:(UILongPressGestureRecognizer *)recognizer {
if (![self isKeyboardShown]) {
if (recognizer.state == UIGestureRecognizerStateBegan) {
[self showKeyboard];
}
}
}
- (void)keyboardPinch:(UIPinchGestureRecognizer *)recognizer {
if ([recognizer scale] < 0.8)
[self showKeyboard];
else if ([recognizer scale] > 1.25)
[self hideKeyboard];
}
#endif
- (void)oneFingerTap:(UITapGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded) {
[self addEvent:InternalEvent(kInputTap, kUIViewTapSingle, 1)];
}
}
- (void)twoFingerTap:(UITapGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded) {
[self addEvent:InternalEvent(kInputTap, kUIViewTapSingle, 2)];
}
}
- (void)pencilThreeTap:(UITapGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded) {
// Click right mouse
[self addEvent:InternalEvent(kInputTap, kUIViewTapSingle, 2)];
}
}
- (void)oneFingerLongPress:(UILongPressGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
[self addEvent:InternalEvent(kInputLongPress, UIViewLongPressStarted, 1)];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
[self addEvent:InternalEvent(kInputLongPress, UIViewLongPressEnded, 1)];
}
}
- (void)twoFingerLongPress:(UILongPressGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
[self addEvent:InternalEvent(kInputLongPress, UIViewLongPressStarted, 2)];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
[self addEvent:InternalEvent(kInputLongPress, UIViewLongPressEnded, 2)];
}
}
- (void)pencilTwoTapLongTouch:(UITapGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
// Hold right mouse
[self addEvent:InternalEvent(kInputLongPress, UIViewLongPressStarted, 2)];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
// Release right mouse
[self addEvent:InternalEvent(kInputLongPress, UIViewLongPressEnded, 2)];
}
}
- (void)twoFingersSwipeRight:(UISwipeGestureRecognizer *)recognizer {
[self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeRight, 2)];
}
- (void)twoFingersSwipeLeft:(UISwipeGestureRecognizer *)recognizer {
[self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeLeft, 2)];
}
- (void)twoFingersSwipeUp:(UISwipeGestureRecognizer *)recognizer {
[self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeUp, 2)];
}
- (void)twoFingersSwipeDown:(UISwipeGestureRecognizer *)recognizer {
[self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeDown, 2)];
}
- (void)threeFingersSwipeRight:(UISwipeGestureRecognizer *)recognizer {
[self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeRight, 3)];
}
- (void)threeFingersSwipeLeft:(UISwipeGestureRecognizer *)recognizer {
[self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeLeft, 3)];
}
- (void)threeFingersSwipeUp:(UISwipeGestureRecognizer *)recognizer {
[self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeUp, 3)];
}
- (void)threeFingersSwipeDown:(UISwipeGestureRecognizer *)recognizer {
[self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeDown, 3)];
}
- (void)twoFingersDoubleTap:(UITapGestureRecognizer *)recognizer {
[self addEvent:InternalEvent(kInputTap, kUIViewTapDouble, 2)];
}
- (void)handleKeyPress:(unichar)c withModifierFlags:(int)f {
if (c == '`') {
[self addEvent:InternalEvent(kInputKeyPressed, '\033', f)];
} else {
[self addEvent:InternalEvent(kInputKeyPressed, c, f)];
}
}
- (void)handleMainMenuKey {
if ([self isInGame]) {
[self addEvent:InternalEvent(kInputMainMenu, 0, 0)];
}
#if TARGET_OS_TV
else {
// According to Apple's guidelines the app should return to the
// home screen when pressing the menu button from the root view.
[[UIApplication sharedApplication] performSelector:@selector(suspend)];
}
#endif
}
- (void)applicationSuspend {
// Make sure to hide the keyboard when suspended. Else the frame
// sizing might become incorrect because the NotificationCenter
// sends keyboard notifications on resume.
[self hideKeyboard];
[self addEvent:InternalEvent(kInputApplicationSuspended, 0, 0)];
}
- (void)applicationResume {
[self addEvent:InternalEvent(kInputApplicationResumed, 0, 0)];
// The device may have changed orientation. Make sure to update
// the screen size to the graphic manager.
[self addEvent:InternalEvent(kInputScreenChanged, 0, 0)];
}
- (void)saveApplicationState {
[self addEvent:InternalEvent(kInputApplicationSaveState, 0, 0)];
}
- (void)clearApplicationState {
[self addEvent:InternalEvent(kInputApplicationClearState, 0, 0)];
}
- (void)restoreApplicationState {
[self addEvent:InternalEvent(kInputApplicationRestoreState, 0, 0)];
}
- (void) beginBackgroundSaveStateTask {
if (_backgroundSaveStateTask == UIBackgroundTaskInvalid) {
_backgroundSaveStateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundSaveStateTask];
}];
}
}
- (void) endBackgroundSaveStateTask {
if (_backgroundSaveStateTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask: _backgroundSaveStateTask];
_backgroundSaveStateTask = UIBackgroundTaskInvalid;
}
}
@end