From 2bdfec24182d0be5cbc48ce15d1011a83fef2c05 Mon Sep 17 00:00:00 2001 From: Sergey Minakov Date: Mon, 5 Oct 2020 22:19:38 +0300 Subject: [PATCH 1/2] iOS: Refactor platform code Change project structure to be more like 4.0 Refactor and remove old code as followup after deprecations fix --- modules/arkit/arkit_interface.mm | 2 - modules/camera/camera_ios.mm | 9 +- platform/iphone/SCsub | 11 +- platform/iphone/app_delegate.h | 12 +- platform/iphone/app_delegate.mm | 589 +----------- platform/iphone/detect.py | 3 + platform/iphone/device_metrics.h | 37 + platform/iphone/device_metrics.m | 152 ++++ platform/iphone/display_layer.h | 45 + platform/iphone/display_layer.mm | 191 ++++ platform/iphone/game_center.mm | 15 +- .../{godot_iphone.cpp => godot_iphone.mm} | 50 +- platform/iphone/godot_view.h | 91 +- platform/iphone/godot_view.mm | 853 +++++++----------- platform/iphone/godot_view_renderer.h | 44 + platform/iphone/godot_view_renderer.mm | 117 +++ platform/iphone/icloud.mm | 8 - platform/iphone/in_app_store.mm | 31 +- platform/iphone/ios.mm | 19 +- platform/iphone/joypad_iphone.h | 50 + platform/iphone/joypad_iphone.mm | 342 +++++++ platform/iphone/native_video_view.h | 42 + platform/iphone/native_video_view.m | 265 ++++++ platform/iphone/os_iphone.h | 90 +- platform/iphone/os_iphone.mm | 444 +++++---- platform/iphone/view_controller.h | 11 +- platform/iphone/view_controller.mm | 147 ++- 27 files changed, 2117 insertions(+), 1553 deletions(-) create mode 100644 platform/iphone/device_metrics.h create mode 100644 platform/iphone/device_metrics.m create mode 100644 platform/iphone/display_layer.h create mode 100644 platform/iphone/display_layer.mm rename platform/iphone/{godot_iphone.cpp => godot_iphone.mm} (77%) create mode 100644 platform/iphone/godot_view_renderer.h create mode 100644 platform/iphone/godot_view_renderer.mm create mode 100644 platform/iphone/joypad_iphone.h create mode 100644 platform/iphone/joypad_iphone.mm create mode 100644 platform/iphone/native_video_view.h create mode 100644 platform/iphone/native_video_view.m diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm index 43d91ce278fb..2f6a450fa43b 100644 --- a/modules/arkit/arkit_interface.mm +++ b/modules/arkit/arkit_interface.mm @@ -469,10 +469,8 @@ if (@available(iOS 13, *)) { orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation; -#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR } else { orientation = [[UIApplication sharedApplication] statusBarOrientation]; -#endif } // Grab our camera image for our backbuffer diff --git a/modules/camera/camera_ios.mm b/modules/camera/camera_ios.mm index e73d911b8ea8..c37a3fcf8d61 100644 --- a/modules/camera/camera_ios.mm +++ b/modules/camera/camera_ios.mm @@ -158,7 +158,14 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM } else if (dataCbCr == NULL) { print_line("Couldn't access CbCr pixel buffer data"); } 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 img[2]; { diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index d4c483d058db..6d7f37f137fd 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -3,10 +3,9 @@ Import("env") iphone_lib = [ - "godot_iphone.cpp", - "os_iphone.mm", "semaphore_iphone.cpp", - "godot_view.mm", + "godot_iphone.mm", + "os_iphone.mm", "main.m", "app_delegate.mm", "view_controller.mm", @@ -14,7 +13,13 @@ iphone_lib = [ "in_app_store.mm", "icloud.mm", "ios.mm", + "joypad_iphone.mm", + "godot_view.mm", + "display_layer.mm", + "godot_view_renderer.mm", "godot_view_gesture_recognizer.mm", + "device_metrics.m", + "native_video_view.m", ] env_ios = env.Clone() diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h index 0082ca536732..71a830cb4dfc 100644 --- a/platform/iphone/app_delegate.h +++ b/platform/iphone/app_delegate.h @@ -28,19 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#import "godot_view.h" -#import "view_controller.h" #import -#import +@class ViewController; -@interface AppDelegate : NSObject { - //@property (strong, nonatomic) UIWindow *window; - ViewController *view_controller; - bool is_focus_out; -}; +@interface AppDelegate : NSObject -@property(strong, class, readonly, nonatomic) ViewController *viewController; @property(strong, nonatomic) UIWindow *window; +@property(strong, class, readonly, nonatomic) ViewController *viewController; @end diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index 7481b4fb7f35..774cc41bf5de 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -35,52 +35,19 @@ #import "godot_view.h" #include "main/main.h" #include "os_iphone.h" +#import "view_controller.h" -#import "GameController/GameController.h" #import -#define kFilteringFactor 0.1 #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 char **gargv; -extern int iphone_main(int, int, int, char **, String); + +extern int iphone_main(int, char **, String); extern void iphone_finish(); -CMMotionManager *motionManager; -bool motionInitialised; +@implementation AppDelegate static ViewController *mainViewController = nil; @@ -88,522 +55,34 @@ + (ViewController *)viewController { 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::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::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 { - CGRect rect = [[UIScreen mainScreen] bounds]; - - is_focus_out = false; - - // disable idle timer - // application.idleTimerDisabled = YES; - // 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, NSUserDomainMask, YES); 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) { // bail, things did not go very well for us, should probably output a message on screen with our error code... exit(0); return FALSE; - }; + } // 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 // 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 - [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] addObserver:self @@ -611,42 +90,39 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( name:AVAudioSessionInterruptionNotification 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 [[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; -}; +} - (void)onAudioInterruption:(NSNotification *)notification { if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) { if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) { 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]]) { 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(); -}; +} // When application goes to background (e.g. user switches to another app or presses Home), // then applicationWillResignActive -> applicationDidEnterBackground are called. @@ -659,15 +135,16 @@ - (void)applicationWillTerminate:(UIApplication *)application { // notification panel by swiping from the upper part of the screen. - (void)applicationWillResignActive:(UIApplication *)application { - on_focus_out(view_controller, &is_focus_out); + OSIPhone::get_singleton()->on_focus_out(); } - (void)applicationDidBecomeActive:(UIApplication *)application { - on_focus_in(view_controller, &is_focus_out); + OSIPhone::get_singleton()->on_focus_in(); } - (void)dealloc { - [window release]; + self.window = nil; + [super dealloc]; } diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index 5f1625351e44..bad185e09d02 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -138,6 +138,9 @@ def configure(env): else: 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 if env["arch"] == "x86" or env["arch"] == "x86_64": diff --git a/platform/iphone/device_metrics.h b/platform/iphone/device_metrics.h new file mode 100644 index 000000000000..6d0ff49077dd --- /dev/null +++ b/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 + +@interface GodotDeviceMetrics : NSObject + +@property(nonatomic, class, readonly, strong) NSDictionary *dpiList; + +@end diff --git a/platform/iphone/device_metrics.m b/platform/iphone/device_metrics.m new file mode 100644 index 000000000000..747872bc490d --- /dev/null +++ b/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 diff --git a/platform/iphone/display_layer.h b/platform/iphone/display_layer.h new file mode 100644 index 000000000000..4de48de9797e --- /dev/null +++ b/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 +#import + +@protocol DisplayLayer + +- (void)startRenderDisplayLayer; +- (void)stopRenderDisplayLayer; +- (void)initializeDisplayLayer; +- (void)layoutDisplayLayer; + +@end + +@interface GodotOpenGLLayer : CAEAGLLayer + +@end diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm new file mode 100644 index 000000000000..44baaf55110c --- /dev/null +++ b/platform/iphone/display_layer.mm @@ -0,0 +1,191 @@ +/*************************************************************************/ +/* 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 +#import +#import +#import +#import +#import +#import + +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; + } + + [super dealloc]; +} + +- (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)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 diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm index cd890ddf10fb..73c52f76e4cf 100644 --- a/platform/iphone/game_center.mm +++ b/platform/iphone/game_center.mm @@ -32,20 +32,9 @@ #include "game_center.h" -#ifdef __IPHONE_9_0 - -#import -extern "C" { - -#else - -extern "C" { -#import - -#endif - #import "app_delegate.h" -}; +#import "view_controller.h" +#import GameCenter *GameCenter::instance = NULL; diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.mm similarity index 77% rename from platform/iphone/godot_iphone.cpp rename to platform/iphone/godot_iphone.mm index 8c3eddc5f7d8..43c12b4ee9a5 100644 --- a/platform/iphone/godot_iphone.cpp +++ b/platform/iphone/godot_iphone.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* godot_iphone.cpp */ +/* godot_iphone.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -38,15 +38,39 @@ 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]); while (len--) { @@ -65,12 +89,12 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) { char cwd[512]; getcwd(cwd, sizeof(cwd)); printf("cwd %s\n", cwd); - os = new OSIPhone(width, height, data_dir); + os = new OSIPhone(data_dir); char *fargv[64]; for (int i = 0; i < argc; i++) { fargv[i] = argv[i]; - }; + } fargv[argc] = NULL; argc = add_path(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"); Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false); printf("setup %i\n", err); - if (err != OK) + if (err != OK) { return 255; + } return 0; -}; +} void iphone_finish() { - printf("iphone_finish\n"); Main::cleanup(); delete os; -}; +} diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h index 0831c2b23984..bfc1484c1cc4 100644 --- a/platform/iphone/godot_view.h +++ b/platform/iphone/godot_view.h @@ -35,93 +35,28 @@ #import #import -@protocol GodotViewDelegate; -@class GodotViewGestureRecognizer; +class String; -@interface GodotView : UIView { -@private - // The pixel dimensions of the backbuffer - GLint backingWidth; - GLint backingHeight; +@protocol DisplayLayer; +@protocol GodotViewRendererProtocol; - EAGLContext *context; +@interface GodotView : UIView - // OpenGL names for the renderbuffer and framebuffers used to render to this view - GLuint viewRenderbuffer, viewFramebuffer; +@property(assign, nonatomic) id 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 *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 *)initializeRendering; +- (void)stopRendering; +- (void)startRendering; - // Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer. - id 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 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; @end - -@protocol GodotViewDelegate - -@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 diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm index afff88481e51..18f47a49492d 100644 --- a/platform/iphone/godot_view.mm +++ b/platform/iphone/godot_view.mm @@ -29,7 +29,6 @@ /*************************************************************************/ #import "godot_view.h" -#import "godot_view_gesture_recognizer.h" #include "core/os/keyboard.h" #include "core/project_settings.h" @@ -39,699 +38,453 @@ #import #import -/* -@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 - 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 *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 *)initializeRendering { + if (self.renderingLayer) { + return self.renderingLayer; } - AVMediaSelectionGroup *subtitlesGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; - NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; + CALayer *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() { - for (int i = 0; i < max_touches; i++) { + return self; +} - touches[i] = NULL; - }; -}; +// Stop animating and release resources when they are no longer needed. +- (void)dealloc { + [self stopRendering]; -// 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]; -} + self.renderer = nil; -//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)delegate { - return delegate; -} + if (self.delayGestureRecognizer) { + self.delayGestureRecognizer = nil; + } -// Update the delegate, and if it needs a -setupView: call, set our internal flag so that it will be called. -- (void)setDelegate:(id)d { - delegate = d; - delegateSetup = ![delegate respondsToSelector:@selector(setupView:)]; + [super dealloc]; } -@synthesize useCADisplayLink; - -// 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. +- (void)godot_commonInit { + self.contentScaleFactor = [UIScreen mainScreen].nativeScale; -- (void)layoutSubviews { - [EAGLContext setCurrentContext:context]; - [self destroyFramebuffer]; - [self createFramebuffer]; - [self drawView]; -} + [self initTouches]; -- (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)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"); - if (useCADisplayLink) { + + if (self.useCADisplayLink) { + self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)]; // Approximate frame rate // 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 - [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; } 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; - 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 - (void)drawView { - - if (!active) { + if (!self.isActive) { printf("draw view not active!\n"); return; - }; - if (useCADisplayLink) { + } + + if (self.useCADisplayLink) { // Pause the CADisplayLink to avoid recursion - [displayLink setPaused:YES]; + [self.displayLink setPaused:YES]; // Process all input events while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) ; // 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 { - - NSArray *tlist = [[event allTouches] allObjects]; - for (unsigned int i = 0; i < [tlist count]; i++) { +- (void)setRenderingInterval:(NSTimeInterval)renderingInterval { + _renderingInterval = renderingInterval; - 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 { 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 { - if (keyboard_text.length()) + if (keyboard_text.length()) { keyboard_text.erase(keyboard_text.length() - 1, 1); + } OSIPhone::get_singleton()->key(KEY_BACKSPACE, true); -}; +} - (BOOL)hasText { - return keyboard_text.length() ? YES : NO; -}; + return keyboard_text.length() > 0; +} - (void)insertText:(NSString *)p_text { String character; character.parse_utf8([p_text UTF8String]); keyboard_text = keyboard_text + character; 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 diff --git a/platform/iphone/godot_view_renderer.h b/platform/iphone/godot_view_renderer.h new file mode 100644 index 000000000000..ea8998c80871 --- /dev/null +++ b/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 + +@protocol GodotViewRendererProtocol + +@property(assign, readonly, nonatomic) BOOL hasFinishedSetup; + +- (BOOL)setupView:(UIView *)view; +- (void)renderOnView:(UIView *)view; + +@end + +@interface GodotViewRenderer : NSObject + +@end diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm new file mode 100644 index 000000000000..8350aa5989e2 --- /dev/null +++ b/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 +#import +#import +#import +#import + +@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 diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm index a74c627e3bae..2b22378143f5 100644 --- a/platform/iphone/icloud.mm +++ b/platform/iphone/icloud.mm @@ -32,18 +32,10 @@ #include "icloud.h" -#ifndef __IPHONE_9_0 -extern "C" { -#endif - #import "app_delegate.h" #import -#ifndef __IPHONE_9_0 -}; -#endif - ICloud *ICloud::instance = NULL; void ICloud::_bind_methods() { diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm index 386ac4dd666d..40561396f974 100644 --- a/platform/iphone/in_app_store.mm +++ b/platform/iphone/in_app_store.mm @@ -32,10 +32,8 @@ #include "in_app_store.h" -extern "C" { #import #import -}; bool auto_finish_transactions = true; NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary]; @@ -189,32 +187,17 @@ - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)tran ret["transaction_id"] = transactionId; 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]; + int sdk_version = [[[UIDevice currentDevice] systemVersion] intValue]; - // Read in the contents of the transaction file. - receipt = [NSData dataWithContentsOfURL:receiptFileURL]; - sdk_version = 7; + NSBundle *bundle = [NSBundle mainBundle]; + // Get the transaction receipt file path location in the app bundle. + NSURL *receiptFileURL = [bundle appStoreReceiptURL]; - } else { - // Fall back to deprecated transaction receipt, - // which is still available in iOS 7. - - // 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; + if (receipt != nil) { receipt_to_send = [receipt base64EncodedStringWithOptions:0]; } diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index 7cd5e3e48146..d21455bbebaa 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -71,25 +71,12 @@ } 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()); + return ret; }; diff --git a/platform/iphone/joypad_iphone.h b/platform/iphone/joypad_iphone.h new file mode 100644 index 000000000000..85e26e1dc8da --- /dev/null +++ b/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 + +@interface JoypadIPhoneObserver : NSObject + +- (void)startObserving; +- (void)startProcessing; +- (void)finishObserving; + +@end + +class JoypadIPhone { +private: + JoypadIPhoneObserver *observer; + +public: + JoypadIPhone(); + ~JoypadIPhone(); + + void start_processing(); +}; diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm new file mode 100644 index 000000000000..6bce377c994c --- /dev/null +++ b/platform/iphone/joypad_iphone.mm @@ -0,0 +1,342 @@ +/*************************************************************************/ +/* 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]; + + [super dealloc]; +} + +- (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... + 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 +}; + +@end diff --git a/platform/iphone/native_video_view.h b/platform/iphone/native_video_view.h new file mode 100644 index 000000000000..b15e0f8dd0f4 --- /dev/null +++ b/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 + +@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 diff --git a/platform/iphone/native_video_view.m b/platform/iphone/native_video_view.m new file mode 100644 index 000000000000..de449b247fe6 --- /dev/null +++ b/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 + +@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 diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 73ba6a6d202a..79f72c6bb2e3 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -36,6 +36,7 @@ #include "core/os/input.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" #include "drivers/unix/os_unix.h" +#include "joypad_iphone.h" #include "game_center.h" #include "icloud.h" @@ -49,11 +50,6 @@ class OSIPhone : public OS_Unix { private: - enum { - MAX_MOUSE_COUNT = 8, - MAX_EVENTS = 64, - }; - static HashMap dynamic_symbol_lookup_table; friend void register_dynamic_symbol(char *name, void *address); @@ -72,6 +68,8 @@ class OSIPhone : public OS_Unix { #endif iOS *ios; + JoypadIPhone *joypad_iphone; + MainLoop *main_loop; VideoMode video_mode; @@ -91,39 +89,56 @@ class OSIPhone : public OS_Unix { 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 &p_event); - Vector3 last_accel; - - Ref event_queue[MAX_EVENTS]; - int event_count; - void queue_event(const Ref &p_event); + void set_data_dir(String p_dir); String data_dir; InputDefault *input; - int virtual_keyboard_height; + int virtual_keyboard_height = 0; int video_driver_index; + bool is_focused = false; + public: + static OSIPhone *get_singleton(); + + OSIPhone(String p_data_dir); + ~OSIPhone(); + 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_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 set_virtual_keyboard_height(int p_height); @@ -139,23 +154,17 @@ class OSIPhone : public OS_Unix { 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); - static OSIPhone *get_singleton(); - virtual void set_mouse_show(bool p_show); virtual void set_mouse_grab(bool p_grab); virtual bool is_mouse_grab_enabled() const; virtual Point2 get_mouse_position() 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 VideoMode get_video_mode(int p_screen = 0) const; + virtual void get_fullscreen_mode_list(List *p_list, int p_screen = 0) const; virtual void set_keep_screen_on(bool p_enabled); @@ -172,30 +181,15 @@ class OSIPhone : public OS_Unix { 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 bool native_video_is_playing() const; virtual void native_video_pause(); virtual void native_video_unpause(); virtual void native_video_focus_out(); 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 diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index 0ab81748fd0f..ecb52ba3a3bc 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -30,11 +30,6 @@ #ifdef IPHONE_ENABLED -// System headers are at top -// to workaround `ambiguous expansion` warning/error -#import -#include - #include "os_iphone.h" #include "drivers/gles2/rasterizer_gles2.h" @@ -52,13 +47,33 @@ #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 +#include +#import + +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 OSIPhone::dynamic_symbol_lookup_table; +int OSIPhone::get_video_driver_count() const { return 2; }; const char *OSIPhone::get_video_driver_name(int p_driver) const { - switch (p_driver) { case VIDEO_DRIVER_GLES3: return "GLES3"; @@ -69,14 +84,10 @@ }; OSIPhone *OSIPhone::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) { - DirAccess *da = DirAccess::open(p_dir); data_dir = da->get_current_dir(); @@ -101,7 +112,13 @@ 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) { @@ -153,10 +170,11 @@ //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 - if (use_gl3) + if (use_gl3) { RasterizerStorageGLES3::system_fbo = gl_view_base_fb; - else + } else { RasterizerStorageGLES2::system_fbo = gl_view_base_fb; + } AudioDriverManager::initialize(p_audio_driver); @@ -180,6 +198,8 @@ ios = memnew(iOS); Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios)); + joypad_iphone = memnew(JoypadIPhone); + return OK; }; @@ -199,78 +219,57 @@ }; bool OSIPhone::iterate() { - - if (!main_loop) + if (!main_loop) { 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(); }; void OSIPhone::key(uint32_t p_key, bool p_pressed) { - Ref ev; ev.instance(); ev->set_echo(false); ev->set_pressed(p_pressed); ev->set_scancode(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) { + if (GLOBAL_DEF("debug/disable_touch", false)) { + return; + } - if (!GLOBAL_DEF("debug/disable_touch", false)) { - Ref 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 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) { + if (GLOBAL_DEF("debug/disable_touch", false)) { + return; + } - if (!GLOBAL_DEF("debug/disable_touch", false)) { - - Ref 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 &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 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 &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; @@ -282,39 +281,6 @@ // 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)); - - /* - 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) { @@ -355,59 +321,76 @@ 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 - memdelete(game_center); + if (game_center) { + memdelete(game_center); + } #endif #ifdef STOREKIT_ENABLED - memdelete(store_kit); + if (store_kit) { + memdelete(store_kit); + } #endif #ifdef ICLOUD_ENABLED - memdelete(icloud); + if (icloud) { + memdelete(icloud); + } #endif visual_server->finish(); memdelete(visual_server); // 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 { - + // Not supported for iOS return true; -}; +} Point2 OSIPhone::get_mouse_position() const { - + // Not supported for iOS return Point2(); -}; +} int OSIPhone::get_mouse_button_state() const { - + // Not supported for iOS 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) { - const CharString utf8_alert = p_alert.utf8(); const CharString utf8_title = p_title.utf8(); 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) { if (p_path.length() == 0) { p_library_handle = RTLD_SELF; @@ -423,7 +406,6 @@ return OS_Unix::close_dynamic_library(p_library_handle); } -HashMap OSIPhone::dynamic_symbol_lookup_table; void register_dynamic_symbol(char *name, void *address) { OSIPhone::dynamic_symbol_lookup_table[String(name)] = address; } @@ -440,55 +422,46 @@ void register_dynamic_symbol(char *name, void *address) { } void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) { - video_mode = p_video_mode; -}; +} OS::VideoMode OSIPhone::get_video_mode(int p_screen) const { return video_mode; -}; +} void OSIPhone::get_fullscreen_mode_list(List *p_list, int p_screen) const { - p_list->push_back(video_mode); -}; +} bool OSIPhone::can_draw() const { if (native_video_is_playing()) return false; return true; -}; +} int OSIPhone::set_base_framebuffer(int p_fb) { - // gl_view_base_fb has not been updated yet RasterizerStorageGLES3::system_fbo = p_fb; return 0; -}; +} bool OSIPhone::has_virtual_keyboard() const { 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) { - _show_keyboard(p_existing_text); + [AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text]; }; void OSIPhone::hide_virtual_keyboard() { - _hide_keyboard(); -}; + [AppDelegate.viewController.godotView resignFirstResponder]; +} 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 { @@ -496,46 +469,103 @@ void register_dynamic_symbol(char *name, void *address) { } 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) { 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 { - return data_dir; -}; +} String OSIPhone::get_name() const { - return "iOS"; -}; +} String OSIPhone::get_model_name() const { - String model = ios->get_model(); - if (model != "") + if (model != "") { return model; + } return OS_Unix::get_model_name(); } Size2 OSIPhone::get_window_size() const { - 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 { - 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 { - return true; } @@ -550,79 +580,82 @@ void register_dynamic_symbol(char *name, void *address) { 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) { FileAccess *f = FileAccess::open(p_path, FileAccess::READ); 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; + } + + String tempFile = OSIPhone::get_singleton()->get_user_data_dir(); if (p_path.begins_with("res://")) { 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; } else { 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); - 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 FAILED; } bool OSIPhone::native_video_is_playing() const { - return _is_video_playing(); + return [AppDelegate.viewController.videoView isVideoPlaying]; } 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() { - _unpause_video(); -}; + [AppDelegate.viewController.videoView unpauseVideo]; +} void OSIPhone::native_video_focus_out() { - _focus_out_video(); -}; + [AppDelegate.viewController.videoView unfocusVideo]; +} 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) { // iOS does not support duration for vibration - _vibrate(); + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); } bool OSIPhone::_check_internal_feature_support(const String &p_feature) { - 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) { if (ios_init_callbacks_count == ios_init_callbacks_capacity) { 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) { ios_init_callbacks[i](); } @@ -649,15 +682,6 @@ void add_ios_init_callback(init_callback cb) { main_loop = 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 // which is initialized in initialize_core data_dir = p_data_dir; @@ -676,4 +700,40 @@ void add_ios_init_callback(init_callback cb) { 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 diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h index 23e970d27a6f..b0b31ae377c7 100644 --- a/platform/iphone/view_controller.h +++ b/platform/iphone/view_controller.h @@ -32,10 +32,15 @@ #import @class GodotView; +@class GodotNativeVideoView; -@interface ViewController : UIViewController { -}; +@interface ViewController : UIViewController -- (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 diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index c406ec5e2baa..33d2996a3221 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -30,59 +30,59 @@ #import "view_controller.h" +#include "core/project_settings.h" #import "godot_view.h" +#import "godot_view_renderer.h" +#import "native_video_view.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 { @@ -93,16 +93,50 @@ - (void)didReceiveMemoryWarning { - (void)viewDidLoad { [super viewDidLoad]; + [self observeKeyboard]; + if (@available(iOS 11.0, *)) { [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]; + + [super dealloc]; +} + +// MARK: Orientation + - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { return UIRectEdgeAll; } - (BOOL)shouldAutorotate { + if (!OSIPhone::get_singleton()) { + return NO; + } + switch (OS::get_singleton()->get_screen_orientation()) { case OS::SCREEN_SENSOR: case OS::SCREEN_SENSOR_LANDSCAPE: @@ -111,9 +145,13 @@ - (BOOL)shouldAutorotate { default: return NO; } -}; +} - (UIInterfaceOrientationMask)supportedInterfaceOrientations { + if (!OSIPhone::get_singleton()) { + return UIInterfaceOrientationMaskAll; + } + switch (OS::get_singleton()->get_screen_orientation()) { case OS::SCREEN_PORTRAIT: return UIInterfaceOrientationMaskPortrait; @@ -130,7 +168,7 @@ - (UIInterfaceOrientationMask)supportedInterfaceOrientations { case OS::SCREEN_LANDSCAPE: return UIInterfaceOrientationMaskLandscapeLeft; } -}; +} - (BOOL)prefersStatusBarHidden { return YES; @@ -144,6 +182,41 @@ - (BOOL)prefersHomeIndicatorAutoHidden { } } +// 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 - (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController { //[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone From 5d1284204de3c261205ff94a0147908644a5a300 Mon Sep 17 00:00:00 2001 From: Sergey Minakov Date: Mon, 5 Oct 2020 23:26:43 +0300 Subject: [PATCH 2/2] iOS: port ARC support --- modules/arkit/arkit_interface.h | 10 +++++-- modules/arkit/arkit_interface.mm | 8 ++--- modules/camera/camera_ios.mm | 30 ++++++------------- modules/mono/mono_gd/support/ios_support.mm | 3 +- platform/iphone/app_delegate.mm | 2 -- platform/iphone/detect.py | 6 ++-- platform/iphone/display_layer.mm | 2 -- platform/iphone/game_center.mm | 15 ++++++---- platform/iphone/godot_view.mm | 2 -- .../iphone/godot_view_gesture_recognizer.mm | 5 ---- platform/iphone/icloud.mm | 14 ++++----- platform/iphone/in_app_store.mm | 13 ++++---- platform/iphone/ios.mm | 1 + platform/iphone/joypad_iphone.mm | 14 +++++++-- platform/iphone/platform_config.h | 7 +++++ platform/iphone/view_controller.mm | 2 -- 16 files changed, 66 insertions(+), 68 deletions(-) diff --git a/modules/arkit/arkit_interface.h b/modules/arkit/arkit_interface.h index cb1835040946..cd9756e5e720 100644 --- a/modules/arkit/arkit_interface.h +++ b/modules/arkit/arkit_interface.h @@ -44,6 +44,12 @@ // forward declaration for some needed objects class ARKitShader; +#ifdef __OBJC__ +typedef NSObject GodotARAnchor; +#else +typedef void GodotARAnchor; +#endif + class ARKitInterface : public ARVRInterface { GDCLASS(ARKitInterface, ARVRInterface); @@ -115,8 +121,8 @@ class ARKitInterface : public ARVRInterface { 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) - 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(); diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm index 2f6a450fa43b..c7cfb02d23b9 100644 --- a/modules/arkit/arkit_interface.mm +++ b/modules/arkit/arkit_interface.mm @@ -305,10 +305,8 @@ remove_all_anchors(); if (@available(iOS 11.0, *)) { - [ar_session release]; - ar_session = NULL; + ar_session = nil; } - [ar_delegate release]; ar_delegate = NULL; initialized = false; @@ -684,7 +682,7 @@ } } -void ARKitInterface::_add_or_update_anchor(void *p_anchor) { +void ARKitInterface::_add_or_update_anchor(GodotARAnchor *p_anchor) { _THREAD_SAFE_METHOD_ if (@available(iOS 11.0, *)) { @@ -746,7 +744,7 @@ } } -void ARKitInterface::_remove_anchor(void *p_anchor) { +void ARKitInterface::_remove_anchor(GodotARAnchor *p_anchor) { _THREAD_SAFE_METHOD_ if (@available(iOS 11.0, *)) { diff --git a/modules/camera/camera_ios.mm b/modules/camera/camera_ios.mm index c37a3fcf8d61..255a599dd4a4 100644 --- a/modules/camera/camera_ios.mm +++ b/modules/camera/camera_ios.mm @@ -124,18 +124,12 @@ - (void)cleanup { if (output) { [self removeOutput:output]; [output setSampleBufferDelegate:nil queue:NULL]; - [output release]; output = nil; } [self commitConfiguration]; } -- (void)dealloc { - // bye bye - [super dealloc]; -} - - (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. // May need to investigate in a way to throttle this if we get more images then we're rendering frames.. @@ -270,7 +264,6 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM void CameraFeedIOS::set_device(AVCaptureDevice *p_device) { device = p_device; - [device retain]; // get some info NSString *device_name = p_device.localizedName; @@ -284,15 +277,13 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM }; 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() { @@ -310,9 +301,8 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM // end camera capture if we have one if (capture_session) { [capture_session cleanup]; - [capture_session release]; - capture_session = NULL; - }; + capture_session = nil; + } }; ////////////////////////////////////////////////////////////////////////// @@ -345,8 +335,6 @@ - (void)dealloc { // remove notifications [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil]; - - [super dealloc]; } @end @@ -449,5 +437,5 @@ - (void)dealloc { }; CameraIOS::~CameraIOS() { - [device_notifications release]; + device_notifications = nil; }; diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm index e3d1a647fd28..dc23c06ebaaf 100755 --- a/modules/mono/mono_gd/support/ios_support.mm +++ b/modules/mono/mono_gd/support/ios_support.mm @@ -131,8 +131,7 @@ GD_PINVOKE_EXPORT void xamarin_log(const uint16_t *p_unicode_message) { NSTimeZone *tz = nil; if (p_name) { NSString *n = [[NSString alloc] initWithUTF8String:p_name]; - tz = [[[NSTimeZone alloc] initWithName:n] autorelease]; - [n release]; + tz = [[NSTimeZone alloc] initWithName:n]; } else { tz = [NSTimeZone localTimeZone]; } diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index 774cc41bf5de..f87d90198974 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -144,8 +144,6 @@ - (void)applicationDidBecomeActive:(UIApplication *)application { - (void)dealloc { self.window = nil; - - [super dealloc]; } @end diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index bad185e09d02..813fcba28eb3 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -115,18 +115,18 @@ def configure(env): CCFLAGS=( "-arch " + 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() ) elif env["arch"] == "arm": detect_darwin_sdk_path("iphone", env) 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": detect_darwin_sdk_path("iphone", env) 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=["LIBYUV_DISABLE_NEON"]) diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm index 44baaf55110c..d60003ba318f 100644 --- a/platform/iphone/display_layer.mm +++ b/platform/iphone/display_layer.mm @@ -130,8 +130,6 @@ - (void)dealloc { if (context) { context = nil; } - - [super dealloc]; } - (BOOL)createFramebuffer { diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm index 73c52f76e4cf..ae223d914a20 100644 --- a/platform/iphone/game_center.mm +++ b/platform/iphone/game_center.mm @@ -71,7 +71,12 @@ // 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 // 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) { + _strongify(root_controller); + _strongify(player); + if (controller) { [root_controller presentViewController:controller animated:YES completion:nil]; } else { @@ -106,8 +111,8 @@ float score = params["score"]; 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; ERR_FAIL_COND_V([GKScore respondsToSelector:@selector(reportScores)], ERR_UNAVAILABLE); @@ -137,8 +142,8 @@ String name = params["name"]; 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([GKAchievement respondsToSelector:@selector(reportAchievements)], ERR_UNAVAILABLE); @@ -302,7 +307,7 @@ controller.leaderboardIdentifier = nil; if (params.has("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; } } diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm index 18f47a49492d..d2f7bebdd195 100644 --- a/platform/iphone/godot_view.mm +++ b/platform/iphone/godot_view.mm @@ -139,8 +139,6 @@ - (void)dealloc { if (self.delayGestureRecognizer) { self.delayGestureRecognizer = nil; } - - [super dealloc]; } - (void)godot_commonInit { diff --git a/platform/iphone/godot_view_gesture_recognizer.mm b/platform/iphone/godot_view_gesture_recognizer.mm index 1c9968f02092..a5969a0e1041 100644 --- a/platform/iphone/godot_view_gesture_recognizer.mm +++ b/platform/iphone/godot_view_gesture_recognizer.mm @@ -75,7 +75,6 @@ - (void)fireDelayedTouches:(id)timer { [self.view touchesBegan:delayedTouches withEvent:delayedEvent]; } - [delayedTouches release]; delayedTouches = nil; delayedEvent = nil; } @@ -103,16 +102,13 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (distance > kGLGestureMovementDistance) { [delayTimer fire]; [self.view touchesMoved:cleared withEvent:event]; - [cleared release]; return; } } - [cleared release]; return; } [self.view touchesMoved:cleared withEvent:event]; - [cleared release]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { @@ -120,7 +116,6 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded]; [self.view touchesEnded:cleared withEvent:event]; - [cleared release]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm index 2b22378143f5..a19966bf7e77 100644 --- a/platform/iphone/icloud.mm +++ b/platform/iphone/icloud.mm @@ -142,7 +142,7 @@ Variant nsobject_to_variant(NSObject *object) { NSObject *variant_to_nsobject(Variant v) { 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) { return [NSNumber numberWithDouble:(double)v]; } else if (v.get_type() == Variant::INT) { @@ -150,11 +150,11 @@ Variant nsobject_to_variant(NSObject *object) { } else if (v.get_type() == Variant::BOOL) { return [NSNumber numberWithBool:BOOL((bool)v)]; } else if (v.get_type() == Variant::DICTIONARY) { - NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease]; + NSMutableDictionary *result = [[NSMutableDictionary alloc] init]; Dictionary dic = v; Array keys = dic.keys(); 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]]); if (key == NULL || value == NULL) { @@ -165,7 +165,7 @@ Variant nsobject_to_variant(NSObject *object) { } return result; } else if (v.get_type() == Variant::ARRAY) { - NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease]; + NSMutableArray *result = [[NSMutableArray alloc] init]; Array arr = v; for (int i = 0; i < arr.size(); ++i) { NSObject *value = variant_to_nsobject(arr[i]); @@ -188,7 +188,7 @@ Variant nsobject_to_variant(NSObject *object) { Error ICloud::remove_key(Variant 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]; @@ -211,7 +211,7 @@ Variant nsobject_to_variant(NSObject *object) { String variant_key = keys[i]; 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) { error_keys.push_back(variant_key); continue; @@ -234,7 +234,7 @@ Variant nsobject_to_variant(NSObject *object) { Variant ICloud::get_key_value(Variant 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]; if (![[store dictionaryRepresentation] objectForKey:key]) { diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm index 40561396f974..7fe2b178bd02 100644 --- a/platform/iphone/in_app_store.mm +++ b/platform/iphone/in_app_store.mm @@ -53,7 +53,6 @@ - (NSString *)localizedPrice { [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; [numberFormatter setLocale:self.priceLocale]; NSString *formattedString = [numberFormatter stringFromNumber:self.price]; - [numberFormatter release]; return formattedString; } @end @@ -123,8 +122,6 @@ - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProdu ret["invalid_ids"] = invalid_ids; InAppStore::get_singleton()->_post_event(ret); - - [request release]; }; @end @@ -137,14 +134,14 @@ - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProdu PoolStringArray pids = params["product_ids"]; 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++) { 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]; }; - NSSet *products = [[[NSSet alloc] initWithArray:array] autorelease]; + NSSet *products = [[NSSet alloc] initWithArray:array]; SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:products]; ProductsDelegate *delegate = [[ProductsDelegate alloc] init]; @@ -256,7 +253,7 @@ - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)tran Dictionary params = p_params; 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; @@ -301,7 +298,7 @@ - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)tran void InAppStore::_record_purchase(String 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] synchronize]; }; diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index d21455bbebaa..32efde877484 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -31,6 +31,7 @@ #include "ios.h" #import "app_delegate.h" +#import "view_controller.h" #import #include diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm index 6bce377c994c..a7d9dd505a57 100644 --- a/platform/iphone/joypad_iphone.mm +++ b/platform/iphone/joypad_iphone.mm @@ -130,8 +130,6 @@ - (void)finishObserving { - (void)dealloc { [self finishObserving]; - - [super dealloc]; } - (int)getJoyIdForController:(GCController *)controller { @@ -250,7 +248,13 @@ - (void)setControllerInputHandler:(GCController *)controller { // 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) { @@ -310,7 +314,13 @@ - (void)setControllerInputHandler:(GCController *)controller { }; } 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) { diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h index d39c64eed600..c7dc5729122c 100644 --- a/platform/iphone/platform_config.h +++ b/platform/iphone/platform_config.h @@ -36,3 +36,10 @@ #define PLATFORM_REFCOUNT #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") diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index 33d2996a3221..830bc394058a 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -122,8 +122,6 @@ - (void)dealloc { self.renderer = nil; [[NSNotificationCenter defaultCenter] removeObserver:self]; - - [super dealloc]; } // MARK: Orientation