Browse Source

Merge pull request #42582 from naithar/feature/3.2-arc-refactor

[3.2] [iOS] Port 4.0 changes
Rémi Verschelde 4 years ago
parent
commit
4040cd350d

+ 8 - 2
modules/arkit/arkit_interface.h

@@ -44,6 +44,12 @@
 // forward declaration for some needed objects
 // forward declaration for some needed objects
 class ARKitShader;
 class ARKitShader;
 
 
+#ifdef __OBJC__
+typedef NSObject GodotARAnchor;
+#else
+typedef void GodotARAnchor;
+#endif
+
 class ARKitInterface : public ARVRInterface {
 class ARKitInterface : public ARVRInterface {
 	GDCLASS(ARKitInterface, ARVRInterface);
 	GDCLASS(ARKitInterface, ARVRInterface);
 
 
@@ -115,8 +121,8 @@ public:
 	virtual void process();
 	virtual void process();
 
 
 	// called by delegate (void * because C++ and Obj-C don't always mix, should really change all platform/iphone/*.cpp files to .mm)
 	// called by delegate (void * because C++ and Obj-C don't always mix, should really change all platform/iphone/*.cpp files to .mm)
-	void _add_or_update_anchor(void *p_anchor);
-	void _remove_anchor(void *p_anchor);
+	void _add_or_update_anchor(GodotARAnchor *p_anchor);
+	void _remove_anchor(GodotARAnchor *p_anchor);
 
 
 	ARKitInterface();
 	ARKitInterface();
 	~ARKitInterface();
 	~ARKitInterface();

+ 3 - 7
modules/arkit/arkit_interface.mm

@@ -305,10 +305,8 @@ void ARKitInterface::uninitialize() {
 		remove_all_anchors();
 		remove_all_anchors();
 
 
 		if (@available(iOS 11.0, *)) {
 		if (@available(iOS 11.0, *)) {
-			[ar_session release];
-			ar_session = NULL;
+			ar_session = nil;
 		}
 		}
-		[ar_delegate release];
 
 
 		ar_delegate = NULL;
 		ar_delegate = NULL;
 		initialized = false;
 		initialized = false;
@@ -469,10 +467,8 @@ void ARKitInterface::process() {
 
 
 				if (@available(iOS 13, *)) {
 				if (@available(iOS 13, *)) {
 					orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
 					orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
-#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
 				} else {
 				} else {
 					orientation = [[UIApplication sharedApplication] statusBarOrientation];
 					orientation = [[UIApplication sharedApplication] statusBarOrientation];
-#endif
 				}
 				}
 
 
 				// Grab our camera image for our backbuffer
 				// Grab our camera image for our backbuffer
@@ -686,7 +682,7 @@ void ARKitInterface::process() {
 	}
 	}
 }
 }
 
 
-void ARKitInterface::_add_or_update_anchor(void *p_anchor) {
+void ARKitInterface::_add_or_update_anchor(GodotARAnchor *p_anchor) {
 	_THREAD_SAFE_METHOD_
 	_THREAD_SAFE_METHOD_
 
 
 	if (@available(iOS 11.0, *)) {
 	if (@available(iOS 11.0, *)) {
@@ -748,7 +744,7 @@ void ARKitInterface::_add_or_update_anchor(void *p_anchor) {
 	}
 	}
 }
 }
 
 
-void ARKitInterface::_remove_anchor(void *p_anchor) {
+void ARKitInterface::_remove_anchor(GodotARAnchor *p_anchor) {
 	_THREAD_SAFE_METHOD_
 	_THREAD_SAFE_METHOD_
 
 
 	if (@available(iOS 11.0, *)) {
 	if (@available(iOS 11.0, *)) {

+ 17 - 22
modules/camera/camera_ios.mm

@@ -124,18 +124,12 @@
 	if (output) {
 	if (output) {
 		[self removeOutput:output];
 		[self removeOutput:output];
 		[output setSampleBufferDelegate:nil queue:NULL];
 		[output setSampleBufferDelegate:nil queue:NULL];
-		[output release];
 		output = nil;
 		output = nil;
 	}
 	}
 
 
 	[self commitConfiguration];
 	[self commitConfiguration];
 }
 }
 
 
-- (void)dealloc {
-	// bye bye
-	[super dealloc];
-}
-
 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
 	// This gets called every time our camera has a new image for us to process.
 	// This gets called every time our camera has a new image for us to process.
 	// May need to investigate in a way to throttle this if we get more images then we're rendering frames..
 	// May need to investigate in a way to throttle this if we get more images then we're rendering frames..
@@ -158,7 +152,14 @@
 	} else if (dataCbCr == NULL) {
 	} else if (dataCbCr == NULL) {
 		print_line("Couldn't access CbCr pixel buffer data");
 		print_line("Couldn't access CbCr pixel buffer data");
 	} else {
 	} else {
-		UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
+		UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown;
+
+		if (@available(iOS 13, *)) {
+			orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+		} else {
+			orientation = [[UIApplication sharedApplication] statusBarOrientation];
+		}
+
 		Ref<Image> img[2];
 		Ref<Image> img[2];
 
 
 		{
 		{
@@ -263,7 +264,6 @@ CameraFeedIOS::CameraFeedIOS() {
 
 
 void CameraFeedIOS::set_device(AVCaptureDevice *p_device) {
 void CameraFeedIOS::set_device(AVCaptureDevice *p_device) {
 	device = p_device;
 	device = p_device;
-	[device retain];
 
 
 	// get some info
 	// get some info
 	NSString *device_name = p_device.localizedName;
 	NSString *device_name = p_device.localizedName;
@@ -277,15 +277,13 @@ void CameraFeedIOS::set_device(AVCaptureDevice *p_device) {
 };
 };
 
 
 CameraFeedIOS::~CameraFeedIOS() {
 CameraFeedIOS::~CameraFeedIOS() {
-	if (capture_session != NULL) {
-		[capture_session release];
-		capture_session = NULL;
-	};
+	if (capture_session) {
+		capture_session = nil;
+	}
 
 
-	if (device != NULL) {
-		[device release];
-		device = NULL;
-	};
+	if (device) {
+		device = nil;
+	}
 };
 };
 
 
 bool CameraFeedIOS::activate_feed() {
 bool CameraFeedIOS::activate_feed() {
@@ -303,9 +301,8 @@ void CameraFeedIOS::deactivate_feed() {
 	// end camera capture if we have one
 	// end camera capture if we have one
 	if (capture_session) {
 	if (capture_session) {
 		[capture_session cleanup];
 		[capture_session cleanup];
-		[capture_session release];
-		capture_session = NULL;
-	};
+		capture_session = nil;
+	}
 };
 };
 
 
 //////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////
@@ -338,8 +335,6 @@ void CameraFeedIOS::deactivate_feed() {
 	// remove notifications
 	// remove notifications
 	[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
 	[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
 	[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
 	[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
-
-	[super dealloc];
 }
 }
 
 
 @end
 @end
@@ -442,5 +437,5 @@ CameraIOS::CameraIOS() {
 };
 };
 
 
 CameraIOS::~CameraIOS() {
 CameraIOS::~CameraIOS() {
-	[device_notifications release];
+	device_notifications = nil;
 };
 };

+ 1 - 2
modules/mono/mono_gd/support/ios_support.mm

@@ -131,8 +131,7 @@ GD_PINVOKE_EXPORT void *xamarin_timezone_get_data(const char *p_name, uint32_t *
 	NSTimeZone *tz = nil;
 	NSTimeZone *tz = nil;
 	if (p_name) {
 	if (p_name) {
 		NSString *n = [[NSString alloc] initWithUTF8String:p_name];
 		NSString *n = [[NSString alloc] initWithUTF8String:p_name];
-		tz = [[[NSTimeZone alloc] initWithName:n] autorelease];
-		[n release];
+		tz = [[NSTimeZone alloc] initWithName:n];
 	} else {
 	} else {
 		tz = [NSTimeZone localTimeZone];
 		tz = [NSTimeZone localTimeZone];
 	}
 	}

+ 8 - 3
platform/iphone/SCsub

@@ -3,10 +3,9 @@
 Import("env")
 Import("env")
 
 
 iphone_lib = [
 iphone_lib = [
-    "godot_iphone.cpp",
-    "os_iphone.mm",
     "semaphore_iphone.cpp",
     "semaphore_iphone.cpp",
-    "godot_view.mm",
+    "godot_iphone.mm",
+    "os_iphone.mm",
     "main.m",
     "main.m",
     "app_delegate.mm",
     "app_delegate.mm",
     "view_controller.mm",
     "view_controller.mm",
@@ -14,7 +13,13 @@ iphone_lib = [
     "in_app_store.mm",
     "in_app_store.mm",
     "icloud.mm",
     "icloud.mm",
     "ios.mm",
     "ios.mm",
+    "joypad_iphone.mm",
+    "godot_view.mm",
+    "display_layer.mm",
+    "godot_view_renderer.mm",
     "godot_view_gesture_recognizer.mm",
     "godot_view_gesture_recognizer.mm",
+    "device_metrics.m",
+    "native_video_view.m",
 ]
 ]
 
 
 env_ios = env.Clone()
 env_ios = env.Clone()

+ 3 - 9
platform/iphone/app_delegate.h

@@ -28,19 +28,13 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 /*************************************************************************/
 
 
-#import "godot_view.h"
-#import "view_controller.h"
 #import <UIKit/UIKit.h>
 #import <UIKit/UIKit.h>
 
 
-#import <CoreMotion/CoreMotion.h>
+@class ViewController;
 
 
-@interface AppDelegate : NSObject <UIApplicationDelegate, GodotViewDelegate> {
-	//@property (strong, nonatomic) UIWindow *window;
-	ViewController *view_controller;
-	bool is_focus_out;
-};
+@interface AppDelegate : NSObject <UIApplicationDelegate>
 
 
-@property(strong, class, readonly, nonatomic) ViewController *viewController;
 @property(strong, nonatomic) UIWindow *window;
 @property(strong, nonatomic) UIWindow *window;
+@property(strong, class, readonly, nonatomic) ViewController *viewController;
 
 
 @end
 @end

+ 32 - 557
platform/iphone/app_delegate.mm

@@ -35,52 +35,19 @@
 #import "godot_view.h"
 #import "godot_view.h"
 #include "main/main.h"
 #include "main/main.h"
 #include "os_iphone.h"
 #include "os_iphone.h"
+#import "view_controller.h"
 
 
-#import "GameController/GameController.h"
 #import <AudioToolbox/AudioServices.h>
 #import <AudioToolbox/AudioServices.h>
 
 
-#define kFilteringFactor 0.1
 #define kRenderingFrequency 60
 #define kRenderingFrequency 60
-#define kAccelerometerFrequency 100.0 // Hz
-
-Error _shell_open(String);
-void _set_keep_screen_on(bool p_enabled);
-
-Error _shell_open(String p_uri) {
-	NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
-	NSURL *url = [NSURL URLWithString:urlPath];
-	[urlPath release];
-
-	if (![[UIApplication sharedApplication] canOpenURL:url]) {
-		[url release];
-		return ERR_CANT_OPEN;
-	}
-
-	printf("opening url %ls\n", p_uri.c_str());
-	[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
-
-	return OK;
-};
-
-void _set_keep_screen_on(bool p_enabled) {
-	[[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled];
-};
-
-void _vibrate() {
-	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
-};
-
-@implementation AppDelegate
-
-@synthesize window;
 
 
 extern int gargc;
 extern int gargc;
 extern char **gargv;
 extern char **gargv;
-extern int iphone_main(int, int, int, char **, String);
+
+extern int iphone_main(int, char **, String);
 extern void iphone_finish();
 extern void iphone_finish();
 
 
-CMMotionManager *motionManager;
-bool motionInitialised;
+@implementation AppDelegate
 
 
 static ViewController *mainViewController = nil;
 static ViewController *mainViewController = nil;
 
 
@@ -88,522 +55,34 @@ static ViewController *mainViewController = nil;
 	return mainViewController;
 	return mainViewController;
 }
 }
 
 
-NSMutableDictionary *ios_joysticks = nil;
-NSMutableArray *pending_ios_joysticks = nil;
-
-- (GCControllerPlayerIndex)getFreePlayerIndex {
-	bool have_player_1 = false;
-	bool have_player_2 = false;
-	bool have_player_3 = false;
-	bool have_player_4 = false;
-
-	if (ios_joysticks == nil) {
-		NSArray *keys = [ios_joysticks allKeys];
-		for (NSNumber *key in keys) {
-			GCController *controller = [ios_joysticks objectForKey:key];
-			if (controller.playerIndex == GCControllerPlayerIndex1) {
-				have_player_1 = true;
-			} else if (controller.playerIndex == GCControllerPlayerIndex2) {
-				have_player_2 = true;
-			} else if (controller.playerIndex == GCControllerPlayerIndex3) {
-				have_player_3 = true;
-			} else if (controller.playerIndex == GCControllerPlayerIndex4) {
-				have_player_4 = true;
-			};
-		};
-	};
-
-	if (!have_player_1) {
-		return GCControllerPlayerIndex1;
-	} else if (!have_player_2) {
-		return GCControllerPlayerIndex2;
-	} else if (!have_player_3) {
-		return GCControllerPlayerIndex3;
-	} else if (!have_player_4) {
-		return GCControllerPlayerIndex4;
-	} else {
-		return GCControllerPlayerIndexUnset;
-	};
-};
-
-void _ios_add_joystick(GCController *controller, AppDelegate *delegate) {
-	// get a new id for our controller
-	int joy_id = OSIPhone::get_singleton()->get_unused_joy_id();
-	if (joy_id != -1) {
-		// assign our player index
-		if (controller.playerIndex == GCControllerPlayerIndexUnset) {
-			controller.playerIndex = [delegate getFreePlayerIndex];
-		};
-
-		// tell Godot about our new controller
-		OSIPhone::get_singleton()->joy_connection_changed(
-				joy_id, true, [controller.vendorName UTF8String]);
-
-		// add it to our dictionary, this will retain our controllers
-		[ios_joysticks setObject:controller
-						  forKey:[NSNumber numberWithInt:joy_id]];
-
-		// set our input handler
-		[delegate setControllerInputHandler:controller];
-	} else {
-		printf("Couldn't retrieve new joy id\n");
-	};
-}
-
-static void on_focus_out(ViewController *view_controller, bool *is_focus_out) {
-	if (!*is_focus_out) {
-		*is_focus_out = true;
-		if (OS::get_singleton()->get_main_loop())
-			OS::get_singleton()->get_main_loop()->notification(
-					MainLoop::NOTIFICATION_WM_FOCUS_OUT);
-
-		[view_controller.godotView stopAnimation];
-		if (OS::get_singleton()->native_video_is_playing()) {
-			OSIPhone::get_singleton()->native_video_focus_out();
-		}
-
-		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
-		if (audio)
-			audio->stop();
-	}
-}
-
-static void on_focus_in(ViewController *view_controller, bool *is_focus_out) {
-	if (*is_focus_out) {
-		*is_focus_out = false;
-		if (OS::get_singleton()->get_main_loop())
-			OS::get_singleton()->get_main_loop()->notification(
-					MainLoop::NOTIFICATION_WM_FOCUS_IN);
-
-		[view_controller.godotView startAnimation];
-		if (OSIPhone::get_singleton()->native_video_is_playing()) {
-			OSIPhone::get_singleton()->native_video_unpause();
-		}
-
-		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
-		if (audio)
-			audio->start();
-	}
-}
-
-- (void)controllerWasConnected:(NSNotification *)notification {
-	// create our dictionary if we don't have one yet
-	if (ios_joysticks == nil) {
-		ios_joysticks = [[NSMutableDictionary alloc] init];
-	};
-
-	// get our controller
-	GCController *controller = (GCController *)notification.object;
-	if (controller == nil) {
-		printf("Couldn't retrieve new controller\n");
-	} else if ([[ios_joysticks allKeysForObject:controller] count] != 0) {
-		printf("Controller is already registered\n");
-	} else if (frame_count > 1) {
-		_ios_add_joystick(controller, self);
-	} else {
-		if (pending_ios_joysticks == nil)
-			pending_ios_joysticks = [[NSMutableArray alloc] init];
-		[pending_ios_joysticks addObject:controller];
-	};
-};
-
-- (void)controllerWasDisconnected:(NSNotification *)notification {
-	if (ios_joysticks != nil) {
-		// find our joystick, there should be only one in our dictionary
-		GCController *controller = (GCController *)notification.object;
-		NSArray *keys = [ios_joysticks allKeysForObject:controller];
-		for (NSNumber *key in keys) {
-			// tell Godot this joystick is no longer there
-			int joy_id = [key intValue];
-			OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, "");
-
-			// and remove it from our dictionary
-			[ios_joysticks removeObjectForKey:key];
-		};
-	};
-};
-
-- (int)getJoyIdForController:(GCController *)controller {
-	if (ios_joysticks != nil) {
-		// find our joystick, there should be only one in our dictionary
-		NSArray *keys = [ios_joysticks allKeysForObject:controller];
-		for (NSNumber *key in keys) {
-			int joy_id = [key intValue];
-			return joy_id;
-		};
-	};
-
-	return -1;
-};
-
-- (void)setControllerInputHandler:(GCController *)controller {
-	// Hook in the callback handler for the correct gamepad profile.
-	// This is a bit of a weird design choice on Apples part.
-	// You need to select the most capable gamepad profile for the
-	// gamepad attached.
-	if (controller.extendedGamepad != nil) {
-		// The extended gamepad profile has all the input you could possibly find on
-		// a gamepad but will only be active if your gamepad actually has all of
-		// these...
-		controller.extendedGamepad.valueChangedHandler = ^(
-				GCExtendedGamepad *gamepad, GCControllerElement *element) {
-			int joy_id = [self getJoyIdForController:controller];
-
-			if (element == gamepad.buttonA) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
-						gamepad.buttonA.isPressed);
-			} else if (element == gamepad.buttonB) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1,
-						gamepad.buttonB.isPressed);
-			} else if (element == gamepad.buttonX) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
-						gamepad.buttonX.isPressed);
-			} else if (element == gamepad.buttonY) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3,
-						gamepad.buttonY.isPressed);
-			} else if (element == gamepad.leftShoulder) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_L,
-						gamepad.leftShoulder.isPressed);
-			} else if (element == gamepad.rightShoulder) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_R,
-						gamepad.rightShoulder.isPressed);
-			} else if (element == gamepad.leftTrigger) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_L2,
-						gamepad.leftTrigger.isPressed);
-			} else if (element == gamepad.rightTrigger) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_R2,
-						gamepad.rightTrigger.isPressed);
-			} else if (element == gamepad.dpad) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
-						gamepad.dpad.up.isPressed);
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
-						gamepad.dpad.down.isPressed);
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
-						gamepad.dpad.left.isPressed);
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
-						gamepad.dpad.right.isPressed);
-			};
-
-			InputDefault::JoyAxis jx;
-			jx.min = -1;
-			if (element == gamepad.leftThumbstick) {
-				jx.value = gamepad.leftThumbstick.xAxis.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LX, jx);
-				jx.value = -gamepad.leftThumbstick.yAxis.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LY, jx);
-			} else if (element == gamepad.rightThumbstick) {
-				jx.value = gamepad.rightThumbstick.xAxis.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RX, jx);
-				jx.value = -gamepad.rightThumbstick.yAxis.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RY, jx);
-			} else if (element == gamepad.leftTrigger) {
-				jx.value = gamepad.leftTrigger.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_L2, jx);
-			} else if (element == gamepad.rightTrigger) {
-				jx.value = gamepad.rightTrigger.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_R2, jx);
-			};
-		};
-	} else if (controller.microGamepad != nil) {
-		// micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
-		controller.microGamepad.valueChangedHandler =
-				^(GCMicroGamepad *gamepad, GCControllerElement *element) {
-					int joy_id = [self getJoyIdForController:controller];
-
-					if (element == gamepad.buttonA) {
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
-								gamepad.buttonA.isPressed);
-					} else if (element == gamepad.buttonX) {
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
-								gamepad.buttonX.isPressed);
-					} else if (element == gamepad.dpad) {
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
-								gamepad.dpad.up.isPressed);
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
-								gamepad.dpad.down.isPressed);
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
-								gamepad.dpad.left.isPressed);
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
-								gamepad.dpad.right.isPressed);
-					};
-				};
-	}
-
-	///@TODO need to add support for controller.motion which gives us access to
-	/// the orientation of the device (if supported)
-
-	///@TODO need to add support for controllerPausedHandler which should be a
-	/// toggle
-};
-
-- (void)initGameControllers {
-	// get told when controllers connect, this will be called right away for
-	// already connected controllers
-	[[NSNotificationCenter defaultCenter]
-			addObserver:self
-			   selector:@selector(controllerWasConnected:)
-				   name:GCControllerDidConnectNotification
-				 object:nil];
-
-	// get told when controllers disconnect
-	[[NSNotificationCenter defaultCenter]
-			addObserver:self
-			   selector:@selector(controllerWasDisconnected:)
-				   name:GCControllerDidDisconnectNotification
-				 object:nil];
-};
-
-- (void)deinitGameControllers {
-	[[NSNotificationCenter defaultCenter]
-			removeObserver:self
-					  name:GCControllerDidConnectNotification
-					object:nil];
-	[[NSNotificationCenter defaultCenter]
-			removeObserver:self
-					  name:GCControllerDidDisconnectNotification
-					object:nil];
-
-	if (ios_joysticks != nil) {
-		[ios_joysticks dealloc];
-		ios_joysticks = nil;
-	};
-
-	if (pending_ios_joysticks != nil) {
-		[pending_ios_joysticks dealloc];
-		pending_ios_joysticks = nil;
-	};
-};
-
-OS::VideoMode _get_video_mode() {
-	int backingWidth;
-	int backingHeight;
-	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
-			GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
-	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
-			GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-
-	OS::VideoMode vm;
-	vm.fullscreen = true;
-	vm.width = backingWidth;
-	vm.height = backingHeight;
-	vm.resizable = false;
-	return vm;
-};
-
-static int frame_count = 0;
-- (void)drawView:(GodotView *)view {
-
-	switch (frame_count) {
-		case 0: {
-			OS::get_singleton()->set_video_mode(_get_video_mode());
-
-			if (!OS::get_singleton()) {
-				exit(0);
-			};
-			++frame_count;
-		}; break;
-
-		case 1: {
-
-			Main::setup2();
-			++frame_count;
-
-			if (pending_ios_joysticks != nil) {
-				for (GCController *controller in pending_ios_joysticks) {
-					_ios_add_joystick(controller, self);
-				}
-				[pending_ios_joysticks dealloc];
-				pending_ios_joysticks = nil;
-			}
-
-			// this might be necessary before here
-			NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
-			for (NSString *key in dict) {
-				NSObject *value = [dict objectForKey:key];
-				String ukey = String::utf8([key UTF8String]);
-
-				// we need a NSObject to Variant conversor
-
-				if ([value isKindOfClass:[NSString class]]) {
-					NSString *str = (NSString *)value;
-					String uval = String::utf8([str UTF8String]);
-
-					ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
-
-				} else if ([value isKindOfClass:[NSNumber class]]) {
-
-					NSNumber *n = (NSNumber *)value;
-					double dval = [n doubleValue];
-
-					ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
-				};
-				// do stuff
-			}
-
-		}; break;
-
-		case 2: {
-
-			Main::start();
-			++frame_count;
-
-		}; break; // no fallthrough
-
-		default: {
-			if (OSIPhone::get_singleton()) {
-				// OSIPhone::get_singleton()->update_accelerometer(accel[0], accel[1],
-				// accel[2]);
-				if (motionInitialised) {
-					// Just using polling approach for now, we can set this up so it sends
-					// data to us in intervals, might be better. See Apple reference pages
-					// for more details:
-					// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
-
-					// Apple splits our accelerometer date into a gravity and user movement
-					// component. We add them back together
-					CMAcceleration gravity = motionManager.deviceMotion.gravity;
-					CMAcceleration acceleration =
-							motionManager.deviceMotion.userAcceleration;
-
-					///@TODO We don't seem to be getting data here, is my device broken or
-					/// is this code incorrect?
-					CMMagneticField magnetic =
-							motionManager.deviceMotion.magneticField.field;
-
-					///@TODO we can access rotationRate as a CMRotationRate variable
-					///(processed date) or CMGyroData (raw data), have to see what works
-					/// best
-					CMRotationRate rotation = motionManager.deviceMotion.rotationRate;
-
-					// Adjust for screen orientation.
-					// [[UIDevice currentDevice] orientation] changes even if we've fixed
-					// our orientation which is not a good thing when you're trying to get
-					// your user to move the screen in all directions and want consistent
-					// output
-
-					///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
-					/// is a bit of a hack. Godot obviously knows the orientation so maybe
-					/// we
-					// can use that instead? (note that left and right seem swapped)
-
-					switch ([[UIApplication sharedApplication] statusBarOrientation]) {
-						case UIInterfaceOrientationLandscapeLeft: {
-							OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x,
-									gravity.z);
-							OSIPhone::get_singleton()->update_accelerometer(
-									-(acceleration.y + gravity.y), (acceleration.x + gravity.x),
-									acceleration.z + gravity.z);
-							OSIPhone::get_singleton()->update_magnetometer(
-									-magnetic.y, magnetic.x, magnetic.z);
-							OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x,
-									rotation.z);
-						}; break;
-						case UIInterfaceOrientationLandscapeRight: {
-							OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x,
-									gravity.z);
-							OSIPhone::get_singleton()->update_accelerometer(
-									(acceleration.y + gravity.y), -(acceleration.x + gravity.x),
-									acceleration.z + gravity.z);
-							OSIPhone::get_singleton()->update_magnetometer(
-									magnetic.y, -magnetic.x, magnetic.z);
-							OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x,
-									rotation.z);
-						}; break;
-						case UIInterfaceOrientationPortraitUpsideDown: {
-							OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y,
-									gravity.z);
-							OSIPhone::get_singleton()->update_accelerometer(
-									-(acceleration.x + gravity.x), (acceleration.y + gravity.y),
-									acceleration.z + gravity.z);
-							OSIPhone::get_singleton()->update_magnetometer(
-									-magnetic.x, magnetic.y, magnetic.z);
-							OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y,
-									rotation.z);
-						}; break;
-						default: { // assume portrait
-							OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y,
-									gravity.z);
-							OSIPhone::get_singleton()->update_accelerometer(
-									acceleration.x + gravity.x, acceleration.y + gravity.y,
-									acceleration.z + gravity.z);
-							OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y,
-									magnetic.z);
-							OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y,
-									rotation.z);
-						}; break;
-					};
-				}
-
-				OSIPhone::get_singleton()->iterate();
-			};
-
-		}; break;
-	};
-};
-
-- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
-	if (OS::get_singleton()->get_main_loop()) {
-		OS::get_singleton()->get_main_loop()->notification(
-				MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
-	}
-};
-
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
-	CGRect rect = [[UIScreen mainScreen] bounds];
-
-	is_focus_out = false;
-
-	// disable idle timer
-	// application.idleTimerDisabled = YES;
-
 	// Create a full-screen window
 	// Create a full-screen window
-	window = [[UIWindow alloc] initWithFrame:rect];
-
-	OS::VideoMode vm = _get_video_mode();
+	CGRect windowBounds = [[UIScreen mainScreen] bounds];
+	self.window = [[UIWindow alloc] initWithFrame:windowBounds];
 
 
 	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
 	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
 			NSUserDomainMask, YES);
 			NSUserDomainMask, YES);
 	NSString *documentsDirectory = [paths objectAtIndex:0];
 	NSString *documentsDirectory = [paths objectAtIndex:0];
 
 
-	int err = iphone_main(vm.width, vm.height, gargc, gargv, String::utf8([documentsDirectory UTF8String]));
+	int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]));
 	if (err != 0) {
 	if (err != 0) {
 		// bail, things did not go very well for us, should probably output a message on screen with our error code...
 		// bail, things did not go very well for us, should probably output a message on screen with our error code...
 		exit(0);
 		exit(0);
 		return FALSE;
 		return FALSE;
-	};
+	}
 
 
 	// WARNING: We must *always* create the GodotView after we have constructed the
 	// WARNING: We must *always* create the GodotView after we have constructed the
 	// OS with iphone_main. This allows the GodotView to access project settings so
 	// OS with iphone_main. This allows the GodotView to access project settings so
 	// it can properly initialize the OpenGL context
 	// it can properly initialize the OpenGL context
-	GodotView *glView = [[GodotView alloc] initWithFrame:rect];
-	glView.delegate = self;
 
 
-	view_controller = [[ViewController alloc] init];
-	view_controller.view = glView;
-	window.rootViewController = view_controller;
+	ViewController *viewController = [[ViewController alloc] init];
+	viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
+	viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
 
 
-	_set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO);
-	glView.useCADisplayLink =
-			bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
-	printf("cadisaplylink: %d", glView.useCADisplayLink);
-	glView.animationInterval = 1.0 / kRenderingFrequency;
-	[glView startAnimation];
+	self.window.rootViewController = viewController;
 
 
 	// Show the window
 	// Show the window
-	[window makeKeyAndVisible];
-
-	// Configure and start accelerometer
-	if (!motionInitialised) {
-		motionManager = [[CMMotionManager alloc] init];
-		if (motionManager.deviceMotionAvailable) {
-			motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
-			[motionManager startDeviceMotionUpdatesUsingReferenceFrame:
-								   CMAttitudeReferenceFrameXMagneticNorthZVertical];
-			motionInitialised = YES;
-		};
-	};
-
-	[self initGameControllers];
+	[self.window makeKeyAndVisible];
 
 
 	[[NSNotificationCenter defaultCenter]
 	[[NSNotificationCenter defaultCenter]
 			addObserver:self
 			addObserver:self
@@ -611,42 +90,39 @@ static int frame_count = 0;
 				   name:AVAudioSessionInterruptionNotification
 				   name:AVAudioSessionInterruptionNotification
 				 object:[AVAudioSession sharedInstance]];
 				 object:[AVAudioSession sharedInstance]];
 
 
-	// OSIPhone::screen_width = rect.size.width - rect.origin.x;
-	// OSIPhone::screen_height = rect.size.height - rect.origin.y;
-
-	mainViewController = view_controller;
+	mainViewController = viewController;
 
 
 	// prevent to stop music in another background app
 	// prevent to stop music in another background app
 	[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
 	[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
 
 
+	bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
+	OSIPhone::get_singleton()->set_keep_screen_on(keep_screen_on);
+
 	return TRUE;
 	return TRUE;
-};
+}
 
 
 - (void)onAudioInterruption:(NSNotification *)notification {
 - (void)onAudioInterruption:(NSNotification *)notification {
 	if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
 	if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
 		if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
 		if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
 			NSLog(@"Audio interruption began");
 			NSLog(@"Audio interruption began");
-			on_focus_out(view_controller, &is_focus_out);
+			OSIPhone::get_singleton()->on_focus_out();
 		} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
 		} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
 			NSLog(@"Audio interruption ended");
 			NSLog(@"Audio interruption ended");
-			on_focus_in(view_controller, &is_focus_out);
+			OSIPhone::get_singleton()->on_focus_in();
 		}
 		}
 	}
 	}
-};
-
-- (void)applicationWillTerminate:(UIApplication *)application {
-	[self deinitGameControllers];
+}
 
 
-	if (motionInitialised) {
-		///@TODO is this the right place to clean this up?
-		[motionManager stopDeviceMotionUpdates];
-		[motionManager release];
-		motionManager = nil;
-		motionInitialised = NO;
-	};
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
+	if (OS::get_singleton()->get_main_loop()) {
+		OS::get_singleton()->get_main_loop()->notification(
+				MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
+	}
+}
 
 
+- (void)applicationWillTerminate:(UIApplication *)application {
 	iphone_finish();
 	iphone_finish();
-};
+}
 
 
 // When application goes to background (e.g. user switches to another app or presses Home),
 // When application goes to background (e.g. user switches to another app or presses Home),
 // then applicationWillResignActive -> applicationDidEnterBackground are called.
 // then applicationWillResignActive -> applicationDidEnterBackground are called.
@@ -659,16 +135,15 @@ static int frame_count = 0;
 // notification panel by swiping from the upper part of the screen.
 // notification panel by swiping from the upper part of the screen.
 
 
 - (void)applicationWillResignActive:(UIApplication *)application {
 - (void)applicationWillResignActive:(UIApplication *)application {
-	on_focus_out(view_controller, &is_focus_out);
+	OSIPhone::get_singleton()->on_focus_out();
 }
 }
 
 
 - (void)applicationDidBecomeActive:(UIApplication *)application {
 - (void)applicationDidBecomeActive:(UIApplication *)application {
-	on_focus_in(view_controller, &is_focus_out);
+	OSIPhone::get_singleton()->on_focus_in();
 }
 }
 
 
 - (void)dealloc {
 - (void)dealloc {
-	[window release];
-	[super dealloc];
+	self.window = nil;
 }
 }
 
 
 @end
 @end

+ 6 - 3
platform/iphone/detect.py

@@ -115,18 +115,18 @@ def configure(env):
             CCFLAGS=(
             CCFLAGS=(
                 "-arch "
                 "-arch "
                 + arch_flag
                 + arch_flag
-                + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0"
+                + " -fobjc-arc -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0"
             ).split()
             ).split()
         )
         )
     elif env["arch"] == "arm":
     elif env["arch"] == "arm":
         detect_darwin_sdk_path("iphone", env)
         detect_darwin_sdk_path("iphone", env)
         env.Append(
         env.Append(
-            CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split()
+            CCFLAGS='-fobjc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split()
         )
         )
     elif env["arch"] == "arm64":
     elif env["arch"] == "arm64":
         detect_darwin_sdk_path("iphone", env)
         detect_darwin_sdk_path("iphone", env)
         env.Append(
         env.Append(
-            CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK".split()
+            CCFLAGS="-fobjc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK".split()
         )
         )
         env.Append(CPPDEFINES=["NEED_LONG_INT"])
         env.Append(CPPDEFINES=["NEED_LONG_INT"])
         env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"])
         env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"])
@@ -138,6 +138,9 @@ def configure(env):
         else:
         else:
             env.Append(CCFLAGS=["-fno-exceptions"])
             env.Append(CCFLAGS=["-fno-exceptions"])
 
 
+    # Temp fix for ABS/MAX/MIN macros in iPhone SDK blocking compilation
+    env.Append(CCFLAGS=["-Wno-ambiguous-macro"])
+
     ## Link flags
     ## Link flags
 
 
     if env["arch"] == "x86" or env["arch"] == "x86_64":
     if env["arch"] == "x86" or env["arch"] == "x86_64":

+ 37 - 0
platform/iphone/device_metrics.h

@@ -0,0 +1,37 @@
+/*************************************************************************/
+/*  device_metrics.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.                */
+/*************************************************************************/
+
+#import <Foundation/Foundation.h>
+
+@interface GodotDeviceMetrics : NSObject
+
+@property(nonatomic, class, readonly, strong) NSDictionary<NSArray *, NSNumber *> *dpiList;
+
+@end

+ 152 - 0
platform/iphone/device_metrics.m

@@ -0,0 +1,152 @@
+/*************************************************************************/
+/*  device_metrics.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 "device_metrics.h"
+
+@implementation GodotDeviceMetrics
+
++ (NSDictionary *)dpiList {
+	return @{
+		@[
+			@"iPad1,1",
+			@"iPad2,1",
+			@"iPad2,2",
+			@"iPad2,3",
+			@"iPad2,4",
+		] : @132,
+		@[
+			@"iPhone1,1",
+			@"iPhone1,2",
+			@"iPhone2,1",
+			@"iPad2,5",
+			@"iPad2,6",
+			@"iPad2,7",
+			@"iPod1,1",
+			@"iPod2,1",
+			@"iPod3,1",
+		] : @163,
+		@[
+			@"iPad3,1",
+			@"iPad3,2",
+			@"iPad3,3",
+			@"iPad3,4",
+			@"iPad3,5",
+			@"iPad3,6",
+			@"iPad4,1",
+			@"iPad4,2",
+			@"iPad4,3",
+			@"iPad5,3",
+			@"iPad5,4",
+			@"iPad6,3",
+			@"iPad6,4",
+			@"iPad6,7",
+			@"iPad6,8",
+			@"iPad6,11",
+			@"iPad6,12",
+			@"iPad7,1",
+			@"iPad7,2",
+			@"iPad7,3",
+			@"iPad7,4",
+			@"iPad7,5",
+			@"iPad7,6",
+			@"iPad7,11",
+			@"iPad7,12",
+			@"iPad8,1",
+			@"iPad8,2",
+			@"iPad8,3",
+			@"iPad8,4",
+			@"iPad8,5",
+			@"iPad8,6",
+			@"iPad8,7",
+			@"iPad8,8",
+			@"iPad8,9",
+			@"iPad8,10",
+			@"iPad8,11",
+			@"iPad8,12",
+			@"iPad11,3",
+			@"iPad11,4",
+		] : @264,
+		@[
+			@"iPhone3,1",
+			@"iPhone3,2",
+			@"iPhone3,3",
+			@"iPhone4,1",
+			@"iPhone5,1",
+			@"iPhone5,2",
+			@"iPhone5,3",
+			@"iPhone5,4",
+			@"iPhone6,1",
+			@"iPhone6,2",
+			@"iPhone7,2",
+			@"iPhone8,1",
+			@"iPhone8,4",
+			@"iPhone9,1",
+			@"iPhone9,3",
+			@"iPhone10,1",
+			@"iPhone10,4",
+			@"iPhone11,8",
+			@"iPhone12,1",
+			@"iPhone12,8",
+			@"iPad4,4",
+			@"iPad4,5",
+			@"iPad4,6",
+			@"iPad4,7",
+			@"iPad4,8",
+			@"iPad4,9",
+			@"iPad5,1",
+			@"iPad5,2",
+			@"iPad11,1",
+			@"iPad11,2",
+			@"iPod4,1",
+			@"iPod5,1",
+			@"iPod7,1",
+			@"iPod9,1",
+		] : @326,
+		@[
+			@"iPhone7,1",
+			@"iPhone8,2",
+			@"iPhone9,2",
+			@"iPhone9,4",
+			@"iPhone10,2",
+			@"iPhone10,5",
+		] : @401,
+		@[
+			@"iPhone10,3",
+			@"iPhone10,6",
+			@"iPhone11,2",
+			@"iPhone11,4",
+			@"iPhone11,6",
+			@"iPhone12,3",
+			@"iPhone12,5",
+		] : @458,
+	};
+}
+
+@end

+ 45 - 0
platform/iphone/display_layer.h

@@ -0,0 +1,45 @@
+/*************************************************************************/
+/*  display_layer.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.                */
+/*************************************************************************/
+
+#import <OpenGLES/EAGLDrawable.h>
+#import <QuartzCore/QuartzCore.h>
+
+@protocol DisplayLayer <NSObject>
+
+- (void)startRenderDisplayLayer;
+- (void)stopRenderDisplayLayer;
+- (void)initializeDisplayLayer;
+- (void)layoutDisplayLayer;
+
+@end
+
+@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer>
+
+@end

+ 189 - 0
platform/iphone/display_layer.mm

@@ -0,0 +1,189 @@
+/*************************************************************************/
+/*  display_layer.mm                                                     */
+/*************************************************************************/
+/*                       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 "display_layer.h"
+
+#include "core/os/keyboard.h"
+#include "core/project_settings.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <GameController/GameController.h>
+#import <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+int gl_view_base_fb;
+bool gles3_available = true;
+
+@implementation GodotOpenGLLayer {
+	// The pixel dimensions of the backbuffer
+	GLint backingWidth;
+	GLint backingHeight;
+
+	EAGLContext *context;
+	GLuint viewRenderbuffer, viewFramebuffer;
+	GLuint depthRenderbuffer;
+}
+
+- (void)initializeDisplayLayer {
+	// Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
+	self.opaque = YES;
+	self.drawableProperties = [NSDictionary
+			dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
+			kEAGLDrawablePropertyRetainedBacking,
+			kEAGLColorFormatRGBA8,
+			kEAGLDrawablePropertyColorFormat,
+			nil];
+	bool fallback_gl2 = false;
+	// Create a GL ES 3 context based on the gl driver from project settings
+	if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3") {
+		context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
+		NSLog(@"Setting up an OpenGL ES 3.0 context. Based on Project Settings \"rendering/quality/driver/driver_name\"");
+		if (!context && GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) {
+			gles3_available = false;
+			fallback_gl2 = true;
+			NSLog(@"Failed to create OpenGL ES 3.0 context. Falling back to OpenGL ES 2.0");
+		}
+	}
+
+	// Create GL ES 2 context
+	if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2" || fallback_gl2) {
+		context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+		NSLog(@"Setting up an OpenGL ES 2.0 context.");
+		if (!context) {
+			NSLog(@"Failed to create OpenGL ES 2.0 context!");
+			return;
+		}
+	}
+
+	if (![EAGLContext setCurrentContext:context]) {
+		NSLog(@"Failed to set EAGLContext!");
+		return;
+	}
+	if (![self createFramebuffer]) {
+		NSLog(@"Failed to create frame buffer!");
+		return;
+	}
+}
+
+- (void)layoutDisplayLayer {
+	[EAGLContext setCurrentContext:context];
+	[self destroyFramebuffer];
+	[self createFramebuffer];
+}
+
+- (void)startRenderDisplayLayer {
+	[EAGLContext setCurrentContext:context];
+
+	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
+}
+
+- (void)stopRenderDisplayLayer {
+	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
+	[context presentRenderbuffer:GL_RENDERBUFFER_OES];
+
+#ifdef DEBUG_ENABLED
+	GLenum err = glGetError();
+	if (err) {
+		NSLog(@"DrawView: %x error", err);
+	}
+#endif
+}
+
+- (void)dealloc {
+	if ([EAGLContext currentContext] == context) {
+		[EAGLContext setCurrentContext:nil];
+	}
+
+	if (context) {
+		context = nil;
+	}
+}
+
+- (BOOL)createFramebuffer {
+	// Generate IDs for a framebuffer object and a color renderbuffer
+	glGenFramebuffersOES(1, &viewFramebuffer);
+	glGenRenderbuffersOES(1, &viewRenderbuffer);
+
+	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
+	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
+	// This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer)
+	// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
+	[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self];
+	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
+
+	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
+	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
+
+	// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
+	glGenRenderbuffersOES(1, &depthRenderbuffer);
+	glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
+	glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
+	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
+
+	if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
+		NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
+		return NO;
+	}
+
+	if (OS::get_singleton()) {
+		OS::VideoMode vm;
+		vm.fullscreen = true;
+		vm.width = backingWidth;
+		vm.height = backingHeight;
+		vm.resizable = false;
+		OS::get_singleton()->set_video_mode(vm);
+		OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
+	}
+
+	gl_view_base_fb = viewFramebuffer;
+
+	return YES;
+}
+
+// Clean up any buffers we have allocated.
+- (void)destroyFramebuffer {
+	glDeleteFramebuffersOES(1, &viewFramebuffer);
+	viewFramebuffer = 0;
+	glDeleteRenderbuffersOES(1, &viewRenderbuffer);
+	viewRenderbuffer = 0;
+
+	if (depthRenderbuffer) {
+		glDeleteRenderbuffersOES(1, &depthRenderbuffer);
+		depthRenderbuffer = 0;
+	}
+}
+
+@end

+ 12 - 18
platform/iphone/game_center.mm

@@ -32,20 +32,9 @@
 
 
 #include "game_center.h"
 #include "game_center.h"
 
 
-#ifdef __IPHONE_9_0
-
-#import <GameKit/GameKit.h>
-extern "C" {
-
-#else
-
-extern "C" {
-#import <GameKit/GameKit.h>
-
-#endif
-
 #import "app_delegate.h"
 #import "app_delegate.h"
-};
+#import "view_controller.h"
+#import <GameKit/GameKit.h>
 
 
 GameCenter *GameCenter::instance = NULL;
 GameCenter *GameCenter::instance = NULL;
 
 
@@ -82,7 +71,12 @@ Error GameCenter::authenticate() {
 	// after the view is cancelled or the user logs in.  Or if the user's already logged in, it's
 	// after the view is cancelled or the user logs in.  Or if the user's already logged in, it's
 	// called just once to confirm they're authenticated.  This is why no result needs to be specified
 	// called just once to confirm they're authenticated.  This is why no result needs to be specified
 	// in the presentViewController phase. In this case, more calls to this function will follow.
 	// in the presentViewController phase. In this case, more calls to this function will follow.
+	_weakify(root_controller);
+	_weakify(player);
 	player.authenticateHandler = (^(UIViewController *controller, NSError *error) {
 	player.authenticateHandler = (^(UIViewController *controller, NSError *error) {
+		_strongify(root_controller);
+		_strongify(player);
+
 		if (controller) {
 		if (controller) {
 			[root_controller presentViewController:controller animated:YES completion:nil];
 			[root_controller presentViewController:controller animated:YES completion:nil];
 		} else {
 		} else {
@@ -117,8 +111,8 @@ Error GameCenter::post_score(Variant p_score) {
 	float score = params["score"];
 	float score = params["score"];
 	String category = params["category"];
 	String category = params["category"];
 
 
-	NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease];
-	GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease];
+	NSString *cat_str = [[NSString alloc] initWithUTF8String:category.utf8().get_data()];
+	GKScore *reporter = [[GKScore alloc] initWithLeaderboardIdentifier:cat_str];
 	reporter.value = score;
 	reporter.value = score;
 
 
 	ERR_FAIL_COND_V([GKScore respondsToSelector:@selector(reportScores)], ERR_UNAVAILABLE);
 	ERR_FAIL_COND_V([GKScore respondsToSelector:@selector(reportScores)], ERR_UNAVAILABLE);
@@ -148,8 +142,8 @@ Error GameCenter::award_achievement(Variant p_params) {
 	String name = params["name"];
 	String name = params["name"];
 	float progress = params["progress"];
 	float progress = params["progress"];
 
 
-	NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
-	GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease];
+	NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()];
+	GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:name_str];
 	ERR_FAIL_COND_V(!achievement, FAILED);
 	ERR_FAIL_COND_V(!achievement, FAILED);
 
 
 	ERR_FAIL_COND_V([GKAchievement respondsToSelector:@selector(reportAchievements)], ERR_UNAVAILABLE);
 	ERR_FAIL_COND_V([GKAchievement respondsToSelector:@selector(reportAchievements)], ERR_UNAVAILABLE);
@@ -313,7 +307,7 @@ Error GameCenter::show_game_center(Variant p_params) {
 		controller.leaderboardIdentifier = nil;
 		controller.leaderboardIdentifier = nil;
 		if (params.has("leaderboard_name")) {
 		if (params.has("leaderboard_name")) {
 			String name = params["leaderboard_name"];
 			String name = params["leaderboard_name"];
-			NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
+			NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()];
 			controller.leaderboardIdentifier = name_str;
 			controller.leaderboardIdentifier = name_str;
 		}
 		}
 	}
 	}

+ 37 - 13
platform/iphone/godot_iphone.cpp → platform/iphone/godot_iphone.mm

@@ -1,5 +1,5 @@
 /*************************************************************************/
 /*************************************************************************/
-/*  godot_iphone.cpp                                                     */
+/*  godot_iphone.mm                                                      */
 /*************************************************************************/
 /*************************************************************************/
 /*                       This file is part of:                           */
 /*                       This file is part of:                           */
 /*                           GODOT ENGINE                                */
 /*                           GODOT ENGINE                                */
@@ -38,15 +38,39 @@
 
 
 static OSIPhone *os = NULL;
 static OSIPhone *os = NULL;
 
 
-extern "C" {
-int add_path(int p_argc, char **p_args);
-int add_cmdline(int p_argc, char **p_args);
-};
+int add_path(int p_argc, char **p_args) {
+	NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
+	if (!str) {
+		return p_argc;
+	}
+
+	p_args[p_argc++] = (char *)"--path";
+	p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+	p_args[p_argc] = NULL;
+
+	return p_argc;
+}
+
+int add_cmdline(int p_argc, char **p_args) {
+	NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
+	if (!arr) {
+		return p_argc;
+	}
 
 
-int iphone_main(int, int, int, char **, String);
+	for (NSUInteger i = 0; i < [arr count]; i++) {
+		NSString *str = [arr objectAtIndex:i];
+		if (!str) {
+			continue;
+		}
+		p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+	}
+
+	p_args[p_argc] = NULL;
 
 
-int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
+	return p_argc;
+}
 
 
+int iphone_main(int argc, char **argv, String data_dir) {
 	size_t len = strlen(argv[0]);
 	size_t len = strlen(argv[0]);
 
 
 	while (len--) {
 	while (len--) {
@@ -65,12 +89,12 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
 	char cwd[512];
 	char cwd[512];
 	getcwd(cwd, sizeof(cwd));
 	getcwd(cwd, sizeof(cwd));
 	printf("cwd %s\n", cwd);
 	printf("cwd %s\n", cwd);
-	os = new OSIPhone(width, height, data_dir);
+	os = new OSIPhone(data_dir);
 
 
 	char *fargv[64];
 	char *fargv[64];
 	for (int i = 0; i < argc; i++) {
 	for (int i = 0; i < argc; i++) {
 		fargv[i] = argv[i];
 		fargv[i] = argv[i];
-	};
+	}
 	fargv[argc] = NULL;
 	fargv[argc] = NULL;
 	argc = add_path(argc, fargv);
 	argc = add_path(argc, fargv);
 	argc = add_cmdline(argc, fargv);
 	argc = add_cmdline(argc, fargv);
@@ -78,15 +102,15 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
 	printf("os created\n");
 	printf("os created\n");
 	Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
 	Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
 	printf("setup %i\n", err);
 	printf("setup %i\n", err);
-	if (err != OK)
+	if (err != OK) {
 		return 255;
 		return 255;
+	}
 
 
 	return 0;
 	return 0;
-};
+}
 
 
 void iphone_finish() {
 void iphone_finish() {
-
 	printf("iphone_finish\n");
 	printf("iphone_finish\n");
 	Main::cleanup();
 	Main::cleanup();
 	delete os;
 	delete os;
-};
+}

+ 13 - 78
platform/iphone/godot_view.h

@@ -35,93 +35,28 @@
 #import <OpenGLES/ES1/glext.h>
 #import <OpenGLES/ES1/glext.h>
 #import <UIKit/UIKit.h>
 #import <UIKit/UIKit.h>
 
 
-@protocol GodotViewDelegate;
-@class GodotViewGestureRecognizer;
+class String;
 
 
-@interface GodotView : UIView <UIKeyInput> {
-@private
-	// The pixel dimensions of the backbuffer
-	GLint backingWidth;
-	GLint backingHeight;
+@protocol DisplayLayer;
+@protocol GodotViewRendererProtocol;
 
 
-	EAGLContext *context;
+@interface GodotView : UIView <UIKeyInput>
 
 
-	// OpenGL names for the renderbuffer and framebuffers used to render to this view
-	GLuint viewRenderbuffer, viewFramebuffer;
+@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
 
 
-	// OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist)
-	GLuint depthRenderbuffer;
+@property(assign, readonly, nonatomic) BOOL isActive;
 
 
-	BOOL useCADisplayLink;
-	// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
-	CADisplayLink *displayLink;
+@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+@property(assign, readonly, nonatomic) BOOL canRender;
 
 
-	// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
-	// Only used if CADisplayLink is not
-	NSTimer *animationTimer;
+@property(assign, nonatomic) NSTimeInterval renderingInterval;
 
 
-	NSTimeInterval animationInterval;
+- (CALayer<DisplayLayer> *)initializeRendering;
+- (void)stopRendering;
+- (void)startRendering;
 
 
-	// Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer.
-	id<GodotViewDelegate> delegate;
+- (BOOL)becomeFirstResponderWithString:(String)p_existing;
 
 
-	// Flag to denote that the -setupView method of a delegate has been called.
-	// Resets to NO whenever the delegate changes.
-	BOOL delegateSetup;
-	BOOL active;
-	float screen_scale;
-
-	// Delay gesture recognizer
-	GodotViewGestureRecognizer *delayGestureRecognizer;
-}
-
-@property(nonatomic, assign) id<GodotViewDelegate> delegate;
-
-// AVPlayer-related properties
-@property(strong, nonatomic) AVAsset *avAsset;
-@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
-@property(strong, nonatomic) AVPlayer *avPlayer;
-@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
-
-@property(strong, nonatomic) UIWindow *backgroundWindow;
-
-@property(nonatomic) UITextAutocorrectionType autocorrectionType;
-
-- (void)startAnimation;
-- (void)stopAnimation;
-- (void)drawView;
-
-- (BOOL)canBecomeFirstResponder;
-
-- (void)open_keyboard;
-- (void)hide_keyboard;
-- (void)deleteBackward;
-- (BOOL)hasText;
-- (void)insertText:(NSString *)p_text;
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification;
-- (void)keyboardOnScreen:(NSNotification *)notification;
-- (void)keyboardHidden:(NSNotification *)notification;
-
-@property(nonatomic, assign) NSTimeInterval animationInterval;
 @property(nonatomic, assign) BOOL useCADisplayLink;
 @property(nonatomic, assign) BOOL useCADisplayLink;
 
 
 @end
 @end
-
-@protocol GodotViewDelegate <NSObject>
-
-@required
-
-// Draw with OpenGL ES
-- (void)drawView:(GodotView *)view;
-
-@optional
-
-// Called whenever you need to do some initialization before rendering.
-- (void)setupView:(GodotView *)view;
-
-@end

+ 302 - 551
platform/iphone/godot_view.mm

@@ -29,7 +29,6 @@
 /*************************************************************************/
 /*************************************************************************/
 
 
 #import "godot_view.h"
 #import "godot_view.h"
-#import "godot_view_gesture_recognizer.h"
 
 
 #include "core/os/keyboard.h"
 #include "core/os/keyboard.h"
 #include "core/project_settings.h"
 #include "core/project_settings.h"
@@ -39,699 +38,451 @@
 #import <OpenGLES/EAGLDrawable.h>
 #import <OpenGLES/EAGLDrawable.h>
 #import <QuartzCore/QuartzCore.h>
 #import <QuartzCore/QuartzCore.h>
 
 
-/*
-@interface GodotView (private)
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-@end
-*/
-
-bool gles3_available = true;
-int gl_view_base_fb;
-static String keyboard_text;
-static GodotView *_instance = NULL;
-
-static bool video_found_error = false;
-static bool video_playing = false;
-static CMTime video_current_time;
-
-void _show_keyboard(String);
-void _hide_keyboard();
-bool _play_video(String, float, String, String);
-bool _is_video_playing();
-void _pause_video();
-void _focus_out_video();
-void _unpause_video();
-void _stop_video();
-CGFloat _points_to_pixels(CGFloat);
-
-void _show_keyboard(String p_existing) {
-	keyboard_text = p_existing;
-	printf("instance on show is %p\n", _instance);
-	[_instance open_keyboard];
-};
-
-void _hide_keyboard() {
-	printf("instance on hide is %p\n", _instance);
-	[_instance hide_keyboard];
-	keyboard_text = "";
-};
+#import "display_layer.h"
+#import "godot_view_gesture_recognizer.h"
+#import "godot_view_renderer.h"
 
 
-Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height) {
-	UIEdgeInsets insets = UIEdgeInsetsZero;
+#import <CoreMotion/CoreMotion.h>
 
 
-	if (@available(iOS 11.0, *)) {
-		insets = [_instance safeAreaInsets];
-	}
+static const int max_touches = 8;
 
 
-	ERR_FAIL_COND_V(insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0,
-			Rect2(0, 0, p_window_width, p_window_height));
-	UIEdgeInsets window_insets = UIEdgeInsetsMake(_points_to_pixels(insets.top), _points_to_pixels(insets.left), _points_to_pixels(insets.bottom), _points_to_pixels(insets.right));
-	return Rect2(window_insets.left, window_insets.top, p_window_width - window_insets.right - window_insets.left, p_window_height - window_insets.bottom - window_insets.top);
+@interface GodotView () {
+	UITouch *godot_touches[max_touches];
+	String keyboard_text;
 }
 }
 
 
-bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
-	p_path = ProjectSettings::get_singleton()->globalize_path(p_path);
+@property(assign, nonatomic) BOOL isActive;
 
 
-	NSString *file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease];
+// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
+@property(strong, nonatomic) CADisplayLink *displayLink;
 
 
-	_instance.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:file_path]];
+// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
+// Only used if CADisplayLink is not
+@property(strong, nonatomic) NSTimer *animationTimer;
 
 
-	_instance.avPlayerItem = [[AVPlayerItem alloc] initWithAsset:_instance.avAsset];
-	[_instance.avPlayerItem addObserver:_instance forKeyPath:@"status" options:0 context:nil];
+@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer;
 
 
-	_instance.avPlayer = [[AVPlayer alloc] initWithPlayerItem:_instance.avPlayerItem];
-	_instance.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_instance.avPlayer];
+@property(strong, nonatomic) CMMotionManager *motionManager;
 
 
-	[_instance.avPlayer addObserver:_instance forKeyPath:@"status" options:0 context:nil];
-	[[NSNotificationCenter defaultCenter]
-			addObserver:_instance
-			   selector:@selector(playerItemDidReachEnd:)
-				   name:AVPlayerItemDidPlayToEndTimeNotification
-				 object:[_instance.avPlayer currentItem]];
+@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer;
 
 
-	[_instance.avPlayer addObserver:_instance forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
-
-	[_instance.avPlayerLayer setFrame:_instance.bounds];
-	[_instance.layer addSublayer:_instance.avPlayerLayer];
-	[_instance.avPlayer play];
-
-	AVMediaSelectionGroup *audioGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
-
-	NSMutableArray *allAudioParams = [NSMutableArray array];
-	for (id track in audioGroup.options) {
-		NSString *language = [[track locale] localeIdentifier];
-		NSLog(@"subtitle lang: %@", language);
-
-		if ([language isEqualToString:[NSString stringWithUTF8String:p_audio_track.utf8()]]) {
-			AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
-			[audioInputParams setVolume:p_volume atTime:kCMTimeZero];
-			[audioInputParams setTrackID:[track trackID]];
-			[allAudioParams addObject:audioInputParams];
-
-			AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
-			[audioMix setInputParameters:allAudioParams];
+@end
 
 
-			[_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
-			[_instance.avPlayer.currentItem setAudioMix:audioMix];
+@implementation GodotView
 
 
-			break;
-		}
+// Implement this to override the default layer class (which is [CALayer class]).
+// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering.
+- (CALayer<DisplayLayer> *)initializeRendering {
+	if (self.renderingLayer) {
+		return self.renderingLayer;
 	}
 	}
 
 
-	AVMediaSelectionGroup *subtitlesGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
-	NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
+	CALayer<DisplayLayer> *layer = [GodotOpenGLLayer layer];
 
 
-	for (id track in useableTracks) {
-		NSString *language = [[track locale] localeIdentifier];
-		NSLog(@"subtitle lang: %@", language);
+	layer.frame = self.bounds;
+	layer.contentsScale = self.contentScaleFactor;
 
 
-		if ([language isEqualToString:[NSString stringWithUTF8String:p_subtitle_track.utf8()]]) {
-			[_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
-			break;
-		}
-	}
+	[self.layer addSublayer:layer];
+	self.renderingLayer = layer;
 
 
-	video_playing = true;
+	[layer initializeDisplayLayer];
 
 
-	return true;
+	return self.renderingLayer;
 }
 }
 
 
-bool _is_video_playing() {
-	if (_instance.avPlayer.error) {
-		printf("Error during playback\n");
+- (instancetype)initWithCoder:(NSCoder *)coder {
+	self = [super initWithCoder:coder];
+
+	if (self) {
+		[self godot_commonInit];
 	}
 	}
-	return (_instance.avPlayer.rate > 0 && !_instance.avPlayer.error);
-}
 
 
-void _pause_video() {
-	video_current_time = _instance.avPlayer.currentTime;
-	[_instance.avPlayer pause];
-	video_playing = false;
+	return self;
 }
 }
 
 
-void _focus_out_video() {
-	printf("focus out pausing video\n");
-	[_instance.avPlayer pause];
-};
-
-void _unpause_video() {
-
-	[_instance.avPlayer play];
-	video_playing = true;
-};
-
-void _stop_video() {
-	[_instance.avPlayer pause];
-	[_instance.avPlayerLayer removeFromSuperlayer];
-	_instance.avPlayer = nil;
-	video_playing = false;
-}
+- (instancetype)initWithFrame:(CGRect)frame {
+	self = [super initWithFrame:frame];
 
 
-CGFloat _points_to_pixels(CGFloat points) {
-	float pixelPerInch;
-	if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
-		pixelPerInch = 132;
-	} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
-		pixelPerInch = 163;
-	} else {
-		pixelPerInch = 160;
+	if (self) {
+		[self godot_commonInit];
 	}
 	}
-	CGFloat pointsPerInch = 72.0;
-	return (points / pointsPerInch * pixelPerInch);
-}
-
-@implementation GodotView
-
-@synthesize animationInterval;
-
-static const int max_touches = 8;
-static UITouch *touches[max_touches];
-
-static void init_touches() {
-
-	for (int i = 0; i < max_touches; i++) {
-		touches[i] = NULL;
-	};
-};
-
-static int get_touch_id(UITouch *p_touch) {
-
-	int first = -1;
-	for (int i = 0; i < max_touches; i++) {
-		if (first == -1 && touches[i] == NULL) {
-			first = i;
-			continue;
-		};
-		if (touches[i] == p_touch)
-			return i;
-	};
-
-	if (first != -1) {
-		touches[first] = p_touch;
-		return first;
-	};
-
-	return -1;
-};
-
-static int remove_touch(UITouch *p_touch) {
-
-	int remaining = 0;
-	for (int i = 0; i < max_touches; i++) {
-
-		if (touches[i] == NULL)
-			continue;
-		if (touches[i] == p_touch)
-			touches[i] = NULL;
-		else
-			++remaining;
-	};
-	return remaining;
-};
 
 
-static void clear_touches() {
+	return self;
+}
 
 
-	for (int i = 0; i < max_touches; i++) {
+// Stop animating and release resources when they are no longer needed.
+- (void)dealloc {
+	[self stopRendering];
 
 
-		touches[i] = NULL;
-	};
-};
+	self.renderer = nil;
 
 
-// Implement this to override the default layer class (which is [CALayer class]).
-// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering.
-+ (Class)layerClass {
-	return [CAEAGLLayer class];
-}
-
-//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
-- (id)initWithCoder:(NSCoder *)coder {
-	active = FALSE;
-	if ((self = [super initWithCoder:coder])) {
-		self = [self initGLES];
-		[self initGestureRecognizer];
+	if (self.renderingLayer) {
+		[self.renderingLayer removeFromSuperlayer];
+		self.renderingLayer = nil;
 	}
 	}
-	return self;
-}
 
 
-- (id)initGLES {
-	// Get our backing layer
-	CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
-
-	// Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
-	eaglLayer.opaque = YES;
-	eaglLayer.drawableProperties = [NSDictionary
-			dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
-			kEAGLDrawablePropertyRetainedBacking,
-			kEAGLColorFormatRGBA8,
-			kEAGLDrawablePropertyColorFormat,
-			nil];
-	bool fallback_gl2 = false;
-	// Create a GL ES 3 context based on the gl driver from project settings
-	if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3") {
-		context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
-		NSLog(@"Setting up an OpenGL ES 3.0 context. Based on Project Settings \"rendering/quality/driver/driver_name\"");
-		if (!context && GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) {
-			gles3_available = false;
-			fallback_gl2 = true;
-			NSLog(@"Failed to create OpenGL ES 3.0 context. Falling back to OpenGL ES 2.0");
-		}
+	if (self.motionManager) {
+		[self.motionManager stopDeviceMotionUpdates];
+		self.motionManager = nil;
 	}
 	}
 
 
-	// Create GL ES 2 context
-	if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2" || fallback_gl2) {
-		context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
-		NSLog(@"Setting up an OpenGL ES 2.0 context.");
-		if (!context) {
-			NSLog(@"Failed to create OpenGL ES 2.0 context!");
-			return nil;
-		}
+	if (self.displayLink) {
+		[self.displayLink invalidate];
+		self.displayLink = nil;
 	}
 	}
 
 
-	if (![EAGLContext setCurrentContext:context]) {
-		NSLog(@"Failed to set EAGLContext!");
-		return nil;
-	}
-	if (![self createFramebuffer]) {
-		NSLog(@"Failed to create frame buffer!");
-		return nil;
+	if (self.animationTimer) {
+		[self.animationTimer invalidate];
+		self.animationTimer = nil;
 	}
 	}
 
 
-	// Default the animation interval to 1/60th of a second.
-	animationInterval = 1.0 / 60.0;
-	return self;
-}
-
-- (void)initGestureRecognizer {
-	delayGestureRecognizer = [[GodotViewGestureRecognizer alloc] init];
-	[self addGestureRecognizer:delayGestureRecognizer];
-}
-
-- (id<GodotViewDelegate>)delegate {
-	return delegate;
-}
-
-// Update the delegate, and if it needs a -setupView: call, set our internal flag so that it will be called.
-- (void)setDelegate:(id<GodotViewDelegate>)d {
-	delegate = d;
-	delegateSetup = ![delegate respondsToSelector:@selector(setupView:)];
+	if (self.delayGestureRecognizer) {
+		self.delayGestureRecognizer = nil;
+	}
 }
 }
 
 
-@synthesize useCADisplayLink;
+- (void)godot_commonInit {
+	self.contentScaleFactor = [UIScreen mainScreen].nativeScale;
 
 
-// If our view is resized, we'll be asked to layout subviews.
-// This is the perfect opportunity to also update the framebuffer so that it is
-// the same size as our display area.
+	[self initTouches];
 
 
-- (void)layoutSubviews {
-	[EAGLContext setCurrentContext:context];
-	[self destroyFramebuffer];
-	[self createFramebuffer];
-	[self drawView];
-}
-
-- (BOOL)createFramebuffer {
-	// Generate IDs for a framebuffer object and a color renderbuffer
-	UIScreen *mainscr = [UIScreen mainScreen];
-	printf("******** screen size %i, %i\n", (int)mainscr.currentMode.size.width, (int)mainscr.currentMode.size.height);
-	self.contentScaleFactor = mainscr.nativeScale;
-
-	glGenFramebuffersOES(1, &viewFramebuffer);
-	glGenRenderbuffersOES(1, &viewRenderbuffer);
-
-	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
-	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
-	// This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer)
-	// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
-	[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
-	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
-
-	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
-	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-
-	// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
-	glGenRenderbuffersOES(1, &depthRenderbuffer);
-	glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
-	glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
-	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
-
-	if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
-		NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
-		return NO;
-	}
-
-	if (OS::get_singleton()) {
-		OS::VideoMode vm;
-		vm.fullscreen = true;
-		vm.width = backingWidth;
-		vm.height = backingHeight;
-		vm.resizable = false;
-		OS::get_singleton()->set_video_mode(vm);
-		OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
-	};
-	gl_view_base_fb = viewFramebuffer;
+	// Configure and start accelerometer
+	if (!self.motionManager) {
+		self.motionManager = [[CMMotionManager alloc] init];
+		if (self.motionManager.deviceMotionAvailable) {
+			self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
+			[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical];
+		} else {
+			self.motionManager = nil;
+		}
+	}
 
 
-	return YES;
+	// Initialize delay gesture recognizer
+	GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init];
+	self.delayGestureRecognizer = gestureRecognizer;
+	[self addGestureRecognizer:self.delayGestureRecognizer];
 }
 }
 
 
-// Clean up any buffers we have allocated.
-- (void)destroyFramebuffer {
-	glDeleteFramebuffersOES(1, &viewFramebuffer);
-	viewFramebuffer = 0;
-	glDeleteRenderbuffersOES(1, &viewRenderbuffer);
-	viewRenderbuffer = 0;
-
-	if (depthRenderbuffer) {
-		glDeleteRenderbuffersOES(1, &depthRenderbuffer);
-		depthRenderbuffer = 0;
+- (void)startRendering {
+	if (self.isActive) {
+		return;
 	}
 	}
-}
 
 
-- (void)startAnimation {
-	if (active)
-		return;
-	active = TRUE;
+	self.isActive = YES;
+
 	printf("start animation!\n");
 	printf("start animation!\n");
-	if (useCADisplayLink) {
+
+	if (self.useCADisplayLink) {
+		self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
 
 
 		// Approximate frame rate
 		// Approximate frame rate
 		// assumes device refreshes at 60 fps
 		// assumes device refreshes at 60 fps
-		int displayFPS = (NSInteger)(1.0 / animationInterval);
+		int displayFPS = (NSInteger)(1.0 / self.renderingInterval);
 
 
-		displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
-		displayLink.preferredFramesPerSecond = displayFPS;
+		self.displayLink.preferredFramesPerSecond = displayFPS;
 
 
 		// Setup DisplayLink in main thread
 		// Setup DisplayLink in main thread
-		[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+		[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
 	} else {
 	} else {
-		animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
-	}
-
-	if (video_playing) {
-		_unpause_video();
+		self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
 	}
 	}
 }
 }
 
 
-- (void)stopAnimation {
-	if (!active)
+- (void)stopRendering {
+	if (!self.isActive) {
 		return;
 		return;
-	active = FALSE;
-	printf("******** stop animation!\n");
-
-	if (useCADisplayLink) {
-		[displayLink invalidate];
-		displayLink = nil;
-	} else {
-		[animationTimer invalidate];
-		animationTimer = nil;
 	}
 	}
 
 
-	clear_touches();
+	self.isActive = NO;
 
 
-	if (video_playing) {
-		// save position
-	}
-}
+	printf("******** stop animation!\n");
 
 
-- (void)setAnimationInterval:(NSTimeInterval)interval {
-	animationInterval = interval;
-	if ((useCADisplayLink && displayLink) || (!useCADisplayLink && animationTimer)) {
-		[self stopAnimation];
-		[self startAnimation];
+	if (self.useCADisplayLink) {
+		[self.displayLink invalidate];
+		self.displayLink = nil;
+	} else {
+		[self.animationTimer invalidate];
+		self.animationTimer = nil;
 	}
 	}
+
+	[self clearTouches];
 }
 }
 
 
 // Updates the OpenGL view when the timer fires
 // Updates the OpenGL view when the timer fires
 - (void)drawView {
 - (void)drawView {
-
-	if (!active) {
+	if (!self.isActive) {
 		printf("draw view not active!\n");
 		printf("draw view not active!\n");
 		return;
 		return;
-	};
-	if (useCADisplayLink) {
+	}
+
+	if (self.useCADisplayLink) {
 		// Pause the CADisplayLink to avoid recursion
 		// Pause the CADisplayLink to avoid recursion
-		[displayLink setPaused:YES];
+		[self.displayLink setPaused:YES];
 
 
 		// Process all input events
 		// Process all input events
 		while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
 		while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
 			;
 			;
 
 
 		// We are good to go, resume the CADisplayLink
 		// We are good to go, resume the CADisplayLink
-		[displayLink setPaused:NO];
+		[self.displayLink setPaused:NO];
 	}
 	}
 
 
-	// Make sure that you are drawing to the current context
-	[EAGLContext setCurrentContext:context];
+	[self.renderingLayer startRenderDisplayLayer];
 
 
-	// If our drawing delegate needs to have the view setup, then call -setupView: and flag that it won't need to be called again.
-	if (!delegateSetup) {
-		[delegate setupView:self];
-		delegateSetup = YES;
+	if (!self.renderer) {
+		return;
 	}
 	}
 
 
-	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
-
-	[delegate drawView:self];
+	if ([self.renderer setupView:self]) {
+		return;
+	}
 
 
-	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
-	[context presentRenderbuffer:GL_RENDERBUFFER_OES];
+	[self handleMotion];
+	[self.renderer renderOnView:self];
 
 
-#ifdef DEBUG_ENABLED
-	GLenum err = glGetError();
-	if (err)
-		NSLog(@"DrawView: %x error", err);
-#endif
+	[self.renderingLayer stopRenderDisplayLayer];
 }
 }
 
 
-- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
-	NSArray *tlist = [[event allTouches] allObjects];
-	for (unsigned int i = 0; i < [tlist count]; i++) {
-
-		if ([touches containsObject:[tlist objectAtIndex:i]]) {
-
-			UITouch *touch = [tlist objectAtIndex:i];
-			int tid = get_touch_id(touch);
-			ERR_FAIL_COND(tid == -1);
-			CGPoint touchPoint = [touch locationInView:self];
-			OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
-		};
-	};
+- (BOOL)canRender {
+	if (self.useCADisplayLink) {
+		return self.displayLink != nil;
+	} else {
+		return self.animationTimer != nil;
+	}
 }
 }
 
 
-- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
+	_renderingInterval = renderingInterval;
 
 
-	NSArray *tlist = [[event allTouches] allObjects];
-	for (unsigned int i = 0; i < [tlist count]; i++) {
-
-		if ([touches containsObject:[tlist objectAtIndex:i]]) {
-
-			UITouch *touch = [tlist objectAtIndex:i];
-			int tid = get_touch_id(touch);
-			ERR_FAIL_COND(tid == -1);
-			CGPoint touchPoint = [touch locationInView:self];
-			CGPoint prev_point = [touch previousLocationInView:self];
-			OSIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
-		};
-	};
+	if (self.canRender) {
+		[self stopRendering];
+		[self startRendering];
+	}
 }
 }
 
 
-- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
-	NSArray *tlist = [[event allTouches] allObjects];
-	for (unsigned int i = 0; i < [tlist count]; i++) {
+// If our view is resized, we'll be asked to layout subviews.
+// This is the perfect opportunity to also update the framebuffer so that it is
+// the same size as our display area.
 
 
-		if ([touches containsObject:[tlist objectAtIndex:i]]) {
+- (void)layoutSubviews {
+	if (self.renderingLayer) {
+		self.renderingLayer.frame = self.bounds;
+		[self.renderingLayer layoutDisplayLayer];
+	}
 
 
-			UITouch *touch = [tlist objectAtIndex:i];
-			int tid = get_touch_id(touch);
-			ERR_FAIL_COND(tid == -1);
-			remove_touch(touch);
-			CGPoint touchPoint = [touch locationInView:self];
-			OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
-		};
-	};
+	[super layoutSubviews];
 }
 }
 
 
-- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+// MARK: - Input
 
 
-	OSIPhone::get_singleton()->touches_cancelled();
-	clear_touches();
-};
+// MARK: Keyboard
 
 
 - (BOOL)canBecomeFirstResponder {
 - (BOOL)canBecomeFirstResponder {
 	return YES;
 	return YES;
-};
-
-- (void)open_keyboard {
-	//keyboard_text = p_existing;
-	[self becomeFirstResponder];
-};
-
-- (void)hide_keyboard {
-	//keyboard_text = p_existing;
-	[self resignFirstResponder];
-};
-
-- (void)keyboardOnScreen:(NSNotification *)notification {
-	NSDictionary *info = notification.userInfo;
-	NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
-
-	CGRect rawFrame = [value CGRectValue];
-	CGRect keyboardFrame = [self convertRect:rawFrame fromView:nil];
+}
 
 
-	OSIPhone::get_singleton()->set_virtual_keyboard_height(_points_to_pixels(keyboardFrame.size.height));
+- (BOOL)becomeFirstResponderWithString:(String)p_existing {
+	keyboard_text = p_existing;
+	return [self becomeFirstResponder];
 }
 }
 
 
-- (void)keyboardHidden:(NSNotification *)notification {
-	OSIPhone::get_singleton()->set_virtual_keyboard_height(0);
+- (BOOL)resignFirstResponder {
+	keyboard_text = String();
+	return [super resignFirstResponder];
 }
 }
 
 
 - (void)deleteBackward {
 - (void)deleteBackward {
-	if (keyboard_text.length())
+	if (keyboard_text.length()) {
 		keyboard_text.erase(keyboard_text.length() - 1, 1);
 		keyboard_text.erase(keyboard_text.length() - 1, 1);
+	}
 	OSIPhone::get_singleton()->key(KEY_BACKSPACE, true);
 	OSIPhone::get_singleton()->key(KEY_BACKSPACE, true);
-};
+}
 
 
 - (BOOL)hasText {
 - (BOOL)hasText {
-	return keyboard_text.length() ? YES : NO;
-};
+	return keyboard_text.length() > 0;
+}
 
 
 - (void)insertText:(NSString *)p_text {
 - (void)insertText:(NSString *)p_text {
 	String character;
 	String character;
 	character.parse_utf8([p_text UTF8String]);
 	character.parse_utf8([p_text UTF8String]);
 	keyboard_text = keyboard_text + character;
 	keyboard_text = keyboard_text + character;
 	OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
 	OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
-	printf("inserting text with character %lc\n", (CharType)character[0]);
-};
+}
 
 
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
-	printf("*********** route changed!\n");
-	NSDictionary *interuptionDict = notification.userInfo;
+// MARK: Touches
 
 
-	NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
+- (void)initTouches {
+	for (int i = 0; i < max_touches; i++) {
+		godot_touches[i] = NULL;
+	}
+}
 
 
-	switch (routeChangeReason) {
+- (int)getTouchIDForTouch:(UITouch *)p_touch {
+	int first = -1;
+	for (int i = 0; i < max_touches; i++) {
+		if (first == -1 && godot_touches[i] == NULL) {
+			first = i;
+			continue;
+		}
+		if (godot_touches[i] == p_touch) {
+			return i;
+		}
+	}
 
 
-		case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
-			NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
-			NSLog(@"Headphone/Line plugged in");
-		}; break;
+	if (first != -1) {
+		godot_touches[first] = p_touch;
+		return first;
+	}
 
 
-		case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
-			NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
-			NSLog(@"Headphone/Line was pulled. Resuming video play....");
-			if (_is_video_playing()) {
+	return -1;
+}
 
 
-				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
-					[_instance.avPlayer play]; // NOTE: change this line according your current player implementation
-					NSLog(@"resumed play");
-				});
-			};
-		}; break;
+- (int)removeTouch:(UITouch *)p_touch {
+	int remaining = 0;
+	for (int i = 0; i < max_touches; i++) {
+		if (godot_touches[i] == NULL) {
+			continue;
+		}
+		if (godot_touches[i] == p_touch) {
+			godot_touches[i] = NULL;
+		} else {
+			++remaining;
+		}
+	}
+	return remaining;
+}
 
 
-		case AVAudioSessionRouteChangeReasonCategoryChange: {
-			// called at start - also when other audio wants to play
-			NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
-		}; break;
+- (void)clearTouches {
+	for (int i = 0; i < max_touches; i++) {
+		godot_touches[i] = NULL;
 	}
 	}
 }
 }
 
 
-// When created via code however, we get initWithFrame
-- (id)initWithFrame:(CGRect)frame {
-	self = [super initWithFrame:frame];
-	_instance = self;
-	printf("after init super %p\n", self);
-	if (self != nil) {
-		self = [self initGLES];
-		printf("after init gles %p\n", self);
-		[self initGestureRecognizer];
-	}
-	init_touches();
-	self.multipleTouchEnabled = YES;
-	self.autocorrectionType = UITextAutocorrectionTypeNo;
-
-	printf("******** adding observer for sound routing changes\n");
-	[[NSNotificationCenter defaultCenter]
-			addObserver:self
-			   selector:@selector(audioRouteChangeListenerCallback:)
-				   name:AVAudioSessionRouteChangeNotification
-				 object:nil];
-
-	printf("******** adding observer for keyboard show/hide\n");
-	[[NSNotificationCenter defaultCenter]
-			addObserver:self
-			   selector:@selector(keyboardOnScreen:)
-				   name:UIKeyboardDidShowNotification
-				 object:nil];
-	[[NSNotificationCenter defaultCenter]
-			addObserver:self
-			   selector:@selector(keyboardHidden:)
-				   name:UIKeyboardDidHideNotification
-				 object:nil];
-
-	//self.autoresizesSubviews = YES;
-	//[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth];
+- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {
+	NSArray *tlist = [event.allTouches allObjects];
+	for (unsigned int i = 0; i < [tlist count]; i++) {
+		if ([touchesSet containsObject:[tlist objectAtIndex:i]]) {
+			UITouch *touch = [tlist objectAtIndex:i];
+			int tid = [self getTouchIDForTouch:touch];
+			ERR_FAIL_COND(tid == -1);
+			CGPoint touchPoint = [touch locationInView:self];
+			OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
+		}
+	}
+}
 
 
-	return self;
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+	NSArray *tlist = [event.allTouches allObjects];
+	for (unsigned int i = 0; i < [tlist count]; i++) {
+		if ([touches containsObject:[tlist objectAtIndex:i]]) {
+			UITouch *touch = [tlist objectAtIndex:i];
+			int tid = [self getTouchIDForTouch:touch];
+			ERR_FAIL_COND(tid == -1);
+			CGPoint touchPoint = [touch locationInView:self];
+			CGPoint prev_point = [touch previousLocationInView:self];
+			OSIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
+		}
+	}
 }
 }
 
 
-//- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
-//	return YES;
-//}
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+	NSArray *tlist = [event.allTouches allObjects];
+	for (unsigned int i = 0; i < [tlist count]; i++) {
+		if ([touches containsObject:[tlist objectAtIndex:i]]) {
+			UITouch *touch = [tlist objectAtIndex:i];
+			int tid = [self getTouchIDForTouch:touch];
+			ERR_FAIL_COND(tid == -1);
+			[self removeTouch:touch];
+			CGPoint touchPoint = [touch locationInView:self];
+			OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
+		}
+	}
+}
 
 
-//- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
-//	return YES;
-//}
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+	NSArray *tlist = [event.allTouches allObjects];
+	for (unsigned int i = 0; i < [tlist count]; i++) {
+		if ([touches containsObject:[tlist objectAtIndex:i]]) {
+			UITouch *touch = [tlist objectAtIndex:i];
+			int tid = [self getTouchIDForTouch:touch];
+			ERR_FAIL_COND(tid == -1);
+			OSIPhone::get_singleton()->touches_cancelled(tid);
+		}
+	}
+	[self clearTouches];
+}
 
 
-// Stop animating and release resources when they are no longer needed.
-- (void)dealloc {
-	[self stopAnimation];
+// MARK: Motion
 
 
-	if ([EAGLContext currentContext] == context) {
-		[EAGLContext setCurrentContext:nil];
+- (void)handleMotion {
+	if (!self.motionManager) {
+		return;
 	}
 	}
 
 
-	[context release];
-	context = nil;
-
-	[super dealloc];
-}
+	// Just using polling approach for now, we can set this up so it sends
+	// data to us in intervals, might be better. See Apple reference pages
+	// for more details:
+	// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
 
 
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+	// Apple splits our accelerometer date into a gravity and user movement
+	// component. We add them back together
+	CMAcceleration gravity = self.motionManager.deviceMotion.gravity;
+	CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration;
 
 
-	if (object == _instance.avPlayerItem && [keyPath isEqualToString:@"status"]) {
-		if (_instance.avPlayerItem.status == AVPlayerItemStatusFailed || _instance.avPlayer.status == AVPlayerStatusFailed) {
-			_stop_video();
-			video_found_error = true;
-		}
+	///@TODO We don't seem to be getting data here, is my device broken or
+	/// is this code incorrect?
+	CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field;
 
 
-		if (_instance.avPlayer.status == AVPlayerStatusReadyToPlay &&
-				_instance.avPlayerItem.status == AVPlayerItemStatusReadyToPlay &&
-				CMTIME_COMPARE_INLINE(video_current_time, ==, kCMTimeZero)) {
+	///@TODO we can access rotationRate as a CMRotationRate variable
+	///(processed date) or CMGyroData (raw data), have to see what works
+	/// best
+	CMRotationRate rotation = self.motionManager.deviceMotion.rotationRate;
 
 
-			//NSLog(@"time: %@", video_current_time);
+	// Adjust for screen orientation.
+	// [[UIDevice currentDevice] orientation] changes even if we've fixed
+	// our orientation which is not a good thing when you're trying to get
+	// your user to move the screen in all directions and want consistent
+	// output
 
 
-			[_instance.avPlayer seekToTime:video_current_time];
-			video_current_time = kCMTimeZero;
-		}
-	}
+	///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
+	/// is a bit of a hack. Godot obviously knows the orientation so maybe
+	/// we
+	// can use that instead? (note that left and right seem swapped)
 
 
-	if (object == _instance.avPlayer && [keyPath isEqualToString:@"rate"]) {
-		NSLog(@"Player playback rate changed: %.5f", _instance.avPlayer.rate);
-		if (_is_video_playing() && _instance.avPlayer.rate == 0.0 && !_instance.avPlayer.error) {
-			dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
-				[_instance.avPlayer play]; // NOTE: change this line according your current player implementation
-				NSLog(@"resumed play");
-			});
+	UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
 
 
-			NSLog(@" . . . PAUSED (or just started)");
-		}
+	if (@available(iOS 13, *)) {
+		interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+	} else {
+		interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
 	}
 	}
-}
 
 
-- (void)playerItemDidReachEnd:(NSNotification *)notification {
-	_stop_video();
+	switch (interfaceOrientation) {
+		case UIInterfaceOrientationLandscapeLeft: {
+			OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z);
+			OSIPhone::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z);
+			OSIPhone::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z);
+			OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z);
+		} break;
+		case UIInterfaceOrientationLandscapeRight: {
+			OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z);
+			OSIPhone::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z);
+			OSIPhone::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z);
+			OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z);
+		} break;
+		case UIInterfaceOrientationPortraitUpsideDown: {
+			OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z);
+			OSIPhone::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z);
+			OSIPhone::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z);
+			OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z);
+		} break;
+		default: { // assume portrait
+			OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z);
+			OSIPhone::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z);
+			OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z);
+			OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z);
+		} break;
+	}
 }
 }
 
 
 @end
 @end

+ 0 - 5
platform/iphone/godot_view_gesture_recognizer.mm

@@ -75,7 +75,6 @@ const CGFloat kGLGestureMovementDistance = 0.5;
 		[self.view touchesBegan:delayedTouches withEvent:delayedEvent];
 		[self.view touchesBegan:delayedTouches withEvent:delayedEvent];
 	}
 	}
 
 
-	[delayedTouches release];
 	delayedTouches = nil;
 	delayedTouches = nil;
 	delayedEvent = nil;
 	delayedEvent = nil;
 }
 }
@@ -103,16 +102,13 @@ const CGFloat kGLGestureMovementDistance = 0.5;
 			if (distance > kGLGestureMovementDistance) {
 			if (distance > kGLGestureMovementDistance) {
 				[delayTimer fire];
 				[delayTimer fire];
 				[self.view touchesMoved:cleared withEvent:event];
 				[self.view touchesMoved:cleared withEvent:event];
-				[cleared release];
 				return;
 				return;
 			}
 			}
 		}
 		}
-		[cleared release];
 		return;
 		return;
 	}
 	}
 
 
 	[self.view touchesMoved:cleared withEvent:event];
 	[self.view touchesMoved:cleared withEvent:event];
-	[cleared release];
 }
 }
 
 
 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
@@ -120,7 +116,6 @@ const CGFloat kGLGestureMovementDistance = 0.5;
 
 
 	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
 	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
 	[self.view touchesEnded:cleared withEvent:event];
 	[self.view touchesEnded:cleared withEvent:event];
-	[cleared release];
 }
 }
 
 
 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {

+ 44 - 0
platform/iphone/godot_view_renderer.h

@@ -0,0 +1,44 @@
+/*************************************************************************/
+/*  godot_view_renderer.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.                */
+/*************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+@protocol GodotViewRendererProtocol <NSObject>
+
+@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
+
+- (BOOL)setupView:(UIView *)view;
+- (void)renderOnView:(UIView *)view;
+
+@end
+
+@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol>
+
+@end

+ 117 - 0
platform/iphone/godot_view_renderer.mm

@@ -0,0 +1,117 @@
+/*************************************************************************/
+/*  godot_view_renderer.mm                                               */
+/*************************************************************************/
+/*                       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_renderer.h"
+
+#include "core/os/keyboard.h"
+#include "core/project_settings.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <CoreMotion/CoreMotion.h>
+#import <GameController/GameController.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+@interface GodotViewRenderer ()
+
+@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
+@property(assign, nonatomic) BOOL hasStartedMain;
+@property(assign, nonatomic) BOOL hasFinishedSetup;
+
+@end
+
+@implementation GodotViewRenderer
+
+- (BOOL)setupView:(UIView *)view {
+	if (self.hasFinishedSetup) {
+		return NO;
+	}
+
+	if (!OS::get_singleton()) {
+		exit(0);
+	}
+
+	if (!self.hasFinishedProjectDataSetup) {
+		[self setupProjectData];
+		return YES;
+	}
+
+	if (!self.hasStartedMain) {
+		self.hasStartedMain = YES;
+		OSIPhone::get_singleton()->start();
+		return YES;
+	}
+
+	self.hasFinishedSetup = YES;
+
+	return NO;
+}
+
+- (void)setupProjectData {
+	self.hasFinishedProjectDataSetup = YES;
+
+	Main::setup2();
+
+	// this might be necessary before here
+	NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
+	for (NSString *key in dict) {
+		NSObject *value = [dict objectForKey:key];
+		String ukey = String::utf8([key UTF8String]);
+
+		// we need a NSObject to Variant conversor
+
+		if ([value isKindOfClass:[NSString class]]) {
+			NSString *str = (NSString *)value;
+			String uval = String::utf8([str UTF8String]);
+
+			ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
+
+		} else if ([value isKindOfClass:[NSNumber class]]) {
+			NSNumber *n = (NSNumber *)value;
+			double dval = [n doubleValue];
+
+			ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
+		};
+		// do stuff
+	}
+}
+
+- (void)renderOnView:(UIView *)view {
+	if (!OSIPhone::get_singleton()) {
+		return;
+	}
+
+	OSIPhone::get_singleton()->iterate();
+}
+
+@end

+ 7 - 15
platform/iphone/icloud.mm

@@ -32,18 +32,10 @@
 
 
 #include "icloud.h"
 #include "icloud.h"
 
 
-#ifndef __IPHONE_9_0
-extern "C" {
-#endif
-
 #import "app_delegate.h"
 #import "app_delegate.h"
 
 
 #import <Foundation/Foundation.h>
 #import <Foundation/Foundation.h>
 
 
-#ifndef __IPHONE_9_0
-};
-#endif
-
 ICloud *ICloud::instance = NULL;
 ICloud *ICloud::instance = NULL;
 
 
 void ICloud::_bind_methods() {
 void ICloud::_bind_methods() {
@@ -150,7 +142,7 @@ Variant nsobject_to_variant(NSObject *object) {
 
 
 NSObject *variant_to_nsobject(Variant v) {
 NSObject *variant_to_nsobject(Variant v) {
 	if (v.get_type() == Variant::STRING) {
 	if (v.get_type() == Variant::STRING) {
-		return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease];
+		return [[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()];
 	} else if (v.get_type() == Variant::REAL) {
 	} else if (v.get_type() == Variant::REAL) {
 		return [NSNumber numberWithDouble:(double)v];
 		return [NSNumber numberWithDouble:(double)v];
 	} else if (v.get_type() == Variant::INT) {
 	} else if (v.get_type() == Variant::INT) {
@@ -158,11 +150,11 @@ NSObject *variant_to_nsobject(Variant v) {
 	} else if (v.get_type() == Variant::BOOL) {
 	} else if (v.get_type() == Variant::BOOL) {
 		return [NSNumber numberWithBool:BOOL((bool)v)];
 		return [NSNumber numberWithBool:BOOL((bool)v)];
 	} else if (v.get_type() == Variant::DICTIONARY) {
 	} else if (v.get_type() == Variant::DICTIONARY) {
-		NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease];
+		NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
 		Dictionary dic = v;
 		Dictionary dic = v;
 		Array keys = dic.keys();
 		Array keys = dic.keys();
 		for (int i = 0; i < keys.size(); ++i) {
 		for (int i = 0; i < keys.size(); ++i) {
-			NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease];
+			NSString *key = [[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()];
 			NSObject *value = variant_to_nsobject(dic[keys[i]]);
 			NSObject *value = variant_to_nsobject(dic[keys[i]]);
 
 
 			if (key == NULL || value == NULL) {
 			if (key == NULL || value == NULL) {
@@ -173,7 +165,7 @@ NSObject *variant_to_nsobject(Variant v) {
 		}
 		}
 		return result;
 		return result;
 	} else if (v.get_type() == Variant::ARRAY) {
 	} else if (v.get_type() == Variant::ARRAY) {
-		NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
+		NSMutableArray *result = [[NSMutableArray alloc] init];
 		Array arr = v;
 		Array arr = v;
 		for (int i = 0; i < arr.size(); ++i) {
 		for (int i = 0; i < arr.size(); ++i) {
 			NSObject *value = variant_to_nsobject(arr[i]);
 			NSObject *value = variant_to_nsobject(arr[i]);
@@ -196,7 +188,7 @@ NSObject *variant_to_nsobject(Variant v) {
 
 
 Error ICloud::remove_key(Variant p_param) {
 Error ICloud::remove_key(Variant p_param) {
 	String param = p_param;
 	String param = p_param;
-	NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
+	NSString *key = [[NSString alloc] initWithUTF8String:param.utf8().get_data()];
 
 
 	NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
 	NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
 
 
@@ -219,7 +211,7 @@ Variant ICloud::set_key_values(Variant p_params) {
 		String variant_key = keys[i];
 		String variant_key = keys[i];
 		Variant variant_value = params[variant_key];
 		Variant variant_value = params[variant_key];
 
 
-		NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease];
+		NSString *key = [[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()];
 		if (key == NULL) {
 		if (key == NULL) {
 			error_keys.push_back(variant_key);
 			error_keys.push_back(variant_key);
 			continue;
 			continue;
@@ -242,7 +234,7 @@ Variant ICloud::set_key_values(Variant p_params) {
 Variant ICloud::get_key_value(Variant p_param) {
 Variant ICloud::get_key_value(Variant p_param) {
 	String param = p_param;
 	String param = p_param;
 
 
-	NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
+	NSString *key = [[NSString alloc] initWithUTF8String:param.utf8().get_data()];
 	NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
 	NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
 
 
 	if (![[store dictionaryRepresentation] objectForKey:key]) {
 	if (![[store dictionaryRepresentation] objectForKey:key]) {

+ 12 - 32
platform/iphone/in_app_store.mm

@@ -32,10 +32,8 @@
 
 
 #include "in_app_store.h"
 #include "in_app_store.h"
 
 
-extern "C" {
 #import <Foundation/Foundation.h>
 #import <Foundation/Foundation.h>
 #import <StoreKit/StoreKit.h>
 #import <StoreKit/StoreKit.h>
-};
 
 
 bool auto_finish_transactions = true;
 bool auto_finish_transactions = true;
 NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary];
 NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary];
@@ -55,7 +53,6 @@ static NSArray *latestProducts;
 	[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
 	[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
 	[numberFormatter setLocale:self.priceLocale];
 	[numberFormatter setLocale:self.priceLocale];
 	NSString *formattedString = [numberFormatter stringFromNumber:self.price];
 	NSString *formattedString = [numberFormatter stringFromNumber:self.price];
-	[numberFormatter release];
 	return formattedString;
 	return formattedString;
 }
 }
 @end
 @end
@@ -125,8 +122,6 @@ void InAppStore::_bind_methods() {
 	ret["invalid_ids"] = invalid_ids;
 	ret["invalid_ids"] = invalid_ids;
 
 
 	InAppStore::get_singleton()->_post_event(ret);
 	InAppStore::get_singleton()->_post_event(ret);
-
-	[request release];
 };
 };
 
 
 @end
 @end
@@ -139,14 +134,14 @@ Error InAppStore::request_product_info(Variant p_params) {
 	PoolStringArray pids = params["product_ids"];
 	PoolStringArray pids = params["product_ids"];
 	printf("************ request product info! %i\n", pids.size());
 	printf("************ request product info! %i\n", pids.size());
 
 
-	NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease];
+	NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:pids.size()];
 	for (int i = 0; i < pids.size(); i++) {
 	for (int i = 0; i < pids.size(); i++) {
 		printf("******** adding %ls to product list\n", pids[i].c_str());
 		printf("******** adding %ls to product list\n", pids[i].c_str());
-		NSString *pid = [[[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()] autorelease];
+		NSString *pid = [[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()];
 		[array addObject:pid];
 		[array addObject:pid];
 	};
 	};
 
 
-	NSSet *products = [[[NSSet alloc] initWithArray:array] autorelease];
+	NSSet *products = [[NSSet alloc] initWithArray:array];
 	SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:products];
 	SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:products];
 
 
 	ProductsDelegate *delegate = [[ProductsDelegate alloc] init];
 	ProductsDelegate *delegate = [[ProductsDelegate alloc] init];
@@ -189,32 +184,17 @@ Error InAppStore::restore_purchases() {
 				ret["transaction_id"] = transactionId;
 				ret["transaction_id"] = transactionId;
 
 
 				NSData *receipt = nil;
 				NSData *receipt = nil;
-				int sdk_version = 6;
-
-				if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
-					NSURL *receiptFileURL = nil;
-					NSBundle *bundle = [NSBundle mainBundle];
-					if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) {
-						// Get the transaction receipt file path location in the app bundle.
-						receiptFileURL = [bundle appStoreReceiptURL];
-
-						// Read in the contents of the transaction file.
-						receipt = [NSData dataWithContentsOfURL:receiptFileURL];
-						sdk_version = 7;
+				int sdk_version = [[[UIDevice currentDevice] systemVersion] intValue];
 
 
-					} else {
-						// Fall back to deprecated transaction receipt,
-						// which is still available in iOS 7.
+				NSBundle *bundle = [NSBundle mainBundle];
+				// Get the transaction receipt file path location in the app bundle.
+				NSURL *receiptFileURL = [bundle appStoreReceiptURL];
 
 
-						// Use SKPaymentTransaction's transactionReceipt.
-						receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
-					}
-
-				} else {
-					receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
-				}
+				// Read in the contents of the transaction file.
+				receipt = [NSData dataWithContentsOfURL:receiptFileURL];
 
 
 				NSString *receipt_to_send = nil;
 				NSString *receipt_to_send = nil;
+
 				if (receipt != nil) {
 				if (receipt != nil) {
 					receipt_to_send = [receipt base64EncodedStringWithOptions:0];
 					receipt_to_send = [receipt base64EncodedStringWithOptions:0];
 				}
 				}
@@ -273,7 +253,7 @@ Error InAppStore::purchase(Variant p_params) {
 	Dictionary params = p_params;
 	Dictionary params = p_params;
 	ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
 	ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
 
 
-	NSString *pid = [[[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()] autorelease];
+	NSString *pid = [[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()];
 
 
 	SKProduct *product = nil;
 	SKProduct *product = nil;
 
 
@@ -318,7 +298,7 @@ void InAppStore::_post_event(Variant p_event) {
 void InAppStore::_record_purchase(String product_id) {
 void InAppStore::_record_purchase(String product_id) {
 
 
 	String skey = "purchased/" + product_id;
 	String skey = "purchased/" + product_id;
-	NSString *key = [[[NSString alloc] initWithUTF8String:skey.utf8().get_data()] autorelease];
+	NSString *key = [[NSString alloc] initWithUTF8String:skey.utf8().get_data()];
 	[[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
 	[[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
 	[[NSUserDefaults standardUserDefaults] synchronize];
 	[[NSUserDefaults standardUserDefaults] synchronize];
 };
 };

+ 4 - 16
platform/iphone/ios.mm

@@ -31,6 +31,7 @@
 #include "ios.h"
 #include "ios.h"
 
 
 #import "app_delegate.h"
 #import "app_delegate.h"
+#import "view_controller.h"
 
 
 #import <UIKit/UIKit.h>
 #import <UIKit/UIKit.h>
 #include <sys/sysctl.h>
 #include <sys/sysctl.h>
@@ -71,25 +72,12 @@ String iOS::get_model() const {
 }
 }
 
 
 String iOS::get_rate_url(int p_app_id) const {
 String iOS::get_rate_url(int p_app_id) const {
-	String templ = "itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID";
-	String templ_iOS7 = "itms-apps://itunes.apple.com/app/idAPP_ID";
-	String templ_iOS8 = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APP_ID&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software";
+	String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID";
 
 
-	//ios7 before
-	String ret = templ;
-
-	if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 && [[[UIDevice currentDevice] systemVersion] floatValue] < 7.1) {
-		// iOS 7 needs a different templateReviewURL @see https://github.com/arashpayan/appirater/issues/131
-		ret = templ_iOS7;
-	} else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
-		// iOS 8 needs a different templateReviewURL also @see https://github.com/arashpayan/appirater/issues/182
-		ret = templ_iOS8;
-	}
-
-	// ios7 for everything?
-	ret = templ_iOS7.replace("APP_ID", String::num(p_app_id));
+	String ret = app_url_path.replace("APP_ID", String::num(p_app_id));
 
 
 	printf("returning rate url %ls\n", ret.c_str());
 	printf("returning rate url %ls\n", ret.c_str());
+
 	return ret;
 	return ret;
 };
 };
 
 

+ 50 - 0
platform/iphone/joypad_iphone.h

@@ -0,0 +1,50 @@
+/*************************************************************************/
+/*  joypad_iphone.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.                */
+/*************************************************************************/
+
+#import <GameController/GameController.h>
+
+@interface JoypadIPhoneObserver : NSObject
+
+- (void)startObserving;
+- (void)startProcessing;
+- (void)finishObserving;
+
+@end
+
+class JoypadIPhone {
+private:
+	JoypadIPhoneObserver *observer;
+
+public:
+	JoypadIPhone();
+	~JoypadIPhone();
+
+	void start_processing();
+};

+ 352 - 0
platform/iphone/joypad_iphone.mm

@@ -0,0 +1,352 @@
+/*************************************************************************/
+/*  joypad_iphone.mm                                                     */
+/*************************************************************************/
+/*                       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 "joypad_iphone.h"
+
+#include "core/project_settings.h"
+#include "drivers/coreaudio/audio_driver_coreaudio.h"
+#import "godot_view.h"
+#include "main/main.h"
+#include "os_iphone.h"
+
+JoypadIPhone::JoypadIPhone() {
+	observer = [[JoypadIPhoneObserver alloc] init];
+	[observer startObserving];
+}
+
+JoypadIPhone::~JoypadIPhone() {
+	if (observer) {
+		[observer finishObserving];
+		observer = nil;
+	}
+}
+
+void JoypadIPhone::start_processing() {
+	if (observer) {
+		[observer startProcessing];
+	}
+}
+
+@interface JoypadIPhoneObserver ()
+
+@property(assign, nonatomic) BOOL isObserving;
+@property(assign, nonatomic) BOOL isProcessing;
+@property(strong, nonatomic) NSMutableDictionary *connectedJoypads;
+@property(strong, nonatomic) NSMutableArray *joypadsQueue;
+
+@end
+
+@implementation JoypadIPhoneObserver
+
+- (instancetype)init {
+	self = [super init];
+
+	if (self) {
+		[self godot_commonInit];
+	}
+
+	return self;
+}
+
+- (void)godot_commonInit {
+	self.isObserving = NO;
+	self.isProcessing = NO;
+}
+
+- (void)startProcessing {
+	self.isProcessing = YES;
+
+	for (GCController *controller in self.joypadsQueue) {
+		[self addiOSJoypad:controller];
+	}
+
+	[self.joypadsQueue removeAllObjects];
+}
+
+- (void)startObserving {
+	if (self.isObserving) {
+		return;
+	}
+
+	self.isObserving = YES;
+
+	self.connectedJoypads = [NSMutableDictionary dictionary];
+	self.joypadsQueue = [NSMutableArray array];
+
+	// get told when controllers connect, this will be called right away for
+	// already connected controllers
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(controllerWasConnected:)
+				   name:GCControllerDidConnectNotification
+				 object:nil];
+
+	// get told when controllers disconnect
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(controllerWasDisconnected:)
+				   name:GCControllerDidDisconnectNotification
+				 object:nil];
+}
+
+- (void)finishObserving {
+	if (self.isObserving) {
+		[[NSNotificationCenter defaultCenter] removeObserver:self];
+	}
+
+	self.isObserving = NO;
+	self.isProcessing = NO;
+
+	self.connectedJoypads = nil;
+	self.joypadsQueue = nil;
+}
+
+- (void)dealloc {
+	[self finishObserving];
+}
+
+- (int)getJoyIdForController:(GCController *)controller {
+	NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+
+	for (NSNumber *key in keys) {
+		int joy_id = [key intValue];
+		return joy_id;
+	};
+
+	return -1;
+};
+
+- (void)addiOSJoypad:(GCController *)controller {
+	//     get a new id for our controller
+	int joy_id = OSIPhone::get_singleton()->get_unused_joy_id();
+
+	if (joy_id == -1) {
+		printf("Couldn't retrieve new joy id\n");
+		return;
+	}
+
+	// assign our player index
+	if (controller.playerIndex == GCControllerPlayerIndexUnset) {
+		controller.playerIndex = [self getFreePlayerIndex];
+	};
+
+	// tell Godot about our new controller
+	OSIPhone::get_singleton()->joy_connection_changed(joy_id, true, [controller.vendorName UTF8String]);
+
+	// add it to our dictionary, this will retain our controllers
+	[self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]];
+
+	// set our input handler
+	[self setControllerInputHandler:controller];
+}
+
+- (void)controllerWasConnected:(NSNotification *)notification {
+	// get our controller
+	GCController *controller = (GCController *)notification.object;
+
+	if (!controller) {
+		printf("Couldn't retrieve new controller\n");
+		return;
+	}
+
+	if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) {
+		printf("Controller is already registered\n");
+	} else if (!self.isProcessing) {
+		[self.joypadsQueue addObject:controller];
+	} else {
+		[self addiOSJoypad:controller];
+	}
+}
+
+- (void)controllerWasDisconnected:(NSNotification *)notification {
+	// find our joystick, there should be only one in our dictionary
+	GCController *controller = (GCController *)notification.object;
+
+	if (!controller) {
+		return;
+	}
+
+	NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+	for (NSNumber *key in keys) {
+		// tell Godot this joystick is no longer there
+		int joy_id = [key intValue];
+		OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, "");
+
+		// and remove it from our dictionary
+		[self.connectedJoypads removeObjectForKey:key];
+	};
+};
+
+- (GCControllerPlayerIndex)getFreePlayerIndex {
+	bool have_player_1 = false;
+	bool have_player_2 = false;
+	bool have_player_3 = false;
+	bool have_player_4 = false;
+
+	if (self.connectedJoypads == nil) {
+		NSArray *keys = [self.connectedJoypads allKeys];
+		for (NSNumber *key in keys) {
+			GCController *controller = [self.connectedJoypads objectForKey:key];
+			if (controller.playerIndex == GCControllerPlayerIndex1) {
+				have_player_1 = true;
+			} else if (controller.playerIndex == GCControllerPlayerIndex2) {
+				have_player_2 = true;
+			} else if (controller.playerIndex == GCControllerPlayerIndex3) {
+				have_player_3 = true;
+			} else if (controller.playerIndex == GCControllerPlayerIndex4) {
+				have_player_4 = true;
+			};
+		};
+	};
+
+	if (!have_player_1) {
+		return GCControllerPlayerIndex1;
+	} else if (!have_player_2) {
+		return GCControllerPlayerIndex2;
+	} else if (!have_player_3) {
+		return GCControllerPlayerIndex3;
+	} else if (!have_player_4) {
+		return GCControllerPlayerIndex4;
+	} else {
+		return GCControllerPlayerIndexUnset;
+	};
+}
+
+- (void)setControllerInputHandler:(GCController *)controller {
+	// Hook in the callback handler for the correct gamepad profile.
+	// This is a bit of a weird design choice on Apples part.
+	// You need to select the most capable gamepad profile for the
+	// gamepad attached.
+	if (controller.extendedGamepad != nil) {
+		// The extended gamepad profile has all the input you could possibly find on
+		// a gamepad but will only be active if your gamepad actually has all of
+		// these...
+		_weakify(self);
+		_weakify(controller);
+
+		controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) {
+			_strongify(self);
+			_strongify(controller);
+
+			int joy_id = [self getJoyIdForController:controller];
+
+			if (element == gamepad.buttonA) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
+						gamepad.buttonA.isPressed);
+			} else if (element == gamepad.buttonB) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1,
+						gamepad.buttonB.isPressed);
+			} else if (element == gamepad.buttonX) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
+						gamepad.buttonX.isPressed);
+			} else if (element == gamepad.buttonY) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3,
+						gamepad.buttonY.isPressed);
+			} else if (element == gamepad.leftShoulder) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_L,
+						gamepad.leftShoulder.isPressed);
+			} else if (element == gamepad.rightShoulder) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_R,
+						gamepad.rightShoulder.isPressed);
+			} else if (element == gamepad.leftTrigger) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_L2,
+						gamepad.leftTrigger.isPressed);
+			} else if (element == gamepad.rightTrigger) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_R2,
+						gamepad.rightTrigger.isPressed);
+			} else if (element == gamepad.dpad) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
+						gamepad.dpad.up.isPressed);
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
+						gamepad.dpad.down.isPressed);
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
+						gamepad.dpad.left.isPressed);
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
+						gamepad.dpad.right.isPressed);
+			};
+
+			InputDefault::JoyAxis jx;
+			jx.min = -1;
+			if (element == gamepad.leftThumbstick) {
+				jx.value = gamepad.leftThumbstick.xAxis.value;
+				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LX, jx);
+				jx.value = -gamepad.leftThumbstick.yAxis.value;
+				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LY, jx);
+			} else if (element == gamepad.rightThumbstick) {
+				jx.value = gamepad.rightThumbstick.xAxis.value;
+				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RX, jx);
+				jx.value = -gamepad.rightThumbstick.yAxis.value;
+				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RY, jx);
+			} else if (element == gamepad.leftTrigger) {
+				jx.value = gamepad.leftTrigger.value;
+				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_L2, jx);
+			} else if (element == gamepad.rightTrigger) {
+				jx.value = gamepad.rightTrigger.value;
+				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_R2, jx);
+			}
+		};
+	} else if (controller.microGamepad != nil) {
+		// micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
+		_weakify(self);
+		_weakify(controller);
+
+		controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
+			_strongify(self);
+			_strongify(controller);
+
+			int joy_id = [self getJoyIdForController:controller];
+
+			if (element == gamepad.buttonA) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
+						gamepad.buttonA.isPressed);
+			} else if (element == gamepad.buttonX) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
+						gamepad.buttonX.isPressed);
+			} else if (element == gamepad.dpad) {
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
+						gamepad.dpad.up.isPressed);
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
+						gamepad.dpad.down.isPressed);
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
+						gamepad.dpad.left.isPressed);
+				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
+						gamepad.dpad.right.isPressed);
+			}
+		};
+	}
+
+	///@TODO need to add support for controller.motion which gives us access to
+	/// the orientation of the device (if supported)
+
+	///@TODO need to add support for controllerPausedHandler which should be a
+	/// toggle
+};
+
+@end

+ 42 - 0
platform/iphone/native_video_view.h

@@ -0,0 +1,42 @@
+/*************************************************************************/
+/*  native_video_view.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.                */
+/*************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+@interface GodotNativeVideoView : UIView
+
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
+- (BOOL)isVideoPlaying;
+- (void)pauseVideo;
+- (void)unfocusVideo;
+- (void)unpauseVideo;
+- (void)stopVideo;
+
+@end

+ 265 - 0
platform/iphone/native_video_view.m

@@ -0,0 +1,265 @@
+/*************************************************************************/
+/*  native_video_view.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 "native_video_view.h"
+
+#import <AVFoundation/AVFoundation.h>
+
+@interface GodotNativeVideoView ()
+
+@property(strong, nonatomic) AVAsset *avAsset;
+@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
+@property(strong, nonatomic) AVPlayer *avPlayer;
+@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
+@property(assign, nonatomic) CMTime videoCurrentTime;
+@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying;
+
+@end
+
+@implementation GodotNativeVideoView
+
+- (instancetype)initWithFrame:(CGRect)frame {
+	self = [super initWithFrame:frame];
+
+	if (self) {
+		[self godot_commonInit];
+	}
+
+	return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+	self = [super initWithCoder:coder];
+
+	if (self) {
+		[self godot_commonInit];
+	}
+
+	return self;
+}
+
+- (void)godot_commonInit {
+	self.isVideoCurrentlyPlaying = NO;
+	self.videoCurrentTime = kCMTimeZero;
+
+	[self observeVideoAudio];
+}
+
+- (void)observeVideoAudio {
+	printf("******** adding observer for sound routing changes\n");
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(audioRouteChangeListenerCallback:)
+				   name:AVAudioSessionRouteChangeNotification
+				 object:nil];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+	if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) {
+		[self handleVideoOrPlayerStatus];
+	}
+
+	if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) {
+		[self handleVideoPlayRate];
+	}
+}
+
+// MARK: Video Audio
+
+- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
+	printf("*********** route changed!\n");
+	NSDictionary *interuptionDict = notification.userInfo;
+
+	NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
+
+	switch (routeChangeReason) {
+		case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
+			NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
+			NSLog(@"Headphone/Line plugged in");
+		} break;
+		case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
+			NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
+			NSLog(@"Headphone/Line was pulled. Resuming video play....");
+			if ([self isVideoPlaying]) {
+				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+					[self.avPlayer play]; // NOTE: change this line according your current player implementation
+					NSLog(@"resumed play");
+				});
+			}
+		} break;
+		case AVAudioSessionRouteChangeReasonCategoryChange: {
+			// called at start - also when other audio wants to play
+			NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
+		} break;
+	}
+}
+
+// MARK: Native Video Player
+
+- (void)handleVideoOrPlayerStatus {
+	if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) {
+		[self stopVideo];
+	}
+
+	if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) {
+		//        NSLog(@"time: %@", self.video_current_time);
+		[self.avPlayer seekToTime:self.videoCurrentTime];
+		self.videoCurrentTime = kCMTimeZero;
+	}
+}
+
+- (void)handleVideoPlayRate {
+	NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate);
+	if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) {
+		dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+			[self.avPlayer play]; // NOTE: change this line according your current player implementation
+			NSLog(@"resumed play");
+		});
+
+		NSLog(@" . . . PAUSED (or just started)");
+	}
+}
+
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
+	self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]];
+
+	self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset];
+	[self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil];
+
+	self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem];
+	self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
+
+	[self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil];
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(playerItemDidReachEnd:)
+				   name:AVPlayerItemDidPlayToEndTimeNotification
+				 object:[self.avPlayer currentItem]];
+
+	[self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
+
+	[self.avPlayerLayer setFrame:self.bounds];
+	[self.layer addSublayer:self.avPlayerLayer];
+	[self.avPlayer play];
+
+	AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
+
+	NSMutableArray *allAudioParams = [NSMutableArray array];
+	for (id track in audioGroup.options) {
+		NSString *language = [[track locale] localeIdentifier];
+		NSLog(@"subtitle lang: %@", language);
+
+		if ([language isEqualToString:audioTrack]) {
+			AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
+			[audioInputParams setVolume:videoVolume atTime:kCMTimeZero];
+			[audioInputParams setTrackID:[track trackID]];
+			[allAudioParams addObject:audioInputParams];
+
+			AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
+			[audioMix setInputParameters:allAudioParams];
+
+			[self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
+			[self.avPlayer.currentItem setAudioMix:audioMix];
+
+			break;
+		}
+	}
+
+	AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
+	NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
+
+	for (id track in useableTracks) {
+		NSString *language = [[track locale] localeIdentifier];
+		NSLog(@"subtitle lang: %@", language);
+
+		if ([language isEqualToString:subtitleTrack]) {
+			[self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
+			break;
+		}
+	}
+
+	self.isVideoCurrentlyPlaying = YES;
+
+	return true;
+}
+
+- (BOOL)isVideoPlaying {
+	if (self.avPlayer.error) {
+		printf("Error during playback\n");
+	}
+	return (self.avPlayer.rate > 0 && !self.avPlayer.error);
+}
+
+- (void)pauseVideo {
+	self.videoCurrentTime = self.avPlayer.currentTime;
+	[self.avPlayer pause];
+	self.isVideoCurrentlyPlaying = NO;
+}
+
+- (void)unfocusVideo {
+	[self.avPlayer pause];
+}
+
+- (void)unpauseVideo {
+	[self.avPlayer play];
+	self.isVideoCurrentlyPlaying = YES;
+}
+
+- (void)playerItemDidReachEnd:(NSNotification *)notification {
+	[self stopVideo];
+}
+
+- (void)finishPlayingVideo {
+	[self.avPlayer pause];
+	[self.avPlayerLayer removeFromSuperlayer];
+	self.avPlayerLayer = nil;
+
+	if (self.avPlayerItem) {
+		[self.avPlayerItem removeObserver:self forKeyPath:@"status"];
+		self.avPlayerItem = nil;
+	}
+
+	if (self.avPlayer) {
+		[self.avPlayer removeObserver:self forKeyPath:@"status"];
+		self.avPlayer = nil;
+	}
+
+	self.avAsset = nil;
+
+	self.isVideoCurrentlyPlaying = NO;
+}
+
+- (void)stopVideo {
+	[self finishPlayingVideo];
+
+	[self removeFromSuperview];
+}
+
+@end

+ 42 - 48
platform/iphone/os_iphone.h

@@ -36,6 +36,7 @@
 #include "core/os/input.h"
 #include "core/os/input.h"
 #include "drivers/coreaudio/audio_driver_coreaudio.h"
 #include "drivers/coreaudio/audio_driver_coreaudio.h"
 #include "drivers/unix/os_unix.h"
 #include "drivers/unix/os_unix.h"
+#include "joypad_iphone.h"
 
 
 #include "game_center.h"
 #include "game_center.h"
 #include "icloud.h"
 #include "icloud.h"
@@ -49,11 +50,6 @@
 class OSIPhone : public OS_Unix {
 class OSIPhone : public OS_Unix {
 
 
 private:
 private:
-	enum {
-		MAX_MOUSE_COUNT = 8,
-		MAX_EVENTS = 64,
-	};
-
 	static HashMap<String, void *> dynamic_symbol_lookup_table;
 	static HashMap<String, void *> dynamic_symbol_lookup_table;
 	friend void register_dynamic_symbol(char *name, void *address);
 	friend void register_dynamic_symbol(char *name, void *address);
 
 
@@ -72,6 +68,8 @@ private:
 #endif
 #endif
 	iOS *ios;
 	iOS *ios;
 
 
+	JoypadIPhone *joypad_iphone;
+
 	MainLoop *main_loop;
 	MainLoop *main_loop;
 
 
 	VideoMode video_mode;
 	VideoMode video_mode;
@@ -91,39 +89,56 @@ private:
 
 
 	virtual void finalize();
 	virtual void finalize();
 
 
-	struct MouseList {
-
-		bool pressed[MAX_MOUSE_COUNT];
-		MouseList() {
-			for (int i = 0; i < MAX_MOUSE_COUNT; i++)
-				pressed[i] = false;
-		};
-	};
-
-	MouseList touch_list;
+	void perform_event(const Ref<InputEvent> &p_event);
 
 
-	Vector3 last_accel;
-
-	Ref<InputEvent> event_queue[MAX_EVENTS];
-	int event_count;
-	void queue_event(const Ref<InputEvent> &p_event);
+	void set_data_dir(String p_dir);
 
 
 	String data_dir;
 	String data_dir;
 
 
 	InputDefault *input;
 	InputDefault *input;
 
 
-	int virtual_keyboard_height;
+	int virtual_keyboard_height = 0;
 
 
 	int video_driver_index;
 	int video_driver_index;
 
 
+	bool is_focused = false;
+
 public:
 public:
+	static OSIPhone *get_singleton();
+
+	OSIPhone(String p_data_dir);
+	~OSIPhone();
+
 	bool iterate();
 	bool iterate();
 
 
-	uint8_t get_orientations() const;
+	void start();
+
+	virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
+	virtual Error close_dynamic_library(void *p_library_handle);
+	virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
+
+	virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
+
+	virtual String get_name() const;
+	virtual String get_model_name() const;
+
+	Error shell_open(String p_uri);
+
+	String get_user_data_dir() const;
+
+	String get_locale() const;
+
+	String get_unique_id() const;
+
+	virtual void vibrate_handheld(int p_duration_ms = 500);
+
+	virtual bool _check_internal_feature_support(const String &p_feature);
+
+	virtual int get_screen_dpi(int p_screen = -1) const;
 
 
 	void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
 	void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
 	void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
 	void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
-	void touches_cancelled();
+	void touches_cancelled(int p_idx);
 	void key(uint32_t p_key, bool p_pressed);
 	void key(uint32_t p_key, bool p_pressed);
 	void set_virtual_keyboard_height(int p_height);
 	void set_virtual_keyboard_height(int p_height);
 
 
@@ -139,23 +154,17 @@ public:
 	void joy_button(int p_device, int p_button, bool p_pressed);
 	void joy_button(int p_device, int p_button, bool p_pressed);
 	void joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value);
 	void joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value);
 
 
-	static OSIPhone *get_singleton();
-
 	virtual void set_mouse_show(bool p_show);
 	virtual void set_mouse_show(bool p_show);
 	virtual void set_mouse_grab(bool p_grab);
 	virtual void set_mouse_grab(bool p_grab);
 	virtual bool is_mouse_grab_enabled() const;
 	virtual bool is_mouse_grab_enabled() const;
 	virtual Point2 get_mouse_position() const;
 	virtual Point2 get_mouse_position() const;
 	virtual int get_mouse_button_state() const;
 	virtual int get_mouse_button_state() const;
-	virtual void set_window_title(const String &p_title);
-
-	virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
 
 
-	virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
-	virtual Error close_dynamic_library(void *p_library_handle);
-	virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
+	virtual void set_window_title(const String &p_title);
 
 
 	virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
 	virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
 	virtual VideoMode get_video_mode(int p_screen = 0) const;
 	virtual VideoMode get_video_mode(int p_screen = 0) const;
+
 	virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
 	virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
 
 
 	virtual void set_keep_screen_on(bool p_enabled);
 	virtual void set_keep_screen_on(bool p_enabled);
@@ -172,30 +181,15 @@ public:
 
 
 	virtual bool has_touchscreen_ui_hint() const;
 	virtual bool has_touchscreen_ui_hint() const;
 
 
-	void set_data_dir(String p_dir);
-
-	virtual String get_name() const;
-	virtual String get_model_name() const;
-
-	Error shell_open(String p_uri);
-
-	String get_user_data_dir() const;
-
-	String get_locale() const;
-
-	String get_unique_id() const;
-
 	virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
 	virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
 	virtual bool native_video_is_playing() const;
 	virtual bool native_video_is_playing() const;
 	virtual void native_video_pause();
 	virtual void native_video_pause();
 	virtual void native_video_unpause();
 	virtual void native_video_unpause();
 	virtual void native_video_focus_out();
 	virtual void native_video_focus_out();
 	virtual void native_video_stop();
 	virtual void native_video_stop();
-	virtual void vibrate_handheld(int p_duration_ms = 500);
 
 
-	virtual bool _check_internal_feature_support(const String &p_feature);
-	OSIPhone(int width, int height, String p_data_dir);
-	~OSIPhone();
+	void on_focus_out();
+	void on_focus_in();
 };
 };
 
 
 #endif // OS_IPHONE_H
 #endif // OS_IPHONE_H

+ 252 - 192
platform/iphone/os_iphone.mm

@@ -30,11 +30,6 @@
 
 
 #ifdef IPHONE_ENABLED
 #ifdef IPHONE_ENABLED
 
 
-// System headers are at top
-// to workaround `ambiguous expansion` warning/error
-#import <UIKit/UIKit.h>
-#include <dlfcn.h>
-
 #include "os_iphone.h"
 #include "os_iphone.h"
 
 
 #include "drivers/gles2/rasterizer_gles2.h"
 #include "drivers/gles2/rasterizer_gles2.h"
@@ -52,13 +47,33 @@
 
 
 #include "semaphore_iphone.h"
 #include "semaphore_iphone.h"
 
 
-int OSIPhone::get_video_driver_count() const {
+#import "app_delegate.h"
+#import "device_metrics.h"
+#import "godot_view.h"
+#import "native_video_view.h"
+#import "view_controller.h"
+
+#import <UIKit/UIKit.h>
+#include <dlfcn.h>
+#import <sys/utsname.h>
+
+extern int gl_view_base_fb; // from gl_view.mm
+extern bool gles3_available; // from gl_view.mm
+
+// Initialization order between compilation units is not guaranteed,
+// so we use this as a hack to ensure certain code is called before
+// everything else, but after all units are initialized.
+typedef void (*init_callback)();
+static init_callback *ios_init_callbacks = NULL;
+static int ios_init_callbacks_count = 0;
+static int ios_init_callbacks_capacity = 0;
+HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
 
 
+int OSIPhone::get_video_driver_count() const {
 	return 2;
 	return 2;
 };
 };
 
 
 const char *OSIPhone::get_video_driver_name(int p_driver) const {
 const char *OSIPhone::get_video_driver_name(int p_driver) const {
-
 	switch (p_driver) {
 	switch (p_driver) {
 		case VIDEO_DRIVER_GLES3:
 		case VIDEO_DRIVER_GLES3:
 			return "GLES3";
 			return "GLES3";
@@ -69,14 +84,10 @@ const char *OSIPhone::get_video_driver_name(int p_driver) const {
 };
 };
 
 
 OSIPhone *OSIPhone::get_singleton() {
 OSIPhone *OSIPhone::get_singleton() {
-
 	return (OSIPhone *)OS::get_singleton();
 	return (OSIPhone *)OS::get_singleton();
 };
 };
 
 
-extern int gl_view_base_fb; // from gl_view.mm
-
 void OSIPhone::set_data_dir(String p_dir) {
 void OSIPhone::set_data_dir(String p_dir) {
-
 	DirAccess *da = DirAccess::open(p_dir);
 	DirAccess *da = DirAccess::open(p_dir);
 
 
 	data_dir = da->get_current_dir();
 	data_dir = da->get_current_dir();
@@ -101,7 +112,13 @@ int OSIPhone::get_current_video_driver() const {
 	return video_driver_index;
 	return video_driver_index;
 }
 }
 
 
-extern bool gles3_available; // from gl_view.mm
+void OSIPhone::start() {
+	Main::start();
+
+	if (joypad_iphone) {
+		joypad_iphone->start_processing();
+	}
+}
 
 
 Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
 Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
 
 
@@ -153,10 +170,11 @@ Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p
 	//visual_server->cursor_set_visible(false, 0);
 	//visual_server->cursor_set_visible(false, 0);
 
 
 	// reset this to what it should be, it will have been set to 0 after visual_server->init() is called
 	// reset this to what it should be, it will have been set to 0 after visual_server->init() is called
-	if (use_gl3)
+	if (use_gl3) {
 		RasterizerStorageGLES3::system_fbo = gl_view_base_fb;
 		RasterizerStorageGLES3::system_fbo = gl_view_base_fb;
-	else
+	} else {
 		RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
 		RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
+	}
 
 
 	AudioDriverManager::initialize(p_audio_driver);
 	AudioDriverManager::initialize(p_audio_driver);
 
 
@@ -180,6 +198,8 @@ Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p
 	ios = memnew(iOS);
 	ios = memnew(iOS);
 	Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
 	Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
 
 
+	joypad_iphone = memnew(JoypadIPhone);
+
 	return OK;
 	return OK;
 };
 };
 
 
@@ -199,78 +219,57 @@ void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
 };
 };
 
 
 bool OSIPhone::iterate() {
 bool OSIPhone::iterate() {
-
-	if (!main_loop)
+	if (!main_loop) {
 		return true;
 		return true;
-
-	if (main_loop) {
-		for (int i = 0; i < event_count; i++) {
-
-			input->parse_input_event(event_queue[i]);
-		};
-	};
-	event_count = 0;
+	}
 
 
 	return Main::iteration();
 	return Main::iteration();
 };
 };
 
 
 void OSIPhone::key(uint32_t p_key, bool p_pressed) {
 void OSIPhone::key(uint32_t p_key, bool p_pressed) {
-
 	Ref<InputEventKey> ev;
 	Ref<InputEventKey> ev;
 	ev.instance();
 	ev.instance();
 	ev->set_echo(false);
 	ev->set_echo(false);
 	ev->set_pressed(p_pressed);
 	ev->set_pressed(p_pressed);
 	ev->set_scancode(p_key);
 	ev->set_scancode(p_key);
 	ev->set_unicode(p_key);
 	ev->set_unicode(p_key);
-	queue_event(ev);
+	perform_event(ev);
 };
 };
 
 
 void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
 void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
+	if (GLOBAL_DEF("debug/disable_touch", false)) {
+		return;
+	}
 
 
-	if (!GLOBAL_DEF("debug/disable_touch", false)) {
-		Ref<InputEventScreenTouch> ev;
-		ev.instance();
-
-		ev->set_index(p_idx);
-		ev->set_pressed(p_pressed);
-		ev->set_position(Vector2(p_x, p_y));
-		queue_event(ev);
-	};
+	Ref<InputEventScreenTouch> ev;
+	ev.instance();
 
 
-	touch_list.pressed[p_idx] = p_pressed;
+	ev->set_index(p_idx);
+	ev->set_pressed(p_pressed);
+	ev->set_position(Vector2(p_x, p_y));
+	perform_event(ev);
 };
 };
 
 
 void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
 void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
+	if (GLOBAL_DEF("debug/disable_touch", false)) {
+		return;
+	}
 
 
-	if (!GLOBAL_DEF("debug/disable_touch", false)) {
-
-		Ref<InputEventScreenDrag> ev;
-		ev.instance();
-		ev->set_index(p_idx);
-		ev->set_position(Vector2(p_x, p_y));
-		ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
-		queue_event(ev);
-	};
-};
-
-void OSIPhone::queue_event(const Ref<InputEvent> &p_event) {
-
-	ERR_FAIL_INDEX(event_count, MAX_EVENTS);
-
-	event_queue[event_count++] = p_event;
-};
-
-void OSIPhone::touches_cancelled() {
-
-	for (int i = 0; i < MAX_MOUSE_COUNT; i++) {
+	Ref<InputEventScreenDrag> ev;
+	ev.instance();
+	ev->set_index(p_idx);
+	ev->set_position(Vector2(p_x, p_y));
+	ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
+	perform_event(ev);
+}
 
 
-		if (touch_list.pressed[i]) {
+void OSIPhone::perform_event(const Ref<InputEvent> &p_event) {
+	input->parse_input_event(p_event);
+}
 
 
-			// send a mouse_up outside the screen
-			touch_press(i, -1, -1, false, false);
-		};
-	};
-};
+void OSIPhone::touches_cancelled(int p_idx) {
+	touch_press(p_idx, -1, -1, false, false);
+}
 
 
 static const float ACCEL_RANGE = 1;
 static const float ACCEL_RANGE = 1;
 
 
@@ -282,39 +281,6 @@ void OSIPhone::update_accelerometer(float p_x, float p_y, float p_z) {
 
 
 	// Found out the Z should not be negated! Pass as is!
 	// Found out the Z should not be negated! Pass as is!
 	input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE));
 	input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE));
-
-	/*
-	if (p_x != last_accel.x) {
-		//printf("updating accel x %f\n", p_x);
-		InputEvent ev;
-		ev.type = InputEvent::JOYPAD_MOTION;
-		ev.device = 0;
-		ev.joy_motion.axis = JOY_ANALOG_0;
-		ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE);
-		last_accel.x = p_x;
-		queue_event(ev);
-	};
-	if (p_y != last_accel.y) {
-		//printf("updating accel y %f\n", p_y);
-		InputEvent ev;
-		ev.type = InputEvent::JOYPAD_MOTION;
-		ev.device = 0;
-		ev.joy_motion.axis = JOY_ANALOG_1;
-		ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE);
-		last_accel.y = p_y;
-		queue_event(ev);
-	};
-	if (p_z != last_accel.z) {
-		//printf("updating accel z %f\n", p_z);
-		InputEvent ev;
-		ev.type = InputEvent::JOYPAD_MOTION;
-		ev.device = 0;
-		ev.joy_motion.axis = JOY_ANALOG_2;
-		ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE);
-		last_accel.z = p_z;
-		queue_event(ev);
-	};
-	*/
 };
 };
 
 
 void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
 void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
@@ -355,59 +321,76 @@ void OSIPhone::finalize() {
 
 
 	delete_main_loop();
 	delete_main_loop();
 
 
-	memdelete(input);
-	memdelete(ios);
+	if (joypad_iphone) {
+		memdelete(joypad_iphone);
+	}
+
+	if (input) {
+		memdelete(input);
+	}
+
+	if (ios) {
+		memdelete(ios);
+	}
 
 
 #ifdef GAME_CENTER_ENABLED
 #ifdef GAME_CENTER_ENABLED
-	memdelete(game_center);
+	if (game_center) {
+		memdelete(game_center);
+	}
 #endif
 #endif
 
 
 #ifdef STOREKIT_ENABLED
 #ifdef STOREKIT_ENABLED
-	memdelete(store_kit);
+	if (store_kit) {
+		memdelete(store_kit);
+	}
 #endif
 #endif
 
 
 #ifdef ICLOUD_ENABLED
 #ifdef ICLOUD_ENABLED
-	memdelete(icloud);
+	if (icloud) {
+		memdelete(icloud);
+	}
 #endif
 #endif
 
 
 	visual_server->finish();
 	visual_server->finish();
 	memdelete(visual_server);
 	memdelete(visual_server);
 	//	memdelete(rasterizer);
 	//	memdelete(rasterizer);
+}
 
 
-	// Free unhandled events before close
-	for (int i = 0; i < MAX_EVENTS; i++) {
-		event_queue[i].unref();
-	};
-	event_count = 0;
-};
+void OSIPhone::set_mouse_show(bool p_show) {
+	// Not supported for iOS
+}
 
 
-void OSIPhone::set_mouse_show(bool p_show){};
-void OSIPhone::set_mouse_grab(bool p_grab){};
+void OSIPhone::set_mouse_grab(bool p_grab) {
+	// Not supported for iOS
+}
 
 
 bool OSIPhone::is_mouse_grab_enabled() const {
 bool OSIPhone::is_mouse_grab_enabled() const {
-
+	// Not supported for iOS
 	return true;
 	return true;
-};
+}
 
 
 Point2 OSIPhone::get_mouse_position() const {
 Point2 OSIPhone::get_mouse_position() const {
-
+	// Not supported for iOS
 	return Point2();
 	return Point2();
-};
+}
 
 
 int OSIPhone::get_mouse_button_state() const {
 int OSIPhone::get_mouse_button_state() const {
-
+	// Not supported for iOS
 	return 0;
 	return 0;
-};
+}
 
 
-void OSIPhone::set_window_title(const String &p_title){};
+void OSIPhone::set_window_title(const String &p_title) {
+	// Not supported for iOS
+}
 
 
 void OSIPhone::alert(const String &p_alert, const String &p_title) {
 void OSIPhone::alert(const String &p_alert, const String &p_title) {
-
 	const CharString utf8_alert = p_alert.utf8();
 	const CharString utf8_alert = p_alert.utf8();
 	const CharString utf8_title = p_title.utf8();
 	const CharString utf8_title = p_title.utf8();
 	iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
 	iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
 }
 }
 
 
+// MARK: Dynamic Libraries
+
 Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
 Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
 	if (p_path.length() == 0) {
 	if (p_path.length() == 0) {
 		p_library_handle = RTLD_SELF;
 		p_library_handle = RTLD_SELF;
@@ -423,7 +406,6 @@ Error OSIPhone::close_dynamic_library(void *p_library_handle) {
 	return OS_Unix::close_dynamic_library(p_library_handle);
 	return OS_Unix::close_dynamic_library(p_library_handle);
 }
 }
 
 
-HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
 void register_dynamic_symbol(char *name, void *address) {
 void register_dynamic_symbol(char *name, void *address) {
 	OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
 	OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
 }
 }
@@ -440,55 +422,46 @@ Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const
 }
 }
 
 
 void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
 void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
-
 	video_mode = p_video_mode;
 	video_mode = p_video_mode;
-};
+}
 
 
 OS::VideoMode OSIPhone::get_video_mode(int p_screen) const {
 OS::VideoMode OSIPhone::get_video_mode(int p_screen) const {
 
 
 	return video_mode;
 	return video_mode;
-};
+}
 
 
 void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
 void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
-
 	p_list->push_back(video_mode);
 	p_list->push_back(video_mode);
-};
+}
 
 
 bool OSIPhone::can_draw() const {
 bool OSIPhone::can_draw() const {
 
 
 	if (native_video_is_playing())
 	if (native_video_is_playing())
 		return false;
 		return false;
 	return true;
 	return true;
-};
+}
 
 
 int OSIPhone::set_base_framebuffer(int p_fb) {
 int OSIPhone::set_base_framebuffer(int p_fb) {
-
 	// gl_view_base_fb has not been updated yet
 	// gl_view_base_fb has not been updated yet
 	RasterizerStorageGLES3::system_fbo = p_fb;
 	RasterizerStorageGLES3::system_fbo = p_fb;
 
 
 	return 0;
 	return 0;
-};
+}
 
 
 bool OSIPhone::has_virtual_keyboard() const {
 bool OSIPhone::has_virtual_keyboard() const {
 	return true;
 	return true;
 };
 };
 
 
-extern void _show_keyboard(String p_existing);
-extern void _hide_keyboard();
-extern Error _shell_open(String p_uri);
-extern void _set_keep_screen_on(bool p_enabled);
-extern void _vibrate();
-
 void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
 void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
-	_show_keyboard(p_existing_text);
+	[AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text];
 };
 };
 
 
 void OSIPhone::hide_virtual_keyboard() {
 void OSIPhone::hide_virtual_keyboard() {
-	_hide_keyboard();
-};
+	[AppDelegate.viewController.godotView resignFirstResponder];
+}
 
 
 void OSIPhone::set_virtual_keyboard_height(int p_height) {
 void OSIPhone::set_virtual_keyboard_height(int p_height) {
-	virtual_keyboard_height = p_height;
+	virtual_keyboard_height = p_height * [UIScreen mainScreen].nativeScale;
 }
 }
 
 
 int OSIPhone::get_virtual_keyboard_height() const {
 int OSIPhone::get_virtual_keyboard_height() const {
@@ -496,46 +469,103 @@ int OSIPhone::get_virtual_keyboard_height() const {
 }
 }
 
 
 Error OSIPhone::shell_open(String p_uri) {
 Error OSIPhone::shell_open(String p_uri) {
-	return _shell_open(p_uri);
-};
+	NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
+	NSURL *url = [NSURL URLWithString:urlPath];
+
+	if (![[UIApplication sharedApplication] canOpenURL:url]) {
+		return ERR_CANT_OPEN;
+	}
+
+	printf("opening url %s\n", p_uri.utf8().get_data());
+
+	[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
+
+	return OK;
+}
 
 
 void OSIPhone::set_keep_screen_on(bool p_enabled) {
 void OSIPhone::set_keep_screen_on(bool p_enabled) {
 	OS::set_keep_screen_on(p_enabled);
 	OS::set_keep_screen_on(p_enabled);
-	_set_keep_screen_on(p_enabled);
+	[UIApplication sharedApplication].idleTimerDisabled = p_enabled;
 };
 };
 
 
 String OSIPhone::get_user_data_dir() const {
 String OSIPhone::get_user_data_dir() const {
-
 	return data_dir;
 	return data_dir;
-};
+}
 
 
 String OSIPhone::get_name() const {
 String OSIPhone::get_name() const {
-
 	return "iOS";
 	return "iOS";
-};
+}
 
 
 String OSIPhone::get_model_name() const {
 String OSIPhone::get_model_name() const {
-
 	String model = ios->get_model();
 	String model = ios->get_model();
-	if (model != "")
+	if (model != "") {
 		return model;
 		return model;
+	}
 
 
 	return OS_Unix::get_model_name();
 	return OS_Unix::get_model_name();
 }
 }
 
 
 Size2 OSIPhone::get_window_size() const {
 Size2 OSIPhone::get_window_size() const {
-
 	return Vector2(video_mode.width, video_mode.height);
 	return Vector2(video_mode.width, video_mode.height);
 }
 }
 
 
-extern Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height);
+int OSIPhone::get_screen_dpi(int p_screen) const {
+	struct utsname systemInfo;
+	uname(&systemInfo);
+
+	NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
+
+	NSDictionary *iOSModelToDPI = [GodotDeviceMetrics dpiList];
+
+	for (NSArray *keyArray in iOSModelToDPI) {
+		if ([keyArray containsObject:string]) {
+			NSNumber *value = iOSModelToDPI[keyArray];
+			return [value intValue];
+		}
+	}
+
+	// If device wasn't found in dictionary
+	// make a best guess from device metrics.
+	CGFloat scale = [UIScreen mainScreen].scale;
+
+	UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom;
+
+	switch (idiom) {
+		case UIUserInterfaceIdiomPad:
+			return scale == 2 ? 264 : 132;
+		case UIUserInterfaceIdiomPhone: {
+			if (scale == 3) {
+				CGFloat nativeScale = [UIScreen mainScreen].nativeScale;
+				return nativeScale == 3 ? 458 : 401;
+			}
+
+			return 326;
+		}
+		default:
+			return 72;
+	}
+}
 
 
 Rect2 OSIPhone::get_window_safe_area() const {
 Rect2 OSIPhone::get_window_safe_area() const {
-	return _get_ios_window_safe_area(video_mode.width, video_mode.height);
+	if (@available(iOS 11, *)) {
+		UIEdgeInsets insets = UIEdgeInsetsZero;
+		UIView *view = AppDelegate.viewController.godotView;
+
+		if ([view respondsToSelector:@selector(safeAreaInsets)]) {
+			insets = [view safeAreaInsets];
+		}
+
+		float scale = [UIScreen mainScreen].nativeScale;
+		Size2i insets_position = Size2i(insets.left, insets.top) * scale;
+		Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
+
+		return Rect2i(insets_position, get_window_size() - insets_size);
+	} else {
+		return Rect2i(Size2i(0, 0), get_window_size());
+	}
 }
 }
 
 
 bool OSIPhone::has_touchscreen_ui_hint() const {
 bool OSIPhone::has_touchscreen_ui_hint() const {
-
 	return true;
 	return true;
 }
 }
 
 
@@ -550,79 +580,82 @@ String OSIPhone::get_locale() const {
 	return String::utf8([localeIdentifier UTF8String]).replace("-", "_");
 	return String::utf8([localeIdentifier UTF8String]).replace("-", "_");
 }
 }
 
 
-extern bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
-extern bool _is_video_playing();
-extern void _pause_video();
-extern void _unpause_video();
-extern void _stop_video();
-extern void _focus_out_video();
-
 Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
 Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
 	FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
 	FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
 	bool exists = f && f->is_open();
 	bool exists = f && f->is_open();
 
 
-	String tempFile = get_user_data_dir();
-	if (!exists)
+	String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir();
+
+	if (!exists) {
 		return FAILED;
 		return FAILED;
+	}
+
+	String tempFile = OSIPhone::get_singleton()->get_user_data_dir();
 
 
 	if (p_path.begins_with("res://")) {
 	if (p_path.begins_with("res://")) {
 		if (PackedData::get_singleton()->has_path(p_path)) {
 		if (PackedData::get_singleton()->has_path(p_path)) {
-			print("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str());
+			printf("Unable to play %s using the native player as it resides in a .pck file\n", p_path.utf8().get_data());
 			return ERR_INVALID_PARAMETER;
 			return ERR_INVALID_PARAMETER;
 		} else {
 		} else {
 			p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
 			p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
 		}
 		}
-	} else if (p_path.begins_with("user://"))
-		p_path = p_path.replace("user:/", get_user_data_dir());
+	} else if (p_path.begins_with("user://")) {
+		p_path = p_path.replace("user:/", user_data_dir);
+	}
 
 
 	memdelete(f);
 	memdelete(f);
 
 
-	print("Playing video: %S\n", p_path.c_str());
-	if (_play_video(p_path, p_volume, p_audio_track, p_subtitle_track))
+	printf("Playing video: %s\n", p_path.utf8().get_data());
+
+	String file_path = ProjectSettings::get_singleton()->globalize_path(p_path);
+
+	NSString *filePath = [[NSString alloc] initWithUTF8String:file_path.utf8().get_data()];
+	NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()];
+	NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()];
+
+	if (![AppDelegate.viewController playVideoAtPath:filePath
+											  volume:p_volume
+											   audio:audioTrack
+											subtitle:subtitleTrack]) {
 		return OK;
 		return OK;
+	}
+
 	return FAILED;
 	return FAILED;
 }
 }
 
 
 bool OSIPhone::native_video_is_playing() const {
 bool OSIPhone::native_video_is_playing() const {
-	return _is_video_playing();
+	return [AppDelegate.viewController.videoView isVideoPlaying];
 }
 }
 
 
 void OSIPhone::native_video_pause() {
 void OSIPhone::native_video_pause() {
-	if (native_video_is_playing())
-		_pause_video();
+	if (native_video_is_playing()) {
+		[AppDelegate.viewController.videoView pauseVideo];
+	}
 }
 }
 
 
 void OSIPhone::native_video_unpause() {
 void OSIPhone::native_video_unpause() {
-	_unpause_video();
-};
+	[AppDelegate.viewController.videoView unpauseVideo];
+}
 
 
 void OSIPhone::native_video_focus_out() {
 void OSIPhone::native_video_focus_out() {
-	_focus_out_video();
-};
+	[AppDelegate.viewController.videoView unfocusVideo];
+}
 
 
 void OSIPhone::native_video_stop() {
 void OSIPhone::native_video_stop() {
-	if (native_video_is_playing())
-		_stop_video();
+	if (native_video_is_playing()) {
+		[AppDelegate.viewController.videoView stopVideo];
+	}
 }
 }
 
 
 void OSIPhone::vibrate_handheld(int p_duration_ms) {
 void OSIPhone::vibrate_handheld(int p_duration_ms) {
 	// iOS does not support duration for vibration
 	// iOS does not support duration for vibration
-	_vibrate();
+	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
 }
 }
 
 
 bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
 bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
-
 	return p_feature == "mobile";
 	return p_feature == "mobile";
 }
 }
 
 
-// Initialization order between compilation units is not guaranteed,
-// so we use this as a hack to ensure certain code is called before
-// everything else, but after all units are initialized.
-typedef void (*init_callback)();
-static init_callback *ios_init_callbacks = NULL;
-static int ios_init_callbacks_count = 0;
-static int ios_init_callbacks_capacity = 0;
-
 void add_ios_init_callback(init_callback cb) {
 void add_ios_init_callback(init_callback cb) {
 	if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
 	if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
 		void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
 		void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
@@ -637,7 +670,7 @@ void add_ios_init_callback(init_callback cb) {
 	}
 	}
 }
 }
 
 
-OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
+OSIPhone::OSIPhone(String p_data_dir) {
 	for (int i = 0; i < ios_init_callbacks_count; ++i) {
 	for (int i = 0; i < ios_init_callbacks_count; ++i) {
 		ios_init_callbacks[i]();
 		ios_init_callbacks[i]();
 	}
 	}
@@ -649,15 +682,6 @@ OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
 	main_loop = NULL;
 	main_loop = NULL;
 	visual_server = NULL;
 	visual_server = NULL;
 
 
-	VideoMode vm;
-	vm.fullscreen = true;
-	vm.width = width;
-	vm.height = height;
-	vm.resizable = false;
-	set_video_mode(vm);
-	event_count = 0;
-	virtual_keyboard_height = 0;
-
 	// can't call set_data_dir from here, since it requires DirAccess
 	// can't call set_data_dir from here, since it requires DirAccess
 	// which is initialized in initialize_core
 	// which is initialized in initialize_core
 	data_dir = p_data_dir;
 	data_dir = p_data_dir;
@@ -676,4 +700,40 @@ OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
 OSIPhone::~OSIPhone() {
 OSIPhone::~OSIPhone() {
 }
 }
 
 
+void OSIPhone::on_focus_out() {
+	if (is_focused) {
+		is_focused = false;
+
+		if (get_main_loop()) {
+			get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
+		}
+
+		[AppDelegate.viewController.godotView stopRendering];
+
+		if (native_video_is_playing()) {
+			native_video_focus_out();
+		}
+
+		audio_driver.stop();
+	}
+}
+
+void OSIPhone::on_focus_in() {
+	if (!is_focused) {
+		is_focused = true;
+
+		if (get_main_loop()) {
+			get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
+		}
+
+		[AppDelegate.viewController.godotView startRendering];
+
+		if (native_video_is_playing()) {
+			native_video_unpause();
+		}
+
+		audio_driver.start();
+	}
+}
+
 #endif
 #endif

+ 7 - 0
platform/iphone/platform_config.h

@@ -36,3 +36,10 @@
 #define PLATFORM_REFCOUNT
 #define PLATFORM_REFCOUNT
 
 
 #define PTHREAD_RENAME_SELF
 #define PTHREAD_RENAME_SELF
+
+#define _weakify(var) __weak typeof(var) GDWeak_##var = var;
+#define _strongify(var)                                      \
+	_Pragma("clang diagnostic push")                         \
+			_Pragma("clang diagnostic ignored \"-Wshadow\"") \
+					__strong typeof(var) var = GDWeak_##var; \
+	_Pragma("clang diagnostic pop")

+ 8 - 3
platform/iphone/view_controller.h

@@ -32,10 +32,15 @@
 #import <UIKit/UIKit.h>
 #import <UIKit/UIKit.h>
 
 
 @class GodotView;
 @class GodotView;
+@class GodotNativeVideoView;
 
 
-@interface ViewController : UIViewController <GKGameCenterControllerDelegate> {
-};
+@interface ViewController : UIViewController <GKGameCenterControllerDelegate>
 
 
-- (GodotView *)godotView;
+@property(nonatomic, readonly, strong) GodotView *godotView;
+@property(nonatomic, readonly, strong) GodotNativeVideoView *videoView;
+
+// MARK: Native Video Player
+
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
 
 
 @end
 @end

+ 108 - 37
platform/iphone/view_controller.mm

@@ -30,59 +30,59 @@
 
 
 #import "view_controller.h"
 #import "view_controller.h"
 
 
+#include "core/project_settings.h"
 #import "godot_view.h"
 #import "godot_view.h"
+#import "godot_view_renderer.h"
+#import "native_video_view.h"
 #include "os_iphone.h"
 #include "os_iphone.h"
 
 
-#include "core/project_settings.h"
+@interface ViewController ()
 
 
-extern "C" {
+@property(strong, nonatomic) GodotViewRenderer *renderer;
+@property(strong, nonatomic) GodotNativeVideoView *videoView;
 
 
-int add_path(int, char **);
-int add_cmdline(int, char **);
+@end
 
 
-int add_path(int p_argc, char **p_args) {
+@implementation ViewController
 
 
-	NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
-	if (!str) {
-		return p_argc;
-	}
+- (GodotView *)godotView {
+	return (GodotView *)self.view;
+}
 
 
-	p_args[p_argc++] = (char *)"--path";
-	p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
-	p_args[p_argc] = NULL;
+- (void)loadView {
+	GodotView *view = [[GodotView alloc] init];
+	[view initializeRendering];
 
 
-	return p_argc;
-};
+	GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
 
 
-int add_cmdline(int p_argc, char **p_args) {
+	self.renderer = renderer;
+	self.view = view;
 
 
-	NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
-	if (!arr) {
-		return p_argc;
-	}
+	view.renderer = self.renderer;
+}
 
 
-	for (NSUInteger i = 0; i < [arr count]; i++) {
-		NSString *str = [arr objectAtIndex:i];
-		if (!str) {
-			continue;
-		}
-		p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
-	}
+- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
+	self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
 
 
-	p_args[p_argc] = NULL;
+	if (self) {
+		[self godot_commonInit];
+	}
 
 
-	return p_argc;
-};
-}; // extern "C"
+	return self;
+}
 
 
-@interface ViewController ()
+- (instancetype)initWithCoder:(NSCoder *)coder {
+	self = [super initWithCoder:coder];
 
 
-@end
+	if (self) {
+		[self godot_commonInit];
+	}
 
 
-@implementation ViewController
+	return self;
+}
 
 
-- (GodotView *)godotView {
-	return (GodotView *)self.view;
+- (void)godot_commonInit {
+	// Initialize view controller values.
 }
 }
 
 
 - (void)didReceiveMemoryWarning {
 - (void)didReceiveMemoryWarning {
@@ -93,16 +93,48 @@ int add_cmdline(int p_argc, char **p_args) {
 - (void)viewDidLoad {
 - (void)viewDidLoad {
 	[super viewDidLoad];
 	[super viewDidLoad];
 
 
+	[self observeKeyboard];
+
 	if (@available(iOS 11.0, *)) {
 	if (@available(iOS 11.0, *)) {
 		[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
 		[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
 	}
 	}
 }
 }
 
 
+- (void)observeKeyboard {
+	printf("******** adding observer for keyboard show/hide\n");
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(keyboardOnScreen:)
+				   name:UIKeyboardDidShowNotification
+				 object:nil];
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(keyboardHidden:)
+				   name:UIKeyboardDidHideNotification
+				 object:nil];
+}
+
+- (void)dealloc {
+	[self.videoView stopVideo];
+	self.videoView = nil;
+
+	self.videoView = nil;
+	self.renderer = nil;
+
+	[[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+// MARK: Orientation
+
 - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
 - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
 	return UIRectEdgeAll;
 	return UIRectEdgeAll;
 }
 }
 
 
 - (BOOL)shouldAutorotate {
 - (BOOL)shouldAutorotate {
+	if (!OSIPhone::get_singleton()) {
+		return NO;
+	}
+
 	switch (OS::get_singleton()->get_screen_orientation()) {
 	switch (OS::get_singleton()->get_screen_orientation()) {
 		case OS::SCREEN_SENSOR:
 		case OS::SCREEN_SENSOR:
 		case OS::SCREEN_SENSOR_LANDSCAPE:
 		case OS::SCREEN_SENSOR_LANDSCAPE:
@@ -111,9 +143,13 @@ int add_cmdline(int p_argc, char **p_args) {
 		default:
 		default:
 			return NO;
 			return NO;
 	}
 	}
-};
+}
 
 
 - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
 - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
+	if (!OSIPhone::get_singleton()) {
+		return UIInterfaceOrientationMaskAll;
+	}
+
 	switch (OS::get_singleton()->get_screen_orientation()) {
 	switch (OS::get_singleton()->get_screen_orientation()) {
 		case OS::SCREEN_PORTRAIT:
 		case OS::SCREEN_PORTRAIT:
 			return UIInterfaceOrientationMaskPortrait;
 			return UIInterfaceOrientationMaskPortrait;
@@ -130,7 +166,7 @@ int add_cmdline(int p_argc, char **p_args) {
 		case OS::SCREEN_LANDSCAPE:
 		case OS::SCREEN_LANDSCAPE:
 			return UIInterfaceOrientationMaskLandscapeLeft;
 			return UIInterfaceOrientationMaskLandscapeLeft;
 	}
 	}
-};
+}
 
 
 - (BOOL)prefersStatusBarHidden {
 - (BOOL)prefersStatusBarHidden {
 	return YES;
 	return YES;
@@ -144,6 +180,41 @@ int add_cmdline(int p_argc, char **p_args) {
 	}
 	}
 }
 }
 
 
+// MARK: Keyboard
+
+- (void)keyboardOnScreen:(NSNotification *)notification {
+	NSDictionary *info = notification.userInfo;
+	NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
+
+	CGRect rawFrame = [value CGRectValue];
+	CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil];
+
+	if (OSIPhone::get_singleton()) {
+		OSIPhone::get_singleton()->set_virtual_keyboard_height(keyboardFrame.size.height);
+	}
+}
+
+- (void)keyboardHidden:(NSNotification *)notification {
+	if (OSIPhone::get_singleton()) {
+		OSIPhone::get_singleton()->set_virtual_keyboard_height(0);
+	}
+}
+
+// MARK: Native Video Player
+
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
+	// If we are showing some video already, reuse existing view for new video.
+	if (self.videoView) {
+		return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack];
+	} else {
+		// Create autoresizing view for video playback.
+		GodotNativeVideoView *videoView = [[GodotNativeVideoView alloc] initWithFrame:self.view.bounds];
+		videoView.autoresizingMask = UIViewAutoresizingFlexibleWidth & UIViewAutoresizingFlexibleHeight;
+		[self.view addSubview:videoView];
+		return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack];
+	}
+}
+
 #ifdef GAME_CENTER_ENABLED
 #ifdef GAME_CENTER_ENABLED
 - (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
 - (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
 	//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone
 	//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone