Rename OSX to macOS and iPhoneOS to iOS.
This commit is contained in:
parent
292c952e3b
commit
8823eae328
245 changed files with 1151 additions and 1149 deletions
43
platform/ios/SCsub
Normal file
43
platform/ios/SCsub
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
|
||||
ios_lib = [
|
||||
"godot_ios.mm",
|
||||
"os_ios.mm",
|
||||
"main.m",
|
||||
"app_delegate.mm",
|
||||
"view_controller.mm",
|
||||
"ios.mm",
|
||||
"vulkan_context_ios.mm",
|
||||
"display_server_ios.mm",
|
||||
"joypad_ios.mm",
|
||||
"godot_view.mm",
|
||||
"tts_ios.mm",
|
||||
"display_layer.mm",
|
||||
"godot_app_delegate.m",
|
||||
"godot_view_renderer.mm",
|
||||
"godot_view_gesture_recognizer.mm",
|
||||
"device_metrics.m",
|
||||
"keyboard_input_view.mm",
|
||||
]
|
||||
|
||||
env_ios = env.Clone()
|
||||
ios_lib = env_ios.add_library("ios", ios_lib)
|
||||
|
||||
# (iOS) Enable module support
|
||||
env_ios.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
|
||||
|
||||
|
||||
def combine_libs(target=None, source=None, env=None):
|
||||
lib_path = target[0].srcnode().abspath
|
||||
if "osxcross" in env:
|
||||
libtool = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}libtool"
|
||||
else:
|
||||
libtool = "$IOS_TOOLCHAIN_PATH/usr/bin/libtool"
|
||||
env.Execute(
|
||||
libtool + ' -static -o "' + lib_path + '" ' + " ".join([('"' + lib.srcnode().abspath + '"') for lib in source])
|
||||
)
|
||||
|
||||
|
||||
combine_command = env_ios.Command("#bin/libgodot" + env_ios["LIBSUFFIX"], [ios_lib] + env_ios["LIBS"], combine_libs)
|
||||
48
platform/ios/api/api.cpp
Normal file
48
platform/ios/api/api.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*************************************************************************/
|
||||
/* api.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "api.h"
|
||||
|
||||
#if defined(IOS_ENABLED)
|
||||
|
||||
void register_ios_api() {
|
||||
godot_ios_plugins_initialize();
|
||||
}
|
||||
|
||||
void unregister_ios_api() {
|
||||
godot_ios_plugins_deinitialize();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void register_ios_api() {}
|
||||
void unregister_ios_api() {}
|
||||
|
||||
#endif
|
||||
42
platform/ios/api/api.h
Normal file
42
platform/ios/api/api.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*************************************************************************/
|
||||
/* api.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef IOS_API_H
|
||||
#define IOS_API_H
|
||||
|
||||
#if defined(IOS_ENABLED)
|
||||
extern void godot_ios_plugins_initialize();
|
||||
extern void godot_ios_plugins_deinitialize();
|
||||
#endif
|
||||
|
||||
void register_ios_api();
|
||||
void unregister_ios_api();
|
||||
|
||||
#endif // IOS_API_H
|
||||
47
platform/ios/app_delegate.h
Normal file
47
platform/ios/app_delegate.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/*************************************************************************/
|
||||
/* app_delegate.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class ViewController;
|
||||
|
||||
// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented again,
|
||||
// so it can't be done with compilation time branching.
|
||||
//#if defined(GLES3_ENABLED)
|
||||
//@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {
|
||||
//#endif
|
||||
//#if defined(VULKAN_ENABLED)
|
||||
@interface AppDelegate : NSObject <UIApplicationDelegate>
|
||||
//#endif
|
||||
|
||||
@property(strong, nonatomic) UIWindow *window;
|
||||
@property(strong, class, readonly, nonatomic) ViewController *viewController;
|
||||
|
||||
@end
|
||||
149
platform/ios/app_delegate.mm
Normal file
149
platform/ios/app_delegate.mm
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/*************************************************************************/
|
||||
/* app_delegate.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 "app_delegate.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "drivers/coreaudio/audio_driver_coreaudio.h"
|
||||
#import "godot_view.h"
|
||||
#include "main/main.h"
|
||||
#include "os_ios.h"
|
||||
#import "view_controller.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AudioToolbox/AudioServices.h>
|
||||
|
||||
#define kRenderingFrequency 60
|
||||
|
||||
extern int gargc;
|
||||
extern char **gargv;
|
||||
|
||||
extern int ios_main(int, char **, String, String);
|
||||
extern void ios_finish();
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
static ViewController *mainViewController = nil;
|
||||
|
||||
+ (ViewController *)viewController {
|
||||
return mainViewController;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
// TODO: might be required to make an early return, so app wouldn't crash because of timeout.
|
||||
// TODO: logo screen is not displayed while shaders are compiling
|
||||
// DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController
|
||||
|
||||
CGRect windowBounds = [[UIScreen mainScreen] bounds];
|
||||
|
||||
// Create a full-screen window
|
||||
self.window = [[UIWindow alloc] initWithFrame:windowBounds];
|
||||
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||
NSString *documentsDirectory = [paths objectAtIndex:0];
|
||||
paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||
NSString *cacheDirectory = [paths objectAtIndex:0];
|
||||
|
||||
int err = ios_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]), String::utf8([cacheDirectory 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 NO;
|
||||
}
|
||||
|
||||
ViewController *viewController = [[ViewController alloc] init];
|
||||
viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
|
||||
viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
|
||||
|
||||
self.window.rootViewController = viewController;
|
||||
|
||||
// Show the window
|
||||
[self.window makeKeyAndVisible];
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(onAudioInterruption:)
|
||||
name:AVAudioSessionInterruptionNotification
|
||||
object:[AVAudioSession sharedInstance]];
|
||||
|
||||
mainViewController = viewController;
|
||||
|
||||
// prevent to stop music in another background app
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)onAudioInterruption:(NSNotification *)notification {
|
||||
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
|
||||
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
|
||||
NSLog(@"Audio interruption began");
|
||||
OS_IOS::get_singleton()->on_focus_out();
|
||||
} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
|
||||
NSLog(@"Audio interruption ended");
|
||||
OS_IOS::get_singleton()->on_focus_in();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (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 {
|
||||
ios_finish();
|
||||
}
|
||||
|
||||
// When application goes to background (e.g. user switches to another app or presses Home),
|
||||
// then applicationWillResignActive -> applicationDidEnterBackground are called.
|
||||
// When user opens the inactive app again,
|
||||
// applicationWillEnterForeground -> applicationDidBecomeActive are called.
|
||||
|
||||
// There are cases when applicationWillResignActive -> applicationDidBecomeActive
|
||||
// sequence is called without the app going to background. For example, that happens
|
||||
// if you open the app list without switching to another app or open/close the
|
||||
// notification panel by swiping from the upper part of the screen.
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
OS_IOS::get_singleton()->on_focus_out();
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
OS_IOS::get_singleton()->on_focus_in();
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
self.window = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
152
platform/ios/detect.py
Normal file
152
platform/ios/detect.py
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import os
|
||||
import sys
|
||||
from methods import detect_darwin_sdk_path
|
||||
|
||||
|
||||
def is_active():
|
||||
return True
|
||||
|
||||
|
||||
def get_name():
|
||||
return "iOS"
|
||||
|
||||
|
||||
def can_build():
|
||||
if sys.platform == "darwin" or ("OSXCROSS_IOS" in os.environ):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_opts():
|
||||
from SCons.Variables import BoolVariable
|
||||
|
||||
return [
|
||||
(
|
||||
"IOS_TOOLCHAIN_PATH",
|
||||
"Path to iOS toolchain",
|
||||
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain",
|
||||
),
|
||||
("IOS_SDK_PATH", "Path to the iOS SDK", ""),
|
||||
BoolVariable("ios_simulator", "Build for iOS Simulator", False),
|
||||
BoolVariable("ios_exceptions", "Enable exceptions", False),
|
||||
("ios_triple", "Triple for ios toolchain", ""),
|
||||
]
|
||||
|
||||
|
||||
def get_flags():
|
||||
return [
|
||||
("tools", False),
|
||||
("use_volk", False),
|
||||
]
|
||||
|
||||
|
||||
def configure(env):
|
||||
## Build type
|
||||
|
||||
if env["target"].startswith("release"):
|
||||
env.Append(CPPDEFINES=["NDEBUG", ("NS_BLOCK_ASSERTIONS", 1)])
|
||||
if env["optimize"] == "speed": # optimize for speed (default)
|
||||
# `-O2` is more friendly to debuggers than `-O3`, leading to better crash backtraces
|
||||
# when using `target=release_debug`.
|
||||
opt = "-O3" if env["target"] == "release" else "-O2"
|
||||
env.Append(CCFLAGS=[opt, "-ftree-vectorize", "-fomit-frame-pointer"])
|
||||
env.Append(LINKFLAGS=[opt])
|
||||
elif env["optimize"] == "size": # optimize for size
|
||||
env.Append(CCFLAGS=["-Os", "-ftree-vectorize"])
|
||||
env.Append(LINKFLAGS=["-Os"])
|
||||
|
||||
elif env["target"] == "debug":
|
||||
env.Append(CCFLAGS=["-gdwarf-2", "-O0"])
|
||||
env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1)])
|
||||
|
||||
if env["use_lto"]:
|
||||
env.Append(CCFLAGS=["-flto"])
|
||||
env.Append(LINKFLAGS=["-flto"])
|
||||
|
||||
## Architecture
|
||||
env["bits"] = "64"
|
||||
if env["arch"] != "x86_64":
|
||||
env["arch"] = "arm64"
|
||||
|
||||
## Compiler configuration
|
||||
|
||||
# Save this in environment for use by other modules
|
||||
if "OSXCROSS_IOS" in os.environ:
|
||||
env["osxcross"] = True
|
||||
|
||||
env["ENV"]["PATH"] = env["IOS_TOOLCHAIN_PATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"]
|
||||
|
||||
compiler_path = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}"
|
||||
s_compiler_path = "$IOS_TOOLCHAIN_PATH/Developer/usr/bin/"
|
||||
|
||||
ccache_path = os.environ.get("CCACHE")
|
||||
if ccache_path is None:
|
||||
env["CC"] = compiler_path + "clang"
|
||||
env["CXX"] = compiler_path + "clang++"
|
||||
env["S_compiler"] = s_compiler_path + "gcc"
|
||||
else:
|
||||
# there aren't any ccache wrappers available for iOS,
|
||||
# to enable caching we need to prepend the path to the ccache binary
|
||||
env["CC"] = ccache_path + " " + compiler_path + "clang"
|
||||
env["CXX"] = ccache_path + " " + compiler_path + "clang++"
|
||||
env["S_compiler"] = ccache_path + " " + s_compiler_path + "gcc"
|
||||
env["AR"] = compiler_path + "ar"
|
||||
env["RANLIB"] = compiler_path + "ranlib"
|
||||
|
||||
## Compile flags
|
||||
|
||||
if env["ios_simulator"]:
|
||||
detect_darwin_sdk_path("iossimulator", env)
|
||||
env.Append(ASFLAGS=["-mios-simulator-version-min=13.0"])
|
||||
env.Append(CCFLAGS=["-mios-simulator-version-min=13.0"])
|
||||
env.extra_suffix = ".simulator" + env.extra_suffix
|
||||
else:
|
||||
detect_darwin_sdk_path("ios", env)
|
||||
env.Append(ASFLAGS=["-miphoneos-version-min=11.0"])
|
||||
env.Append(CCFLAGS=["-miphoneos-version-min=11.0"])
|
||||
|
||||
if env["arch"] == "x86_64":
|
||||
env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9"
|
||||
env.Append(
|
||||
CCFLAGS=(
|
||||
"-fobjc-arc -arch x86_64"
|
||||
" -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks"
|
||||
" -fasm-blocks -isysroot $IOS_SDK_PATH"
|
||||
).split()
|
||||
)
|
||||
env.Append(ASFLAGS=["-arch", "x86_64"])
|
||||
elif env["arch"] == "arm64":
|
||||
env.Append(
|
||||
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"
|
||||
" -isysroot $IOS_SDK_PATH".split()
|
||||
)
|
||||
)
|
||||
env.Append(ASFLAGS=["-arch", "arm64"])
|
||||
env.Append(CPPDEFINES=["NEED_LONG_INT"])
|
||||
|
||||
# Disable exceptions on non-tools (template) builds
|
||||
if not env["tools"]:
|
||||
if env["ios_exceptions"]:
|
||||
env.Append(CCFLAGS=["-fexceptions"])
|
||||
else:
|
||||
env.Append(CCFLAGS=["-fno-exceptions"])
|
||||
|
||||
# Temp fix for ABS/MAX/MIN macros in iOS SDK blocking compilation
|
||||
env.Append(CCFLAGS=["-Wno-ambiguous-macro"])
|
||||
|
||||
env.Prepend(
|
||||
CPPPATH=[
|
||||
"$IOS_SDK_PATH/usr/include",
|
||||
"$IOS_SDK_PATH/System/Library/Frameworks/AudioUnit.framework/Headers",
|
||||
]
|
||||
)
|
||||
|
||||
env.Prepend(CPPPATH=["#platform/ios"])
|
||||
env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"])
|
||||
|
||||
if env["vulkan"]:
|
||||
env.Append(CPPDEFINES=["VULKAN_ENABLED"])
|
||||
37
platform/ios/device_metrics.h
Normal file
37
platform/ios/device_metrics.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*************************************************************************/
|
||||
/* device_metrics.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface GodotDeviceMetrics : NSObject
|
||||
|
||||
@property(nonatomic, class, readonly, strong) NSDictionary<NSArray *, NSNumber *> *dpiList;
|
||||
|
||||
@end
|
||||
152
platform/ios/device_metrics.m
Normal file
152
platform/ios/device_metrics.m
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/*************************************************************************/
|
||||
/* device_metrics.m */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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
|
||||
58
platform/ios/display_layer.h
Normal file
58
platform/ios/display_layer.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*************************************************************************/
|
||||
/* display_layer.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#import <OpenGLES/EAGLDrawable.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
@protocol DisplayLayer <NSObject>
|
||||
|
||||
- (void)renderDisplayLayer;
|
||||
- (void)initializeDisplayLayer;
|
||||
- (void)layoutDisplayLayer;
|
||||
|
||||
@end
|
||||
|
||||
// An ugly workaround for iOS simulator
|
||||
#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
|
||||
#if defined(__IPHONE_13_0)
|
||||
API_AVAILABLE(ios(13.0))
|
||||
@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
|
||||
#else
|
||||
@interface GodotMetalLayer : CALayer <DisplayLayer>
|
||||
#endif
|
||||
#else
|
||||
@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
|
||||
#endif
|
||||
@end
|
||||
|
||||
API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0))
|
||||
@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer>
|
||||
|
||||
@end
|
||||
173
platform/ios/display_layer.mm
Normal file
173
platform/ios/display_layer.mm
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*************************************************************************/
|
||||
/* display_layer.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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/config/project_settings.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "display_server_ios.h"
|
||||
#include "main/main.h"
|
||||
#include "os_ios.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
#import <AudioToolbox/AudioServices.h>
|
||||
#import <GameController/GameController.h>
|
||||
#import <OpenGLES/EAGL.h>
|
||||
#import <OpenGLES/ES1/gl.h>
|
||||
#import <OpenGLES/ES1/glext.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@implementation GodotMetalLayer
|
||||
|
||||
- (void)initializeDisplayLayer {
|
||||
#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
|
||||
if (@available(iOS 13, *)) {
|
||||
// Simulator supports Metal since iOS 13
|
||||
} else {
|
||||
NSLog(@"iOS Simulator prior to iOS 13 does not support Metal rendering.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)layoutDisplayLayer {
|
||||
}
|
||||
|
||||
- (void)renderDisplayLayer {
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GodotOpenGLLayer {
|
||||
// The pixel dimensions of the backbuffer
|
||||
GLint backingWidth;
|
||||
GLint backingHeight;
|
||||
|
||||
EAGLContext *context;
|
||||
GLuint viewRenderbuffer, viewFramebuffer;
|
||||
GLuint depthRenderbuffer;
|
||||
}
|
||||
|
||||
- (void)initializeDisplayLayer {
|
||||
// Get our backing layer
|
||||
|
||||
// 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];
|
||||
|
||||
// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
|
||||
|
||||
// Create GL ES 2 context
|
||||
if (GLOBAL_GET("rendering/driver/driver_name") == "opengl3") {
|
||||
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)renderDisplayLayer {
|
||||
[EAGLContext setCurrentContext:context];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if ([EAGLContext currentContext] == context) {
|
||||
[EAGLContext setCurrentContext:nil];
|
||||
}
|
||||
|
||||
if (context) {
|
||||
context = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)createFramebuffer {
|
||||
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 CAself)
|
||||
// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
|
||||
[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self];
|
||||
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
|
||||
|
||||
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
|
||||
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
|
||||
|
||||
// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
|
||||
glGenRenderbuffersOES(1, &depthRenderbuffer);
|
||||
glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
|
||||
glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
|
||||
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
|
||||
|
||||
if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
|
||||
NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
|
||||
return NO;
|
||||
}
|
||||
|
||||
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
|
||||
217
platform/ios/display_server_ios.h
Normal file
217
platform/ios/display_server_ios.h
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
/*************************************************************************/
|
||||
/* display_server_ios.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef display_server_ios_h
|
||||
#define display_server_ios_h
|
||||
|
||||
#include "core/input/input.h"
|
||||
#include "servers/display_server.h"
|
||||
|
||||
#if defined(VULKAN_ENABLED)
|
||||
#include "drivers/vulkan/rendering_device_vulkan.h"
|
||||
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
|
||||
|
||||
#include "vulkan_context_ios.h"
|
||||
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#ifdef USE_VOLK
|
||||
#include <volk.h>
|
||||
#else
|
||||
#include <vulkan/vulkan.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class DisplayServerIOS : public DisplayServer {
|
||||
GDCLASS(DisplayServerIOS, DisplayServer)
|
||||
|
||||
_THREAD_SAFE_CLASS_
|
||||
|
||||
#if defined(VULKAN_ENABLED)
|
||||
VulkanContextIOS *context_vulkan = nullptr;
|
||||
RenderingDeviceVulkan *rendering_device_vulkan = nullptr;
|
||||
#endif
|
||||
|
||||
id tts = nullptr;
|
||||
|
||||
DisplayServer::ScreenOrientation screen_orientation;
|
||||
|
||||
ObjectID window_attached_instance_id;
|
||||
|
||||
Callable window_event_callback;
|
||||
Callable window_resize_callback;
|
||||
Callable input_event_callback;
|
||||
Callable input_text_callback;
|
||||
|
||||
int virtual_keyboard_height = 0;
|
||||
|
||||
void perform_event(const Ref<InputEvent> &p_event);
|
||||
|
||||
DisplayServerIOS(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
|
||||
~DisplayServerIOS();
|
||||
|
||||
public:
|
||||
String rendering_driver;
|
||||
|
||||
static DisplayServerIOS *get_singleton();
|
||||
|
||||
static void register_ios_driver();
|
||||
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
|
||||
static Vector<String> get_rendering_drivers_func();
|
||||
|
||||
// MARK: - Events
|
||||
|
||||
virtual void process_events() override;
|
||||
|
||||
virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
|
||||
static void _dispatch_input_events(const Ref<InputEvent> &p_event);
|
||||
void send_input_event(const Ref<InputEvent> &p_event) const;
|
||||
void send_input_text(const String &p_text) const;
|
||||
void send_window_event(DisplayServer::WindowEvent p_event) const;
|
||||
void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
|
||||
|
||||
// MARK: - Input
|
||||
|
||||
// MARK: Touches
|
||||
|
||||
void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click);
|
||||
void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
|
||||
void touches_cancelled(int p_idx);
|
||||
|
||||
// MARK: Keyboard
|
||||
|
||||
void key(Key p_key, bool p_pressed);
|
||||
|
||||
// MARK: Motion
|
||||
|
||||
void update_gravity(float p_x, float p_y, float p_z);
|
||||
void update_accelerometer(float p_x, float p_y, float p_z);
|
||||
void update_magnetometer(float p_x, float p_y, float p_z);
|
||||
void update_gyroscope(float p_x, float p_y, float p_z);
|
||||
|
||||
// MARK: -
|
||||
|
||||
virtual bool has_feature(Feature p_feature) const override;
|
||||
virtual String get_name() const override;
|
||||
|
||||
virtual bool tts_is_speaking() const override;
|
||||
virtual bool tts_is_paused() const override;
|
||||
virtual Array tts_get_voices() const override;
|
||||
|
||||
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
|
||||
virtual void tts_pause() override;
|
||||
virtual void tts_resume() override;
|
||||
virtual void tts_stop() override;
|
||||
|
||||
virtual Rect2i get_display_safe_area() const override;
|
||||
|
||||
virtual int get_screen_count() const override;
|
||||
virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
|
||||
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
|
||||
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
|
||||
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
|
||||
virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
|
||||
virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
|
||||
|
||||
virtual Vector<DisplayServer::WindowID> get_window_list() const override;
|
||||
|
||||
virtual WindowID
|
||||
get_window_at_screen_position(const Point2i &p_position) const override;
|
||||
|
||||
virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
|
||||
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
|
||||
virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
|
||||
virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
|
||||
|
||||
virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
|
||||
virtual float screen_get_max_scale() const override;
|
||||
|
||||
virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override;
|
||||
virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override;
|
||||
|
||||
virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual bool can_any_window_draw() const override;
|
||||
|
||||
virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
|
||||
|
||||
virtual bool screen_is_touchscreen(int p_screen) const override;
|
||||
|
||||
virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) override;
|
||||
virtual void virtual_keyboard_hide() override;
|
||||
|
||||
void virtual_keyboard_set_height(int height);
|
||||
virtual int virtual_keyboard_get_height() const override;
|
||||
|
||||
virtual void clipboard_set(const String &p_text) override;
|
||||
virtual String clipboard_get() const override;
|
||||
|
||||
virtual void screen_set_keep_on(bool p_enable) override;
|
||||
virtual bool screen_is_kept_on() const override;
|
||||
|
||||
void resize_window(CGSize size);
|
||||
};
|
||||
|
||||
#endif /* DISPLAY_SERVER_IOS_H */
|
||||
655
platform/ios/display_server_ios.mm
Normal file
655
platform/ios/display_server_ios.mm
Normal file
|
|
@ -0,0 +1,655 @@
|
|||
/*************************************************************************/
|
||||
/* display_server_ios.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "display_server_ios.h"
|
||||
|
||||
#import "app_delegate.h"
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/file_access_pack.h"
|
||||
#import "device_metrics.h"
|
||||
#import "godot_view.h"
|
||||
#include "ios.h"
|
||||
#import "keyboard_input_view.h"
|
||||
#include "os_ios.h"
|
||||
#include "tts_ios.h"
|
||||
#import "view_controller.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <sys/utsname.h>
|
||||
|
||||
static const float kDisplayServerIOSAcceleration = 1.f;
|
||||
|
||||
DisplayServerIOS *DisplayServerIOS::get_singleton() {
|
||||
return (DisplayServerIOS *)DisplayServer::get_singleton();
|
||||
}
|
||||
|
||||
DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
|
||||
rendering_driver = p_rendering_driver;
|
||||
|
||||
// Init TTS
|
||||
tts = [[TTS_IOS alloc] init];
|
||||
|
||||
#if defined(GLES3_ENABLED)
|
||||
// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented
|
||||
// again,
|
||||
// Note that we should be checking "opengl3" as the driver, might never enable this seeing OpenGL is deprecated on iOS
|
||||
// We are hardcoding the rendering_driver to "vulkan" down below
|
||||
|
||||
if (rendering_driver == "opengl_es") {
|
||||
bool gl_initialization_error = false;
|
||||
|
||||
// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
|
||||
|
||||
if (RasterizerGLES3::is_viable() == OK) {
|
||||
RasterizerGLES3::register_config();
|
||||
RasterizerGLES3::make_current();
|
||||
} else {
|
||||
gl_initialization_error = true;
|
||||
}
|
||||
|
||||
if (gl_initialization_error) {
|
||||
OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", "Unable to initialize video driver");
|
||||
// return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
// rendering_server = memnew(RenderingServerDefault);
|
||||
// // FIXME: Reimplement threaded rendering
|
||||
// if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
|
||||
// rendering_server = memnew(RenderingServerWrapMT(rendering_server,
|
||||
// false));
|
||||
// }
|
||||
// rendering_server->init();
|
||||
// rendering_server->cursor_set_visible(false, 0);
|
||||
|
||||
// reset this to what it should be, it will have been set to 0 after
|
||||
// rendering_server->init() is called
|
||||
// RasterizerStorageGLES3system_fbo = gl_view_base_fb;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(VULKAN_ENABLED)
|
||||
rendering_driver = "vulkan";
|
||||
|
||||
context_vulkan = nullptr;
|
||||
rendering_device_vulkan = nullptr;
|
||||
|
||||
if (rendering_driver == "vulkan") {
|
||||
context_vulkan = memnew(VulkanContextIOS);
|
||||
if (context_vulkan->initialize() != OK) {
|
||||
memdelete(context_vulkan);
|
||||
context_vulkan = nullptr;
|
||||
ERR_FAIL_MSG("Failed to initialize Vulkan context");
|
||||
}
|
||||
|
||||
CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"];
|
||||
|
||||
if (!layer) {
|
||||
ERR_FAIL_MSG("Failed to create iOS rendering layer.");
|
||||
}
|
||||
|
||||
Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
|
||||
if (context_vulkan->window_create(MAIN_WINDOW_ID, p_vsync_mode, layer, size.width, size.height) != OK) {
|
||||
memdelete(context_vulkan);
|
||||
context_vulkan = nullptr;
|
||||
ERR_FAIL_MSG("Failed to create Vulkan window.");
|
||||
}
|
||||
|
||||
rendering_device_vulkan = memnew(RenderingDeviceVulkan);
|
||||
rendering_device_vulkan->initialize(context_vulkan);
|
||||
|
||||
RendererCompositorRD::make_current();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
|
||||
screen_set_keep_on(keep_screen_on);
|
||||
|
||||
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
|
||||
|
||||
r_error = OK;
|
||||
}
|
||||
|
||||
DisplayServerIOS::~DisplayServerIOS() {
|
||||
#if defined(VULKAN_ENABLED)
|
||||
if (rendering_device_vulkan) {
|
||||
rendering_device_vulkan->finalize();
|
||||
memdelete(rendering_device_vulkan);
|
||||
rendering_device_vulkan = nullptr;
|
||||
}
|
||||
|
||||
if (context_vulkan) {
|
||||
context_vulkan->window_destroy(MAIN_WINDOW_ID);
|
||||
memdelete(context_vulkan);
|
||||
context_vulkan = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
DisplayServer *DisplayServerIOS::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
|
||||
return memnew(DisplayServerIOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error));
|
||||
}
|
||||
|
||||
Vector<String> DisplayServerIOS::get_rendering_drivers_func() {
|
||||
Vector<String> drivers;
|
||||
|
||||
#if defined(VULKAN_ENABLED)
|
||||
drivers.push_back("vulkan");
|
||||
#endif
|
||||
#if defined(GLES3_ENABLED)
|
||||
drivers.push_back("opengl_es");
|
||||
#endif
|
||||
|
||||
return drivers;
|
||||
}
|
||||
|
||||
void DisplayServerIOS::register_ios_driver() {
|
||||
register_create_function("iOS", create_func, get_rendering_drivers_func);
|
||||
}
|
||||
|
||||
// MARK: Events
|
||||
|
||||
void DisplayServerIOS::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
|
||||
window_resize_callback = p_callable;
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
|
||||
window_event_callback = p_callable;
|
||||
}
|
||||
void DisplayServerIOS::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
|
||||
input_event_callback = p_callable;
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
|
||||
input_text_callback = p_callable;
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
void DisplayServerIOS::process_events() {
|
||||
Input::get_singleton()->flush_buffered_events();
|
||||
}
|
||||
|
||||
void DisplayServerIOS::_dispatch_input_events(const Ref<InputEvent> &p_event) {
|
||||
DisplayServerIOS::get_singleton()->send_input_event(p_event);
|
||||
}
|
||||
|
||||
void DisplayServerIOS::send_input_event(const Ref<InputEvent> &p_event) const {
|
||||
_window_callback(input_event_callback, p_event);
|
||||
}
|
||||
|
||||
void DisplayServerIOS::send_input_text(const String &p_text) const {
|
||||
_window_callback(input_text_callback, p_text);
|
||||
}
|
||||
|
||||
void DisplayServerIOS::send_window_event(DisplayServer::WindowEvent p_event) const {
|
||||
_window_callback(window_event_callback, int(p_event));
|
||||
}
|
||||
|
||||
void DisplayServerIOS::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
|
||||
if (!p_callable.is_null()) {
|
||||
const Variant *argp = &p_arg;
|
||||
Variant ret;
|
||||
Callable::CallError ce;
|
||||
p_callable.call((const Variant **)&argp, 1, ret, ce);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Input
|
||||
|
||||
// MARK: Touches
|
||||
|
||||
void DisplayServerIOS::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) {
|
||||
if (!GLOBAL_DEF("debug/disable_touch", false)) {
|
||||
Ref<InputEventScreenTouch> ev;
|
||||
ev.instantiate();
|
||||
|
||||
ev->set_index(p_idx);
|
||||
ev->set_pressed(p_pressed);
|
||||
ev->set_position(Vector2(p_x, p_y));
|
||||
perform_event(ev);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayServerIOS::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)) {
|
||||
Ref<InputEventScreenDrag> ev;
|
||||
ev.instantiate();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayServerIOS::perform_event(const Ref<InputEvent> &p_event) {
|
||||
Input::get_singleton()->parse_input_event(p_event);
|
||||
}
|
||||
|
||||
void DisplayServerIOS::touches_cancelled(int p_idx) {
|
||||
touch_press(p_idx, -1, -1, false, false);
|
||||
}
|
||||
|
||||
// MARK: Keyboard
|
||||
|
||||
void DisplayServerIOS::key(Key p_key, bool p_pressed) {
|
||||
Ref<InputEventKey> ev;
|
||||
ev.instantiate();
|
||||
ev->set_echo(false);
|
||||
ev->set_pressed(p_pressed);
|
||||
ev->set_keycode(p_key);
|
||||
ev->set_physical_keycode(p_key);
|
||||
ev->set_unicode((char32_t)p_key);
|
||||
perform_event(ev);
|
||||
}
|
||||
|
||||
// MARK: Motion
|
||||
|
||||
void DisplayServerIOS::update_gravity(float p_x, float p_y, float p_z) {
|
||||
Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z));
|
||||
}
|
||||
|
||||
void DisplayServerIOS::update_accelerometer(float p_x, float p_y, float p_z) {
|
||||
// Found out the Z should not be negated! Pass as is!
|
||||
Vector3 v_accelerometer = Vector3(
|
||||
p_x / kDisplayServerIOSAcceleration,
|
||||
p_y / kDisplayServerIOSAcceleration,
|
||||
p_z / kDisplayServerIOSAcceleration);
|
||||
|
||||
Input::get_singleton()->set_accelerometer(v_accelerometer);
|
||||
}
|
||||
|
||||
void DisplayServerIOS::update_magnetometer(float p_x, float p_y, float p_z) {
|
||||
Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z));
|
||||
}
|
||||
|
||||
void DisplayServerIOS::update_gyroscope(float p_x, float p_y, float p_z) {
|
||||
Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z));
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
bool DisplayServerIOS::has_feature(Feature p_feature) const {
|
||||
switch (p_feature) {
|
||||
// case FEATURE_CURSOR_SHAPE:
|
||||
// case FEATURE_CUSTOM_CURSOR_SHAPE:
|
||||
// case FEATURE_GLOBAL_MENU:
|
||||
// case FEATURE_HIDPI:
|
||||
// case FEATURE_ICON:
|
||||
// case FEATURE_IME:
|
||||
// case FEATURE_MOUSE:
|
||||
// case FEATURE_MOUSE_WARP:
|
||||
// case FEATURE_NATIVE_DIALOG:
|
||||
// case FEATURE_NATIVE_ICON:
|
||||
// case FEATURE_WINDOW_TRANSPARENCY:
|
||||
case FEATURE_CLIPBOARD:
|
||||
case FEATURE_KEEP_SCREEN_ON:
|
||||
case FEATURE_ORIENTATION:
|
||||
case FEATURE_TOUCHSCREEN:
|
||||
case FEATURE_VIRTUAL_KEYBOARD:
|
||||
case FEATURE_TEXT_TO_SPEECH:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String DisplayServerIOS::get_name() const {
|
||||
return "iOS";
|
||||
}
|
||||
|
||||
bool DisplayServerIOS::tts_is_speaking() const {
|
||||
ERR_FAIL_COND_V(!tts, false);
|
||||
return [tts isSpeaking];
|
||||
}
|
||||
|
||||
bool DisplayServerIOS::tts_is_paused() const {
|
||||
ERR_FAIL_COND_V(!tts, false);
|
||||
return [tts isPaused];
|
||||
}
|
||||
|
||||
Array DisplayServerIOS::tts_get_voices() const {
|
||||
ERR_FAIL_COND_V(!tts, Array());
|
||||
return [tts getVoices];
|
||||
}
|
||||
|
||||
void DisplayServerIOS::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
|
||||
ERR_FAIL_COND(!tts);
|
||||
[tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt];
|
||||
}
|
||||
|
||||
void DisplayServerIOS::tts_pause() {
|
||||
ERR_FAIL_COND(!tts);
|
||||
[tts pauseSpeaking];
|
||||
}
|
||||
|
||||
void DisplayServerIOS::tts_resume() {
|
||||
ERR_FAIL_COND(!tts);
|
||||
[tts resumeSpeaking];
|
||||
}
|
||||
|
||||
void DisplayServerIOS::tts_stop() {
|
||||
ERR_FAIL_COND(!tts);
|
||||
[tts stopSpeaking];
|
||||
}
|
||||
|
||||
Rect2i DisplayServerIOS::get_display_safe_area() const {
|
||||
if (@available(iOS 11, *)) {
|
||||
UIEdgeInsets insets = UIEdgeInsetsZero;
|
||||
UIView *view = AppDelegate.viewController.godotView;
|
||||
if ([view respondsToSelector:@selector(safeAreaInsets)]) {
|
||||
insets = [view safeAreaInsets];
|
||||
}
|
||||
float scale = screen_get_scale();
|
||||
Size2i insets_position = Size2i(insets.left, insets.top) * scale;
|
||||
Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
|
||||
return Rect2i(screen_get_position() + insets_position, screen_get_size() - insets_size);
|
||||
} else {
|
||||
return Rect2i(screen_get_position(), screen_get_size());
|
||||
}
|
||||
}
|
||||
|
||||
int DisplayServerIOS::get_screen_count() const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
Point2i DisplayServerIOS::screen_get_position(int p_screen) const {
|
||||
return Size2i();
|
||||
}
|
||||
|
||||
Size2i DisplayServerIOS::screen_get_size(int p_screen) const {
|
||||
CALayer *layer = AppDelegate.viewController.godotView.renderingLayer;
|
||||
|
||||
if (!layer) {
|
||||
return Size2i();
|
||||
}
|
||||
|
||||
return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
|
||||
}
|
||||
|
||||
Rect2i DisplayServerIOS::screen_get_usable_rect(int p_screen) const {
|
||||
return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
|
||||
}
|
||||
|
||||
int DisplayServerIOS::screen_get_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;
|
||||
}
|
||||
}
|
||||
|
||||
float DisplayServerIOS::screen_get_refresh_rate(int p_screen) const {
|
||||
return [UIScreen mainScreen].maximumFramesPerSecond;
|
||||
}
|
||||
|
||||
float DisplayServerIOS::screen_get_scale(int p_screen) const {
|
||||
return [UIScreen mainScreen].nativeScale;
|
||||
}
|
||||
|
||||
Vector<DisplayServer::WindowID> DisplayServerIOS::get_window_list() const {
|
||||
Vector<DisplayServer::WindowID> list;
|
||||
list.push_back(MAIN_WINDOW_ID);
|
||||
return list;
|
||||
}
|
||||
|
||||
DisplayServer::WindowID DisplayServerIOS::get_window_at_screen_position(const Point2i &p_position) const {
|
||||
return MAIN_WINDOW_ID;
|
||||
}
|
||||
|
||||
int64_t DisplayServerIOS::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
|
||||
ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0);
|
||||
switch (p_handle_type) {
|
||||
case DISPLAY_HANDLE: {
|
||||
return 0; // Not supported.
|
||||
}
|
||||
case WINDOW_HANDLE: {
|
||||
return (int64_t)AppDelegate.viewController;
|
||||
}
|
||||
case WINDOW_VIEW: {
|
||||
return (int64_t)AppDelegate.viewController.godotView;
|
||||
}
|
||||
default: {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
|
||||
window_attached_instance_id = p_instance;
|
||||
}
|
||||
|
||||
ObjectID DisplayServerIOS::window_get_attached_instance_id(WindowID p_window) const {
|
||||
return window_attached_instance_id;
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_title(const String &p_title, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
int DisplayServerIOS::window_get_current_screen(WindowID p_window) const {
|
||||
return SCREEN_OF_MAIN_WINDOW;
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_current_screen(int p_screen, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
Point2i DisplayServerIOS::window_get_position(WindowID p_window) const {
|
||||
return Point2i();
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_position(const Point2i &p_position, WindowID p_window) {
|
||||
// Probably not supported for single window iOS app
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_transient(WindowID p_window, WindowID p_parent) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_max_size(const Size2i p_size, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
Size2i DisplayServerIOS::window_get_max_size(WindowID p_window) const {
|
||||
return Size2i();
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_min_size(const Size2i p_size, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
Size2i DisplayServerIOS::window_get_min_size(WindowID p_window) const {
|
||||
return Size2i();
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_size(const Size2i p_size, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
Size2i DisplayServerIOS::window_get_size(WindowID p_window) const {
|
||||
CGRect screenBounds = [UIScreen mainScreen].bounds;
|
||||
return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale();
|
||||
}
|
||||
|
||||
Size2i DisplayServerIOS::window_get_real_size(WindowID p_window) const {
|
||||
return window_get_size(p_window);
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_mode(WindowMode p_mode, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
DisplayServer::WindowMode DisplayServerIOS::window_get_mode(WindowID p_window) const {
|
||||
return WindowMode::WINDOW_MODE_FULLSCREEN;
|
||||
}
|
||||
|
||||
bool DisplayServerIOS::window_is_maximize_allowed(WindowID p_window) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
bool DisplayServerIOS::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_request_attention(WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_move_to_foreground(WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
float DisplayServerIOS::screen_get_max_scale() const {
|
||||
return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
|
||||
}
|
||||
|
||||
void DisplayServerIOS::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
|
||||
screen_orientation = p_orientation;
|
||||
}
|
||||
|
||||
DisplayServer::ScreenOrientation DisplayServerIOS::screen_get_orientation(int p_screen) const {
|
||||
return screen_orientation;
|
||||
}
|
||||
|
||||
bool DisplayServerIOS::window_can_draw(WindowID p_window) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DisplayServerIOS::can_any_window_draw() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DisplayServerIOS::screen_is_touchscreen(int p_screen) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DisplayServerIOS::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {
|
||||
NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()];
|
||||
|
||||
[AppDelegate.viewController.keyboardView
|
||||
becomeFirstResponderWithString:existingString
|
||||
multiline:p_multiline
|
||||
cursorStart:p_cursor_start
|
||||
cursorEnd:p_cursor_end];
|
||||
}
|
||||
|
||||
void DisplayServerIOS::virtual_keyboard_hide() {
|
||||
[AppDelegate.viewController.keyboardView resignFirstResponder];
|
||||
}
|
||||
|
||||
void DisplayServerIOS::virtual_keyboard_set_height(int height) {
|
||||
virtual_keyboard_height = height * screen_get_max_scale();
|
||||
}
|
||||
|
||||
int DisplayServerIOS::virtual_keyboard_get_height() const {
|
||||
return virtual_keyboard_height;
|
||||
}
|
||||
|
||||
void DisplayServerIOS::clipboard_set(const String &p_text) {
|
||||
[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()];
|
||||
}
|
||||
|
||||
String DisplayServerIOS::clipboard_get() const {
|
||||
NSString *text = [UIPasteboard generalPasteboard].string;
|
||||
|
||||
return String::utf8([text UTF8String]);
|
||||
}
|
||||
|
||||
void DisplayServerIOS::screen_set_keep_on(bool p_enable) {
|
||||
[UIApplication sharedApplication].idleTimerDisabled = p_enable;
|
||||
}
|
||||
|
||||
bool DisplayServerIOS::screen_is_kept_on() const {
|
||||
return [UIApplication sharedApplication].idleTimerDisabled;
|
||||
}
|
||||
|
||||
void DisplayServerIOS::resize_window(CGSize viewSize) {
|
||||
Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
|
||||
|
||||
#if defined(VULKAN_ENABLED)
|
||||
if (context_vulkan) {
|
||||
context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y);
|
||||
}
|
||||
#endif
|
||||
|
||||
Variant resize_rect = Rect2i(Point2i(), size);
|
||||
_window_callback(window_resize_callback, resize_rect);
|
||||
}
|
||||
|
||||
void DisplayServerIOS::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
|
||||
_THREAD_SAFE_METHOD_
|
||||
#if defined(VULKAN_ENABLED)
|
||||
context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
DisplayServer::VSyncMode DisplayServerIOS::window_get_vsync_mode(WindowID p_window) const {
|
||||
_THREAD_SAFE_METHOD_
|
||||
#if defined(VULKAN_ENABLED)
|
||||
return context_vulkan->get_vsync_mode(p_window);
|
||||
#else
|
||||
return DisplayServer::VSYNC_ENABLED;
|
||||
#endif
|
||||
}
|
||||
40
platform/ios/export/export.cpp
Normal file
40
platform/ios/export/export.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*************************************************************************/
|
||||
/* export.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "export.h"
|
||||
|
||||
#include "export_plugin.h"
|
||||
|
||||
void register_ios_exporter() {
|
||||
Ref<EditorExportPlatformIOS> platform;
|
||||
platform.instantiate();
|
||||
|
||||
EditorExport::get_singleton()->add_export_platform(platform);
|
||||
}
|
||||
36
platform/ios/export/export.h
Normal file
36
platform/ios/export/export.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*************************************************************************/
|
||||
/* export.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef IOS_EXPORT_H
|
||||
#define IOS_EXPORT_H
|
||||
|
||||
void register_ios_exporter();
|
||||
|
||||
#endif // IOS_EXPORT_H
|
||||
1849
platform/ios/export/export_plugin.cpp
Normal file
1849
platform/ios/export/export_plugin.cpp
Normal file
File diff suppressed because it is too large
Load diff
293
platform/ios/export/export_plugin.h
Normal file
293
platform/ios/export/export_plugin.h
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
/*************************************************************************/
|
||||
/* export_plugin.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef IOS_EXPORT_PLUGIN_H
|
||||
#define IOS_EXPORT_PLUGIN_H
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/image_loader.h"
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "core/io/zip_io.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
#include "core/version.h"
|
||||
#include "editor/editor_export.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "main/splash.gen.h"
|
||||
#include "platform/ios/logo.gen.h"
|
||||
#include "string.h"
|
||||
|
||||
#include "godot_plugin_config.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
class EditorExportPlatformIOS : public EditorExportPlatform {
|
||||
GDCLASS(EditorExportPlatformIOS, EditorExportPlatform);
|
||||
|
||||
Ref<ImageTexture> logo;
|
||||
|
||||
// Plugins
|
||||
SafeFlag plugins_changed;
|
||||
Thread check_for_changes_thread;
|
||||
SafeFlag quit_request;
|
||||
Mutex plugins_lock;
|
||||
Vector<PluginConfigIOS> plugins;
|
||||
|
||||
typedef Error (*FileHandler)(String p_file, void *p_userdata);
|
||||
static Error _walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata);
|
||||
static Error _codesign(String p_file, void *p_userdata);
|
||||
void _blend_and_rotate(Ref<Image> &p_dst, Ref<Image> &p_src, bool p_rot);
|
||||
|
||||
struct IOSConfigData {
|
||||
String pkg_name;
|
||||
String binary_name;
|
||||
String plist_content;
|
||||
String architectures;
|
||||
String linker_flags;
|
||||
String cpp_code;
|
||||
String modules_buildfile;
|
||||
String modules_fileref;
|
||||
String modules_buildphase;
|
||||
String modules_buildgrp;
|
||||
Vector<String> capabilities;
|
||||
};
|
||||
struct ExportArchitecture {
|
||||
String name;
|
||||
bool is_default = false;
|
||||
|
||||
ExportArchitecture() {}
|
||||
|
||||
ExportArchitecture(String p_name, bool p_is_default) {
|
||||
name = p_name;
|
||||
is_default = p_is_default;
|
||||
}
|
||||
};
|
||||
|
||||
struct IOSExportAsset {
|
||||
String exported_path;
|
||||
bool is_framework = false; // framework is anything linked to the binary, otherwise it's a resource
|
||||
bool should_embed = false;
|
||||
};
|
||||
|
||||
String _get_additional_plist_content();
|
||||
String _get_linker_flags();
|
||||
String _get_cpp_code();
|
||||
void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug);
|
||||
Error _export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
|
||||
Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
|
||||
Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir);
|
||||
|
||||
Vector<ExportArchitecture> _get_supported_architectures();
|
||||
Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset);
|
||||
|
||||
void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets);
|
||||
Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
|
||||
Error _copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
|
||||
Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
|
||||
Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug);
|
||||
|
||||
bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
|
||||
String pname = p_package;
|
||||
|
||||
if (pname.length() == 0) {
|
||||
if (r_error) {
|
||||
*r_error = TTR("Identifier is missing.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < pname.length(); i++) {
|
||||
char32_t c = pname[i];
|
||||
if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) {
|
||||
if (r_error) {
|
||||
*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _check_for_changes_poll_thread(void *ud) {
|
||||
EditorExportPlatformIOS *ea = static_cast<EditorExportPlatformIOS *>(ud);
|
||||
|
||||
while (!ea->quit_request.is_set()) {
|
||||
// Nothing to do if we already know the plugins have changed.
|
||||
if (!ea->plugins_changed.is_set()) {
|
||||
MutexLock lock(ea->plugins_lock);
|
||||
|
||||
Vector<PluginConfigIOS> loaded_plugins = get_plugins();
|
||||
|
||||
if (ea->plugins.size() != loaded_plugins.size()) {
|
||||
ea->plugins_changed.set();
|
||||
} else {
|
||||
for (int i = 0; i < ea->plugins.size(); i++) {
|
||||
if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) {
|
||||
ea->plugins_changed.set();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t wait = 3000000;
|
||||
uint64_t time = OS::get_singleton()->get_ticks_usec();
|
||||
while (OS::get_singleton()->get_ticks_usec() - time < wait) {
|
||||
OS::get_singleton()->delay_usec(300000);
|
||||
|
||||
if (ea->quit_request.is_set()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override;
|
||||
virtual void get_export_options(List<ExportOption> *r_options) override;
|
||||
|
||||
public:
|
||||
virtual String get_name() const override { return "iOS"; }
|
||||
virtual String get_os_name() const override { return "iOS"; }
|
||||
virtual Ref<Texture2D> get_logo() const override { return logo; }
|
||||
|
||||
virtual bool should_update_export_options() override {
|
||||
bool export_options_changed = plugins_changed.is_set();
|
||||
if (export_options_changed) {
|
||||
// don't clear unless we're reporting true, to avoid race
|
||||
plugins_changed.clear();
|
||||
}
|
||||
return export_options_changed;
|
||||
}
|
||||
|
||||
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
|
||||
List<String> list;
|
||||
list.push_back("ipa");
|
||||
return list;
|
||||
}
|
||||
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
|
||||
|
||||
virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
|
||||
|
||||
virtual void get_platform_features(List<String> *r_features) override {
|
||||
r_features->push_back("mobile");
|
||||
r_features->push_back("ios");
|
||||
}
|
||||
|
||||
virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) override {
|
||||
}
|
||||
|
||||
EditorExportPlatformIOS();
|
||||
~EditorExportPlatformIOS();
|
||||
|
||||
/// List the gdip files in the directory specified by the p_path parameter.
|
||||
static Vector<String> list_plugin_config_files(const String &p_path, bool p_check_directories) {
|
||||
Vector<String> dir_files;
|
||||
Ref<DirAccess> da = DirAccess::open(p_path);
|
||||
if (da.is_valid()) {
|
||||
da->list_dir_begin();
|
||||
while (true) {
|
||||
String file = da->get_next();
|
||||
if (file.is_empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (file == "." || file == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (da->current_is_hidden()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (da->current_is_dir()) {
|
||||
if (p_check_directories) {
|
||||
Vector<String> directory_files = list_plugin_config_files(p_path.plus_file(file), false);
|
||||
for (int i = 0; i < directory_files.size(); ++i) {
|
||||
dir_files.push_back(file.plus_file(directory_files[i]));
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file.ends_with(PluginConfigIOS::PLUGIN_CONFIG_EXT)) {
|
||||
dir_files.push_back(file);
|
||||
}
|
||||
}
|
||||
da->list_dir_end();
|
||||
}
|
||||
|
||||
return dir_files;
|
||||
}
|
||||
|
||||
static Vector<PluginConfigIOS> get_plugins() {
|
||||
Vector<PluginConfigIOS> loaded_plugins;
|
||||
|
||||
String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("ios/plugins");
|
||||
|
||||
if (DirAccess::exists(plugins_dir)) {
|
||||
Vector<String> plugins_filenames = list_plugin_config_files(plugins_dir, true);
|
||||
|
||||
if (!plugins_filenames.is_empty()) {
|
||||
Ref<ConfigFile> config_file = memnew(ConfigFile);
|
||||
for (int i = 0; i < plugins_filenames.size(); i++) {
|
||||
PluginConfigIOS config = PluginConfigIOS::load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i]));
|
||||
if (config.valid_config) {
|
||||
loaded_plugins.push_back(config);
|
||||
} else {
|
||||
print_error("Invalid plugin config file " + plugins_filenames[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loaded_plugins;
|
||||
}
|
||||
|
||||
static Vector<PluginConfigIOS> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
|
||||
Vector<PluginConfigIOS> enabled_plugins;
|
||||
Vector<PluginConfigIOS> all_plugins = get_plugins();
|
||||
for (int i = 0; i < all_plugins.size(); i++) {
|
||||
PluginConfigIOS plugin = all_plugins[i];
|
||||
bool enabled = p_presets->get("plugins/" + plugin.name);
|
||||
if (enabled) {
|
||||
enabled_plugins.push_back(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
return enabled_plugins;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
285
platform/ios/export/godot_plugin_config.cpp
Normal file
285
platform/ios/export/godot_plugin_config.cpp
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
/*************************************************************************/
|
||||
/* godot_plugin_config.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "godot_plugin_config.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
String PluginConfigIOS::resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
|
||||
String absolute_path;
|
||||
|
||||
if (dependency_path.is_empty()) {
|
||||
return absolute_path;
|
||||
}
|
||||
|
||||
if (dependency_path.is_absolute_path()) {
|
||||
return dependency_path;
|
||||
}
|
||||
|
||||
String res_path = ProjectSettings::get_singleton()->globalize_path("res://");
|
||||
absolute_path = plugin_config_dir.plus_file(dependency_path);
|
||||
|
||||
return absolute_path.replace(res_path, "res://");
|
||||
}
|
||||
|
||||
String PluginConfigIOS::resolve_system_dependency_path(String dependency_path) {
|
||||
String absolute_path;
|
||||
|
||||
if (dependency_path.is_empty()) {
|
||||
return absolute_path;
|
||||
}
|
||||
|
||||
if (dependency_path.is_absolute_path()) {
|
||||
return dependency_path;
|
||||
}
|
||||
|
||||
String system_path = "/System/Library/Frameworks";
|
||||
|
||||
return system_path.plus_file(dependency_path);
|
||||
}
|
||||
|
||||
Vector<String> PluginConfigIOS::resolve_local_dependencies(String plugin_config_dir, Vector<String> p_paths) {
|
||||
Vector<String> paths;
|
||||
|
||||
for (int i = 0; i < p_paths.size(); i++) {
|
||||
String path = resolve_local_dependency_path(plugin_config_dir, p_paths[i]);
|
||||
|
||||
if (path.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
paths.push_back(path);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
Vector<String> PluginConfigIOS::resolve_system_dependencies(Vector<String> p_paths) {
|
||||
Vector<String> paths;
|
||||
|
||||
for (int i = 0; i < p_paths.size(); i++) {
|
||||
String path = resolve_system_dependency_path(p_paths[i]);
|
||||
|
||||
if (path.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
paths.push_back(path);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
bool PluginConfigIOS::validate_plugin(PluginConfigIOS &plugin_config) {
|
||||
bool valid_name = !plugin_config.name.is_empty();
|
||||
bool valid_binary_name = !plugin_config.binary.is_empty();
|
||||
bool valid_initialize = !plugin_config.initialization_method.is_empty();
|
||||
bool valid_deinitialize = !plugin_config.deinitialization_method.is_empty();
|
||||
|
||||
bool fields_value = valid_name && valid_binary_name && valid_initialize && valid_deinitialize;
|
||||
|
||||
if (!fields_value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String plugin_extension = plugin_config.binary.get_extension().to_lower();
|
||||
|
||||
if ((plugin_extension == "a" && FileAccess::exists(plugin_config.binary)) ||
|
||||
(plugin_extension == "xcframework" && DirAccess::exists(plugin_config.binary))) {
|
||||
plugin_config.valid_config = true;
|
||||
plugin_config.supports_targets = false;
|
||||
} else {
|
||||
String file_path = plugin_config.binary.get_base_dir();
|
||||
String file_name = plugin_config.binary.get_basename().get_file();
|
||||
String file_extension = plugin_config.binary.get_extension();
|
||||
String release_file_name = file_path.plus_file(file_name + ".release." + file_extension);
|
||||
String debug_file_name = file_path.plus_file(file_name + ".debug." + file_extension);
|
||||
|
||||
if ((plugin_extension == "a" && FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) ||
|
||||
(plugin_extension == "xcframework" && DirAccess::exists(release_file_name) && DirAccess::exists(debug_file_name))) {
|
||||
plugin_config.valid_config = true;
|
||||
plugin_config.supports_targets = true;
|
||||
}
|
||||
}
|
||||
|
||||
return plugin_config.valid_config;
|
||||
}
|
||||
|
||||
String PluginConfigIOS::get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug) {
|
||||
if (!plugin_config.supports_targets) {
|
||||
return plugin_config.binary;
|
||||
}
|
||||
|
||||
String plugin_binary_dir = plugin_config.binary.get_base_dir();
|
||||
String plugin_name_prefix = plugin_config.binary.get_basename().get_file();
|
||||
String plugin_extension = plugin_config.binary.get_extension();
|
||||
String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + "." + plugin_extension;
|
||||
|
||||
return plugin_binary_dir.plus_file(plugin_file);
|
||||
}
|
||||
|
||||
uint64_t PluginConfigIOS::get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path) {
|
||||
uint64_t last_updated = FileAccess::get_modified_time(config_path);
|
||||
|
||||
if (!plugin_config.supports_targets) {
|
||||
last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary));
|
||||
} else {
|
||||
String file_path = plugin_config.binary.get_base_dir();
|
||||
String file_name = plugin_config.binary.get_basename().get_file();
|
||||
String plugin_extension = plugin_config.binary.get_extension();
|
||||
String release_file_name = file_path.plus_file(file_name + ".release." + plugin_extension);
|
||||
String debug_file_name = file_path.plus_file(file_name + ".debug." + plugin_extension);
|
||||
|
||||
last_updated = MAX(last_updated, FileAccess::get_modified_time(release_file_name));
|
||||
last_updated = MAX(last_updated, FileAccess::get_modified_time(debug_file_name));
|
||||
}
|
||||
|
||||
return last_updated;
|
||||
}
|
||||
|
||||
PluginConfigIOS PluginConfigIOS::load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
|
||||
PluginConfigIOS plugin_config = {};
|
||||
|
||||
if (!config_file.is_valid()) {
|
||||
return plugin_config;
|
||||
}
|
||||
|
||||
config_file->clear();
|
||||
|
||||
Error err = config_file->load(path);
|
||||
|
||||
if (err != OK) {
|
||||
return plugin_config;
|
||||
}
|
||||
|
||||
String config_base_dir = path.get_base_dir();
|
||||
|
||||
plugin_config.name = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_NAME_KEY, String());
|
||||
plugin_config.initialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_INITIALIZE_KEY, String());
|
||||
plugin_config.deinitialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_DEINITIALIZE_KEY, String());
|
||||
|
||||
String binary_path = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_BINARY_KEY, String());
|
||||
plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path);
|
||||
|
||||
if (config_file->has_section(PluginConfigIOS::DEPENDENCIES_SECTION)) {
|
||||
Vector<String> linked_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKED_KEY, Vector<String>());
|
||||
Vector<String> embedded_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_EMBEDDED_KEY, Vector<String>());
|
||||
Vector<String> system_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_SYSTEM_KEY, Vector<String>());
|
||||
Vector<String> files = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_FILES_KEY, Vector<String>());
|
||||
|
||||
plugin_config.linked_dependencies = resolve_local_dependencies(config_base_dir, linked_dependencies);
|
||||
plugin_config.embedded_dependencies = resolve_local_dependencies(config_base_dir, embedded_dependencies);
|
||||
plugin_config.system_dependencies = resolve_system_dependencies(system_dependencies);
|
||||
|
||||
plugin_config.files_to_copy = resolve_local_dependencies(config_base_dir, files);
|
||||
|
||||
plugin_config.capabilities = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_CAPABILITIES_KEY, Vector<String>());
|
||||
|
||||
plugin_config.linker_flags = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKER_FLAGS, Vector<String>());
|
||||
}
|
||||
|
||||
if (config_file->has_section(PluginConfigIOS::PLIST_SECTION)) {
|
||||
List<String> keys;
|
||||
config_file->get_section_keys(PluginConfigIOS::PLIST_SECTION, &keys);
|
||||
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
Vector<String> key_components = keys[i].split(":");
|
||||
|
||||
String key_value = "";
|
||||
PluginConfigIOS::PlistItemType key_type = PluginConfigIOS::PlistItemType::UNKNOWN;
|
||||
|
||||
if (key_components.size() == 1) {
|
||||
key_value = key_components[0];
|
||||
key_type = PluginConfigIOS::PlistItemType::STRING;
|
||||
} else if (key_components.size() == 2) {
|
||||
key_value = key_components[0];
|
||||
|
||||
if (key_components[1].to_lower() == "string") {
|
||||
key_type = PluginConfigIOS::PlistItemType::STRING;
|
||||
} else if (key_components[1].to_lower() == "integer") {
|
||||
key_type = PluginConfigIOS::PlistItemType::INTEGER;
|
||||
} else if (key_components[1].to_lower() == "boolean") {
|
||||
key_type = PluginConfigIOS::PlistItemType::BOOLEAN;
|
||||
} else if (key_components[1].to_lower() == "raw") {
|
||||
key_type = PluginConfigIOS::PlistItemType::RAW;
|
||||
} else if (key_components[1].to_lower() == "string_input") {
|
||||
key_type = PluginConfigIOS::PlistItemType::STRING_INPUT;
|
||||
}
|
||||
}
|
||||
|
||||
if (key_value.is_empty() || key_type == PluginConfigIOS::PlistItemType::UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String value;
|
||||
|
||||
switch (key_type) {
|
||||
case PluginConfigIOS::PlistItemType::STRING: {
|
||||
String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String());
|
||||
value = "<string>" + raw_value + "</string>";
|
||||
} break;
|
||||
case PluginConfigIOS::PlistItemType::INTEGER: {
|
||||
int raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], 0);
|
||||
Dictionary value_dictionary;
|
||||
String value_format = "<integer>$value</integer>";
|
||||
value_dictionary["value"] = raw_value;
|
||||
value = value_format.format(value_dictionary, "$_");
|
||||
} break;
|
||||
case PluginConfigIOS::PlistItemType::BOOLEAN:
|
||||
if (config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], false)) {
|
||||
value = "<true/>";
|
||||
} else {
|
||||
value = "<false/>";
|
||||
}
|
||||
break;
|
||||
case PluginConfigIOS::PlistItemType::RAW: {
|
||||
String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String());
|
||||
value = raw_value;
|
||||
} break;
|
||||
case PluginConfigIOS::PlistItemType::STRING_INPUT: {
|
||||
String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String());
|
||||
value = raw_value;
|
||||
} break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
plugin_config.plist[key_value] = PluginConfigIOS::PlistItem{ key_type, value };
|
||||
}
|
||||
}
|
||||
|
||||
if (validate_plugin(plugin_config)) {
|
||||
plugin_config.last_updated = get_plugin_modification_time(plugin_config, path);
|
||||
}
|
||||
|
||||
return plugin_config;
|
||||
}
|
||||
132
platform/ios/export/godot_plugin_config.h
Normal file
132
platform/ios/export/godot_plugin_config.h
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/*************************************************************************/
|
||||
/* godot_plugin_config.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef IOS_GODOT_PLUGIN_CONFIG_H
|
||||
#define IOS_GODOT_PLUGIN_CONFIG_H
|
||||
|
||||
#include "core/error/error_list.h"
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
/*
|
||||
The `config` section and fields are required and defined as follow:
|
||||
- **name**: name of the plugin
|
||||
- **binary**: path to static `.a` library
|
||||
|
||||
The `dependencies` and fields are optional.
|
||||
- **linked**: dependencies that should only be linked.
|
||||
- **embedded**: dependencies that should be linked and embedded into application.
|
||||
- **system**: system dependencies that should be linked.
|
||||
- **capabilities**: capabilities that would be used for `UIRequiredDeviceCapabilities` options in Info.plist file.
|
||||
- **files**: files that would be copied into application
|
||||
|
||||
The `plist` section are optional.
|
||||
- **key**: key and value that would be added in Info.plist file.
|
||||
*/
|
||||
|
||||
struct PluginConfigIOS {
|
||||
inline static const char *PLUGIN_CONFIG_EXT = ".gdip";
|
||||
|
||||
inline static const char *CONFIG_SECTION = "config";
|
||||
inline static const char *CONFIG_NAME_KEY = "name";
|
||||
inline static const char *CONFIG_BINARY_KEY = "binary";
|
||||
inline static const char *CONFIG_INITIALIZE_KEY = "initialization";
|
||||
inline static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization";
|
||||
|
||||
inline static const char *DEPENDENCIES_SECTION = "dependencies";
|
||||
inline static const char *DEPENDENCIES_LINKED_KEY = "linked";
|
||||
inline static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded";
|
||||
inline static const char *DEPENDENCIES_SYSTEM_KEY = "system";
|
||||
inline static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities";
|
||||
inline static const char *DEPENDENCIES_FILES_KEY = "files";
|
||||
inline static const char *DEPENDENCIES_LINKER_FLAGS = "linker_flags";
|
||||
|
||||
inline static const char *PLIST_SECTION = "plist";
|
||||
|
||||
enum PlistItemType {
|
||||
UNKNOWN,
|
||||
STRING,
|
||||
INTEGER,
|
||||
BOOLEAN,
|
||||
RAW,
|
||||
STRING_INPUT,
|
||||
};
|
||||
|
||||
struct PlistItem {
|
||||
PlistItemType type;
|
||||
String value;
|
||||
};
|
||||
|
||||
// Set to true when the config file is properly loaded.
|
||||
bool valid_config = false;
|
||||
bool supports_targets = false;
|
||||
// Unix timestamp of last change to this plugin.
|
||||
uint64_t last_updated = 0;
|
||||
|
||||
// Required config section
|
||||
String name;
|
||||
String binary;
|
||||
String initialization_method;
|
||||
String deinitialization_method;
|
||||
|
||||
// Optional dependencies section
|
||||
Vector<String> linked_dependencies;
|
||||
Vector<String> embedded_dependencies;
|
||||
Vector<String> system_dependencies;
|
||||
|
||||
Vector<String> files_to_copy;
|
||||
Vector<String> capabilities;
|
||||
|
||||
Vector<String> linker_flags;
|
||||
|
||||
// Optional plist section
|
||||
// String value is default value.
|
||||
// Currently supports `string`, `boolean`, `integer`, `raw`, `string_input` types
|
||||
// <name>:<type> = <value>
|
||||
HashMap<String, PlistItem> plist;
|
||||
|
||||
static String resolve_local_dependency_path(String plugin_config_dir, String dependency_path);
|
||||
|
||||
static String resolve_system_dependency_path(String dependency_path);
|
||||
|
||||
static Vector<String> resolve_local_dependencies(String plugin_config_dir, Vector<String> p_paths);
|
||||
|
||||
static Vector<String> resolve_system_dependencies(Vector<String> p_paths);
|
||||
|
||||
static bool validate_plugin(PluginConfigIOS &plugin_config);
|
||||
|
||||
static String get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug);
|
||||
|
||||
static uint64_t get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path);
|
||||
|
||||
static PluginConfigIOS load_plugin_config(Ref<ConfigFile> config_file, const String &path);
|
||||
};
|
||||
|
||||
#endif // GODOT_PLUGIN_CONFIG_H
|
||||
41
platform/ios/godot_app_delegate.h
Normal file
41
platform/ios/godot_app_delegate.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*************************************************************************/
|
||||
/* godot_app_delegate.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
typedef NSObject<UIApplicationDelegate> ApplicationDelegateService;
|
||||
|
||||
@interface GodotApplicalitionDelegate : NSObject <UIApplicationDelegate>
|
||||
|
||||
@property(class, readonly, strong) NSArray<ApplicationDelegateService *> *services;
|
||||
|
||||
+ (void)addService:(ApplicationDelegateService *)service;
|
||||
|
||||
@end
|
||||
467
platform/ios/godot_app_delegate.m
Normal file
467
platform/ios/godot_app_delegate.m
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
/*************************************************************************/
|
||||
/* godot_app_delegate.m */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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_app_delegate.h"
|
||||
|
||||
#import "app_delegate.h"
|
||||
|
||||
@interface GodotApplicalitionDelegate ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation GodotApplicalitionDelegate
|
||||
|
||||
static NSMutableArray<ApplicationDelegateService *> *services = nil;
|
||||
|
||||
+ (NSArray<ApplicationDelegateService *> *)services {
|
||||
return services;
|
||||
}
|
||||
|
||||
+ (void)load {
|
||||
services = [NSMutableArray new];
|
||||
[services addObject:[AppDelegate new]];
|
||||
}
|
||||
|
||||
+ (void)addService:(ApplicationDelegateService *)service {
|
||||
if (!services || !service) {
|
||||
return;
|
||||
}
|
||||
[services addObject:service];
|
||||
}
|
||||
|
||||
// UIApplicationDelegate documentation can be found here: https://developer.apple.com/documentation/uikit/uiapplicationdelegate
|
||||
|
||||
// MARK: Window
|
||||
|
||||
- (UIWindow *)window {
|
||||
UIWindow *result = nil;
|
||||
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UIWindow *value = [service window];
|
||||
|
||||
if (value) {
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// MARK: Initializing
|
||||
|
||||
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
|
||||
BOOL result = NO;
|
||||
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([service application:application willFinishLaunchingWithOptions:launchOptions]) {
|
||||
result = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
|
||||
BOOL result = NO;
|
||||
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([service application:application didFinishLaunchingWithOptions:launchOptions]) {
|
||||
result = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Can be handled by Info.plist. Not yet supported by Godot.
|
||||
|
||||
// MARK: Scene
|
||||
|
||||
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {}
|
||||
|
||||
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {}
|
||||
|
||||
*/
|
||||
|
||||
// MARK: Life-Cycle
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service applicationDidBecomeActive:application];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service applicationWillResignActive:application];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service applicationDidEnterBackground:application];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service applicationWillEnterForeground:application];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service applicationWillTerminate:application];
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Environment Changes
|
||||
|
||||
- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service applicationProtectedDataDidBecomeAvailable:application];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service applicationProtectedDataWillBecomeUnavailable:application];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service applicationDidReceiveMemoryWarning:application];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationSignificantTimeChange:(UIApplication *)application {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service applicationSignificantTimeChange:application];
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: App State Restoration
|
||||
|
||||
- (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
|
||||
BOOL result = NO;
|
||||
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([service application:application shouldSaveSecureApplicationState:coder]) {
|
||||
result = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
|
||||
BOOL result = NO;
|
||||
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([service application:application shouldRestoreSecureApplicationState:coder]) {
|
||||
result = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UIViewController *controller = [service application:application viewControllerWithRestorationIdentifierPath:identifierComponents coder:coder];
|
||||
|
||||
if (controller) {
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service application:application willEncodeRestorableStateWithCoder:coder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service application:application didDecodeRestorableStateWithCoder:coder];
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Download Data in Background
|
||||
|
||||
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler];
|
||||
}
|
||||
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
// MARK: Remote Notification
|
||||
|
||||
// Moved to the iOS Plugin
|
||||
|
||||
// MARK: User Activity and Handling Quick Actions
|
||||
|
||||
- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType {
|
||||
BOOL result = NO;
|
||||
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([service application:application willContinueUserActivityWithType:userActivityType]) {
|
||||
result = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler {
|
||||
BOOL result = NO;
|
||||
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([service application:application continueUserActivity:userActivity restorationHandler:restorationHandler]) {
|
||||
result = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service application:application didUpdateUserActivity:userActivity];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service application:application didFailToContinueUserActivityWithType:userActivityType error:error];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler];
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: WatchKit
|
||||
|
||||
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service application:application handleWatchKitExtensionRequest:userInfo reply:reply];
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: HealthKit
|
||||
|
||||
- (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service applicationShouldRequestHealthAuthorization:application];
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Opening an URL
|
||||
|
||||
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([service application:app openURL:url options:options]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
// MARK: Disallowing Specified App Extension Types
|
||||
|
||||
- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier {
|
||||
BOOL result = NO;
|
||||
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([service application:application shouldAllowExtensionPointIdentifier:extensionPointIdentifier]) {
|
||||
result = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// MARK: SiriKit
|
||||
|
||||
- (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
id result = [service application:application handlerForIntent:intent];
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
// MARK: CloudKit
|
||||
|
||||
- (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata {
|
||||
for (ApplicationDelegateService *service in services) {
|
||||
if (![service respondsToSelector:_cmd]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[service application:application userDidAcceptCloudKitShareWithMetadata:cloudKitShareMetadata];
|
||||
}
|
||||
}
|
||||
|
||||
/* Handled By Info.plist file for now
|
||||
|
||||
// MARK: Interface Geometry
|
||||
|
||||
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {}
|
||||
|
||||
*/
|
||||
|
||||
@end
|
||||
131
platform/ios/godot_ios.mm
Normal file
131
platform/ios/godot_ios.mm
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/*************************************************************************/
|
||||
/* godot_ios.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/string/ustring.h"
|
||||
#include "main/main.h"
|
||||
#include "os_ios.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static OS_IOS *os = nullptr;
|
||||
|
||||
int add_path(int, char **);
|
||||
int add_cmdline(int, char **);
|
||||
int ios_main(int, char **, String);
|
||||
|
||||
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] = nullptr;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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] = nullptr;
|
||||
|
||||
return p_argc;
|
||||
}
|
||||
|
||||
int ios_main(int argc, char **argv, String data_dir, String cache_dir) {
|
||||
size_t len = strlen(argv[0]);
|
||||
|
||||
while (len--) {
|
||||
if (argv[0][len] == '/') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len >= 0) {
|
||||
char path[512];
|
||||
memcpy(path, argv[0], len > sizeof(path) ? sizeof(path) : len);
|
||||
path[len] = 0;
|
||||
printf("Path: %s\n", path);
|
||||
chdir(path);
|
||||
}
|
||||
|
||||
printf("godot_ios %s\n", argv[0]);
|
||||
char cwd[512];
|
||||
getcwd(cwd, sizeof(cwd));
|
||||
printf("cwd %s\n", cwd);
|
||||
os = new OS_IOS(data_dir, cache_dir);
|
||||
|
||||
// We must override main when testing is enabled
|
||||
TEST_MAIN_OVERRIDE
|
||||
|
||||
char *fargv[64];
|
||||
for (int i = 0; i < argc; i++) {
|
||||
fargv[i] = argv[i];
|
||||
}
|
||||
fargv[argc] = nullptr;
|
||||
argc = add_path(argc, fargv);
|
||||
argc = add_cmdline(argc, fargv);
|
||||
|
||||
printf("os created\n");
|
||||
|
||||
Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
|
||||
printf("setup %i\n", err);
|
||||
|
||||
if (err == ERR_HELP) { // Returned by --help and --version, so success.
|
||||
return 0;
|
||||
} else if (err != OK) {
|
||||
return 255;
|
||||
}
|
||||
|
||||
os->initialize_modules();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ios_finish() {
|
||||
printf("ios_finish\n");
|
||||
Main::cleanup();
|
||||
delete os;
|
||||
}
|
||||
67
platform/ios/godot_view.h
Normal file
67
platform/ios/godot_view.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*************************************************************************/
|
||||
/* godot_view.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
class String;
|
||||
|
||||
@class GodotView;
|
||||
@protocol DisplayLayer;
|
||||
@protocol GodotViewRendererProtocol;
|
||||
|
||||
@protocol GodotViewDelegate
|
||||
|
||||
- (BOOL)godotViewFinishedSetup:(GodotView *)view;
|
||||
|
||||
@end
|
||||
|
||||
@interface GodotView : UIView
|
||||
|
||||
@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
|
||||
@property(assign, nonatomic) id<GodotViewDelegate> delegate;
|
||||
|
||||
@property(assign, readonly, nonatomic) BOOL isActive;
|
||||
|
||||
@property(assign, nonatomic) BOOL useCADisplayLink;
|
||||
@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer;
|
||||
@property(assign, readonly, nonatomic) BOOL canRender;
|
||||
|
||||
@property(assign, nonatomic) NSTimeInterval renderingInterval;
|
||||
|
||||
- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
|
||||
- (void)stopRendering;
|
||||
- (void)startRendering;
|
||||
|
||||
- (void)godotTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
|
||||
- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
|
||||
- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
|
||||
- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
|
||||
|
||||
@end
|
||||
481
platform/ios/godot_view.mm
Normal file
481
platform/ios/godot_view.mm
Normal file
|
|
@ -0,0 +1,481 @@
|
|||
/*************************************************************************/
|
||||
/* godot_view.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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.h"
|
||||
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/string/ustring.h"
|
||||
#import "display_layer.h"
|
||||
#include "display_server_ios.h"
|
||||
#import "godot_view_gesture_recognizer.h"
|
||||
#import "godot_view_renderer.h"
|
||||
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
|
||||
static const int max_touches = 8;
|
||||
static const float earth_gravity = 9.80665;
|
||||
|
||||
@interface GodotView () {
|
||||
UITouch *godot_touches[max_touches];
|
||||
}
|
||||
|
||||
@property(assign, nonatomic) BOOL isActive;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer;
|
||||
|
||||
@property(strong, nonatomic) CMMotionManager *motionManager;
|
||||
|
||||
@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GodotView
|
||||
|
||||
- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
|
||||
if (self.renderingLayer) {
|
||||
return self.renderingLayer;
|
||||
}
|
||||
|
||||
CALayer<DisplayLayer> *layer;
|
||||
|
||||
if ([driverName isEqualToString:@"vulkan"]) {
|
||||
layer = [GodotMetalLayer layer];
|
||||
} else if ([driverName isEqualToString:@"opengl_es"]) {
|
||||
if (@available(iOS 13, *)) {
|
||||
NSLog(@"OpenGL ES is deprecated on iOS 13");
|
||||
}
|
||||
#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
|
||||
return nil;
|
||||
#else
|
||||
layer = [GodotOpenGLLayer layer];
|
||||
#endif
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
layer.frame = self.bounds;
|
||||
layer.contentsScale = self.contentScaleFactor;
|
||||
|
||||
[self.layer addSublayer:layer];
|
||||
self.renderingLayer = layer;
|
||||
|
||||
[layer initializeDisplayLayer];
|
||||
|
||||
return self.renderingLayer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
|
||||
if (self) {
|
||||
[self godot_commonInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
if (self) {
|
||||
[self godot_commonInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stopRendering];
|
||||
|
||||
self.renderer = nil;
|
||||
self.delegate = nil;
|
||||
|
||||
if (self.renderingLayer) {
|
||||
[self.renderingLayer removeFromSuperlayer];
|
||||
self.renderingLayer = nil;
|
||||
}
|
||||
|
||||
if (self.motionManager) {
|
||||
[self.motionManager stopDeviceMotionUpdates];
|
||||
self.motionManager = nil;
|
||||
}
|
||||
|
||||
if (self.displayLink) {
|
||||
[self.displayLink invalidate];
|
||||
self.displayLink = nil;
|
||||
}
|
||||
|
||||
if (self.animationTimer) {
|
||||
[self.animationTimer invalidate];
|
||||
self.animationTimer = nil;
|
||||
}
|
||||
|
||||
if (self.delayGestureRecognizer) {
|
||||
self.delayGestureRecognizer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)godot_commonInit {
|
||||
self.contentScaleFactor = [UIScreen mainScreen].nativeScale;
|
||||
|
||||
[self initTouches];
|
||||
|
||||
self.multipleTouchEnabled = YES;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize delay gesture recognizer
|
||||
GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init];
|
||||
self.delayGestureRecognizer = gestureRecognizer;
|
||||
[self addGestureRecognizer:self.delayGestureRecognizer];
|
||||
}
|
||||
|
||||
- (void)stopRendering {
|
||||
if (!self.isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.isActive = NO;
|
||||
|
||||
printf("******** stop animation!\n");
|
||||
|
||||
if (self.useCADisplayLink) {
|
||||
[self.displayLink invalidate];
|
||||
self.displayLink = nil;
|
||||
} else {
|
||||
[self.animationTimer invalidate];
|
||||
self.animationTimer = nil;
|
||||
}
|
||||
|
||||
[self clearTouches];
|
||||
}
|
||||
|
||||
- (void)startRendering {
|
||||
if (self.isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.isActive = YES;
|
||||
|
||||
printf("start animation!\n");
|
||||
|
||||
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 / self.renderingInterval);
|
||||
|
||||
self.displayLink.preferredFramesPerSecond = displayFPS;
|
||||
|
||||
// Setup DisplayLink in main thread
|
||||
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
|
||||
} else {
|
||||
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)drawView {
|
||||
if (!self.isActive) {
|
||||
printf("draw view not active!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.useCADisplayLink) {
|
||||
// Pause the CADisplayLink to avoid recursion
|
||||
[self.displayLink setPaused:YES];
|
||||
|
||||
// Process all input events
|
||||
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) {
|
||||
// Continue.
|
||||
}
|
||||
|
||||
// We are good to go, resume the CADisplayLink
|
||||
[self.displayLink setPaused:NO];
|
||||
}
|
||||
|
||||
[self.renderingLayer renderDisplayLayer];
|
||||
|
||||
if (!self.renderer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([self.renderer setupView:self]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.delegate) {
|
||||
BOOL delegateFinishedSetup = [self.delegate godotViewFinishedSetup:self];
|
||||
|
||||
if (!delegateFinishedSetup) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[self handleMotion];
|
||||
[self.renderer renderOnView:self];
|
||||
}
|
||||
|
||||
- (BOOL)canRender {
|
||||
if (self.useCADisplayLink) {
|
||||
return self.displayLink != nil;
|
||||
} else {
|
||||
return self.animationTimer != nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
|
||||
_renderingInterval = renderingInterval;
|
||||
|
||||
if (self.canRender) {
|
||||
[self stopRendering];
|
||||
[self startRendering];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
if (self.renderingLayer) {
|
||||
self.renderingLayer.frame = self.bounds;
|
||||
[self.renderingLayer layoutDisplayLayer];
|
||||
|
||||
if (DisplayServerIOS::get_singleton()) {
|
||||
DisplayServerIOS::get_singleton()->resize_window(self.bounds.size);
|
||||
}
|
||||
}
|
||||
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
// MARK: - Input
|
||||
|
||||
// MARK: Touches
|
||||
|
||||
- (void)initTouches {
|
||||
for (int i = 0; i < max_touches; i++) {
|
||||
godot_touches[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
- (int)getTouchIDForTouch:(UITouch *)p_touch {
|
||||
int first = -1;
|
||||
for (int i = 0; i < max_touches; i++) {
|
||||
if (first == -1 && godot_touches[i] == nullptr) {
|
||||
first = i;
|
||||
continue;
|
||||
}
|
||||
if (godot_touches[i] == p_touch) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
if (first != -1) {
|
||||
godot_touches[first] = p_touch;
|
||||
return first;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (int)removeTouch:(UITouch *)p_touch {
|
||||
int remaining = 0;
|
||||
for (int i = 0; i < max_touches; i++) {
|
||||
if (godot_touches[i] == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (godot_touches[i] == p_touch) {
|
||||
godot_touches[i] = nullptr;
|
||||
} else {
|
||||
++remaining;
|
||||
}
|
||||
}
|
||||
return remaining;
|
||||
}
|
||||
|
||||
- (void)clearTouches {
|
||||
for (int i = 0; i < max_touches; i++) {
|
||||
godot_touches[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)godotTouchesBegan:(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];
|
||||
DisplayServerIOS::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)godotTouchesMoved:(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];
|
||||
DisplayServerIOS::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)godotTouchesEnded:(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];
|
||||
DisplayServerIOS::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)godotTouchesCancelled:(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);
|
||||
DisplayServerIOS::get_singleton()->touches_cancelled(tid);
|
||||
}
|
||||
}
|
||||
[self clearTouches];
|
||||
}
|
||||
|
||||
// MARK: Motion
|
||||
|
||||
- (void)handleMotion {
|
||||
if (!self.motionManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 = self.motionManager.deviceMotion.gravity;
|
||||
CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration;
|
||||
|
||||
// To be consistent with Android we convert the unit of measurement from g (Earth's gravity)
|
||||
// to m/s^2.
|
||||
gravity.x *= earth_gravity;
|
||||
gravity.y *= earth_gravity;
|
||||
gravity.z *= earth_gravity;
|
||||
acceleration.x *= earth_gravity;
|
||||
acceleration.y *= earth_gravity;
|
||||
acceleration.z *= earth_gravity;
|
||||
|
||||
///@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;
|
||||
|
||||
///@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;
|
||||
|
||||
// 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)
|
||||
|
||||
UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
|
||||
|
||||
if (@available(iOS 13, *)) {
|
||||
interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
|
||||
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
|
||||
} else {
|
||||
interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
|
||||
#endif
|
||||
}
|
||||
|
||||
switch (interfaceOrientation) {
|
||||
case UIInterfaceOrientationLandscapeLeft: {
|
||||
DisplayServerIOS::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z);
|
||||
DisplayServerIOS::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z);
|
||||
DisplayServerIOS::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z);
|
||||
DisplayServerIOS::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z);
|
||||
} break;
|
||||
case UIInterfaceOrientationLandscapeRight: {
|
||||
DisplayServerIOS::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z);
|
||||
DisplayServerIOS::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z);
|
||||
DisplayServerIOS::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z);
|
||||
DisplayServerIOS::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z);
|
||||
} break;
|
||||
case UIInterfaceOrientationPortraitUpsideDown: {
|
||||
DisplayServerIOS::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z);
|
||||
DisplayServerIOS::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z);
|
||||
DisplayServerIOS::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z);
|
||||
DisplayServerIOS::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z);
|
||||
} break;
|
||||
default: { // assume portrait
|
||||
DisplayServerIOS::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z);
|
||||
DisplayServerIOS::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z);
|
||||
DisplayServerIOS::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z);
|
||||
DisplayServerIOS::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
46
platform/ios/godot_view_gesture_recognizer.h
Normal file
46
platform/ios/godot_view_gesture_recognizer.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*************************************************************************/
|
||||
/* godot_view_gesture_recognizer.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
// GLViewGestureRecognizer allows iOS gestures to work correctly by
|
||||
// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer.
|
||||
// It catches all gestures incoming to UIView and delays them for 150ms
|
||||
// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer)
|
||||
// If touch cancellation or end message is fired it fires delayed
|
||||
// begin touch immediately as well as last touch signal
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface GodotViewGestureRecognizer : UIGestureRecognizer
|
||||
|
||||
@property(nonatomic, readonly, assign) NSTimeInterval delayTimeInterval;
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
@end
|
||||
186
platform/ios/godot_view_gesture_recognizer.mm
Normal file
186
platform/ios/godot_view_gesture_recognizer.mm
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
/*************************************************************************/
|
||||
/* godot_view_gesture_recognizer.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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_gesture_recognizer.h"
|
||||
|
||||
#import "godot_view.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
|
||||
// Minimum distance for touches to move to fire
|
||||
// a delay timer before scheduled time.
|
||||
// Should be the low enough to not cause issues with dragging
|
||||
// but big enough to allow click to work.
|
||||
const CGFloat kGLGestureMovementDistance = 0.5;
|
||||
|
||||
@interface GodotViewGestureRecognizer ()
|
||||
|
||||
@property(nonatomic, readwrite, assign) NSTimeInterval delayTimeInterval;
|
||||
|
||||
@end
|
||||
|
||||
@interface GodotViewGestureRecognizer ()
|
||||
|
||||
// Timer used to delay begin touch message.
|
||||
// Should work as simple emulation of UIDelayedAction
|
||||
@property(strong, nonatomic) NSTimer *delayTimer;
|
||||
|
||||
// Delayed touch parameters
|
||||
@property(strong, nonatomic) NSSet *delayedTouches;
|
||||
@property(strong, nonatomic) UIEvent *delayedEvent;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GodotViewGestureRecognizer
|
||||
|
||||
- (GodotView *)godotView {
|
||||
return (GodotView *)self.view;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
|
||||
self.cancelsTouchesInView = YES;
|
||||
self.delaysTouchesBegan = YES;
|
||||
self.delaysTouchesEnded = YES;
|
||||
self.requiresExclusiveTouchType = NO;
|
||||
|
||||
self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay");
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (self.delayTimer) {
|
||||
[self.delayTimer invalidate];
|
||||
self.delayTimer = nil;
|
||||
}
|
||||
|
||||
if (self.delayedTouches) {
|
||||
self.delayedTouches = nil;
|
||||
}
|
||||
|
||||
if (self.delayedEvent) {
|
||||
self.delayedEvent = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event {
|
||||
[self.delayTimer fire];
|
||||
|
||||
self.delayedTouches = touches;
|
||||
self.delayedEvent = event;
|
||||
|
||||
self.delayTimer = [NSTimer
|
||||
scheduledTimerWithTimeInterval:self.delayTimeInterval
|
||||
target:self
|
||||
selector:@selector(fireDelayedTouches:)
|
||||
userInfo:nil
|
||||
repeats:NO];
|
||||
}
|
||||
|
||||
- (void)fireDelayedTouches:(id)timer {
|
||||
[self.delayTimer invalidate];
|
||||
self.delayTimer = nil;
|
||||
|
||||
if (self.delayedTouches) {
|
||||
[self.godotView godotTouchesBegan:self.delayedTouches withEvent:self.delayedEvent];
|
||||
}
|
||||
|
||||
self.delayedTouches = nil;
|
||||
self.delayedEvent = nil;
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan];
|
||||
[self delayTouches:cleared andEvent:event];
|
||||
|
||||
[super touchesBegan:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseMoved];
|
||||
|
||||
if (self.delayTimer) {
|
||||
// We should check if movement was significant enough to fire an event
|
||||
// for dragging to work correctly.
|
||||
for (UITouch *touch in cleared) {
|
||||
CGPoint from = [touch locationInView:self.godotView];
|
||||
CGPoint to = [touch previousLocationInView:self.godotView];
|
||||
CGFloat xDistance = from.x - to.x;
|
||||
CGFloat yDistance = from.y - to.y;
|
||||
|
||||
CGFloat distance = sqrt(xDistance * xDistance + yDistance * yDistance);
|
||||
|
||||
// Early exit, since one of touches has moved enough to fire a drag event.
|
||||
if (distance > kGLGestureMovementDistance) {
|
||||
[self.delayTimer fire];
|
||||
[self.godotView godotTouchesMoved:cleared withEvent:event];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[self.godotView godotTouchesMoved:cleared withEvent:event];
|
||||
|
||||
[super touchesMoved:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
[self.delayTimer fire];
|
||||
|
||||
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
|
||||
[self.godotView godotTouchesEnded:cleared withEvent:event];
|
||||
|
||||
[super touchesEnded:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
[self.delayTimer fire];
|
||||
[self.godotView godotTouchesCancelled:touches withEvent:event];
|
||||
|
||||
[super touchesCancelled:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave {
|
||||
NSMutableSet *cleared = [touches mutableCopy];
|
||||
|
||||
for (UITouch *touch in touches) {
|
||||
if (touch.view != self.view || touch.phase != phaseToSave) {
|
||||
[cleared removeObject:touch];
|
||||
}
|
||||
}
|
||||
|
||||
return cleared;
|
||||
}
|
||||
|
||||
@end
|
||||
44
platform/ios/godot_view_renderer.h
Normal file
44
platform/ios/godot_view_renderer.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/*************************************************************************/
|
||||
/* godot_view_renderer.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol GodotViewRendererProtocol <NSObject>
|
||||
|
||||
@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
|
||||
|
||||
- (BOOL)setupView:(UIView *)view;
|
||||
- (void)renderOnView:(UIView *)view;
|
||||
|
||||
@end
|
||||
|
||||
@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol>
|
||||
|
||||
@end
|
||||
118
platform/ios/godot_view_renderer.mm
Normal file
118
platform/ios/godot_view_renderer.mm
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/*************************************************************************/
|
||||
/* godot_view_renderer.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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/config/project_settings.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#import "display_server_ios.h"
|
||||
#include "main/main.h"
|
||||
#include "os_ios.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
#import <AudioToolbox/AudioServices.h>
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#import <GameController/GameController.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface GodotViewRenderer ()
|
||||
|
||||
@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
|
||||
@property(assign, nonatomic) BOOL hasStartedMain;
|
||||
@property(assign, nonatomic) BOOL hasFinishedSetup;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GodotViewRenderer
|
||||
|
||||
- (BOOL)setupView:(UIView *)view {
|
||||
if (self.hasFinishedSetup) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!OS::get_singleton()) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (!self.hasFinishedProjectDataSetup) {
|
||||
[self setupProjectData];
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (!self.hasStartedMain) {
|
||||
self.hasStartedMain = YES;
|
||||
OS_IOS::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 (!OS_IOS::get_singleton()) {
|
||||
return;
|
||||
}
|
||||
|
||||
OS_IOS::get_singleton()->iterate();
|
||||
}
|
||||
|
||||
@end
|
||||
61
platform/ios/ios.h
Normal file
61
platform/ios/ios.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/*************************************************************************/
|
||||
/* ios.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef IOS_H
|
||||
#define IOS_H
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#import <CoreHaptics/CoreHaptics.h>
|
||||
|
||||
class iOS : public Object {
|
||||
GDCLASS(iOS, Object);
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = nullptr;
|
||||
|
||||
CHHapticEngine *get_haptic_engine_instance() API_AVAILABLE(ios(13));
|
||||
void start_haptic_engine();
|
||||
void stop_haptic_engine();
|
||||
|
||||
public:
|
||||
static void alert(const char *p_alert, const char *p_title);
|
||||
|
||||
bool supports_haptic_engine();
|
||||
void vibrate_haptic_engine(float p_duration_seconds);
|
||||
|
||||
String get_model() const;
|
||||
String get_rate_url(int p_app_id) const;
|
||||
|
||||
iOS();
|
||||
};
|
||||
|
||||
#endif
|
||||
180
platform/ios/ios.mm
Normal file
180
platform/ios/ios.mm
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
/*************************************************************************/
|
||||
/* ios.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "ios.h"
|
||||
|
||||
#import "app_delegate.h"
|
||||
#import "view_controller.h"
|
||||
|
||||
#import <CoreHaptics/CoreHaptics.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
void iOS::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);
|
||||
ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &iOS::supports_haptic_engine);
|
||||
ClassDB::bind_method(D_METHOD("start_haptic_engine"), &iOS::start_haptic_engine);
|
||||
ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &iOS::stop_haptic_engine);
|
||||
};
|
||||
|
||||
bool iOS::supports_haptic_engine() {
|
||||
if (@available(iOS 13, *)) {
|
||||
id<CHHapticDeviceCapability> capabilities = [CHHapticEngine capabilitiesForHardware];
|
||||
return capabilities.supportsHaptics;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
CHHapticEngine *iOS::get_haptic_engine_instance() API_AVAILABLE(ios(13)) {
|
||||
if (haptic_engine == nullptr) {
|
||||
NSError *error = nullptr;
|
||||
haptic_engine = [[CHHapticEngine alloc] initAndReturnError:&error];
|
||||
|
||||
if (!error) {
|
||||
[haptic_engine setAutoShutdownEnabled:true];
|
||||
} else {
|
||||
haptic_engine = nullptr;
|
||||
NSLog(@"Could not initialize haptic engine: %@", error);
|
||||
}
|
||||
}
|
||||
|
||||
return haptic_engine;
|
||||
}
|
||||
|
||||
void iOS::vibrate_haptic_engine(float p_duration_seconds) API_AVAILABLE(ios(13)) {
|
||||
if (@available(iOS 13, *)) { // We need the @available check every time to make the compiler happy...
|
||||
if (supports_haptic_engine()) {
|
||||
CHHapticEngine *haptic_engine = get_haptic_engine_instance();
|
||||
if (haptic_engine) {
|
||||
NSDictionary *hapticDict = @{
|
||||
CHHapticPatternKeyPattern : @[
|
||||
@{CHHapticPatternKeyEvent : @{
|
||||
CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous,
|
||||
CHHapticPatternKeyTime : @(CHHapticTimeImmediate),
|
||||
CHHapticPatternKeyEventDuration : @(p_duration_seconds)
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
NSError *error;
|
||||
CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error];
|
||||
|
||||
[[haptic_engine createPlayerWithPattern:pattern error:&error] startAtTime:0 error:&error];
|
||||
|
||||
NSLog(@"Could not vibrate using haptic engine: %@", error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NSLog(@"Haptic engine is not supported in this version of iOS");
|
||||
}
|
||||
|
||||
void iOS::start_haptic_engine() {
|
||||
if (@available(iOS 13, *)) {
|
||||
if (supports_haptic_engine()) {
|
||||
CHHapticEngine *haptic_engine = get_haptic_engine_instance();
|
||||
if (haptic_engine) {
|
||||
[haptic_engine startWithCompletionHandler:^(NSError *returnedError) {
|
||||
if (returnedError) {
|
||||
NSLog(@"Could not start haptic engine: %@", returnedError);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NSLog(@"Haptic engine is not supported in this version of iOS");
|
||||
}
|
||||
|
||||
void iOS::stop_haptic_engine() {
|
||||
if (@available(iOS 13, *)) {
|
||||
if (supports_haptic_engine()) {
|
||||
CHHapticEngine *haptic_engine = get_haptic_engine_instance();
|
||||
if (haptic_engine) {
|
||||
[haptic_engine stopWithCompletionHandler:^(NSError *returnedError) {
|
||||
if (returnedError) {
|
||||
NSLog(@"Could not stop haptic engine: %@", returnedError);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NSLog(@"Haptic engine is not supported in this version of iOS");
|
||||
}
|
||||
|
||||
void iOS::alert(const char *p_alert, const char *p_title) {
|
||||
NSString *title = [NSString stringWithUTF8String:p_title];
|
||||
NSString *message = [NSString stringWithUTF8String:p_alert];
|
||||
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK"
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(id){
|
||||
}];
|
||||
|
||||
[alert addAction:button];
|
||||
|
||||
[AppDelegate.viewController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
String iOS::get_model() const {
|
||||
// [[UIDevice currentDevice] model] only returns "iPad" or "iPhone".
|
||||
size_t size;
|
||||
sysctlbyname("hw.machine", nullptr, &size, nullptr, 0);
|
||||
char *model = (char *)malloc(size);
|
||||
if (model == nullptr) {
|
||||
return "";
|
||||
}
|
||||
sysctlbyname("hw.machine", model, &size, nullptr, 0);
|
||||
NSString *platform = [NSString stringWithCString:model encoding:NSUTF8StringEncoding];
|
||||
free(model);
|
||||
const char *str = [platform UTF8String];
|
||||
return String::utf8(str != nullptr ? str : "");
|
||||
}
|
||||
|
||||
String iOS::get_rate_url(int p_app_id) const {
|
||||
String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID";
|
||||
|
||||
String ret = app_url_path.replace("APP_ID", String::num(p_app_id));
|
||||
|
||||
printf("returning rate url %s\n", ret.utf8().get_data());
|
||||
return ret;
|
||||
}
|
||||
|
||||
iOS::iOS() {}
|
||||
50
platform/ios/joypad_ios.h
Normal file
50
platform/ios/joypad_ios.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/*************************************************************************/
|
||||
/* joypad_ios.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#import <GameController/GameController.h>
|
||||
|
||||
@interface JoypadIOSObserver : NSObject
|
||||
|
||||
- (void)startObserving;
|
||||
- (void)startProcessing;
|
||||
- (void)finishObserving;
|
||||
|
||||
@end
|
||||
|
||||
class JoypadIOS {
|
||||
private:
|
||||
JoypadIOSObserver *observer;
|
||||
|
||||
public:
|
||||
JoypadIOS();
|
||||
~JoypadIOS();
|
||||
|
||||
void start_processing();
|
||||
};
|
||||
344
platform/ios/joypad_ios.mm
Normal file
344
platform/ios/joypad_ios.mm
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
/*************************************************************************/
|
||||
/* joypad_ios.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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_ios.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "drivers/coreaudio/audio_driver_coreaudio.h"
|
||||
#include "main/main.h"
|
||||
|
||||
#import "godot_view.h"
|
||||
|
||||
#include "os_ios.h"
|
||||
|
||||
JoypadIOS::JoypadIOS() {
|
||||
observer = [[JoypadIOSObserver alloc] init];
|
||||
[observer startObserving];
|
||||
}
|
||||
|
||||
JoypadIOS::~JoypadIOS() {
|
||||
if (observer) {
|
||||
[observer finishObserving];
|
||||
observer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void JoypadIOS::start_processing() {
|
||||
if (observer) {
|
||||
[observer startProcessing];
|
||||
}
|
||||
}
|
||||
|
||||
@interface JoypadIOSObserver ()
|
||||
|
||||
@property(assign, nonatomic) BOOL isObserving;
|
||||
@property(assign, nonatomic) BOOL isProcessing;
|
||||
@property(strong, nonatomic) NSMutableDictionary *connectedJoypads;
|
||||
@property(strong, nonatomic) NSMutableArray *joypadsQueue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation JoypadIOSObserver
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
[self godot_commonInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)godot_commonInit {
|
||||
self.isObserving = NO;
|
||||
self.isProcessing = NO;
|
||||
}
|
||||
|
||||
- (void)startProcessing {
|
||||
self.isProcessing = YES;
|
||||
|
||||
for (GCController *controller in self.joypadsQueue) {
|
||||
[self addiOSJoypad:controller];
|
||||
}
|
||||
|
||||
[self.joypadsQueue removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)startObserving {
|
||||
if (self.isObserving) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.isObserving = YES;
|
||||
|
||||
self.connectedJoypads = [NSMutableDictionary dictionary];
|
||||
self.joypadsQueue = [NSMutableArray array];
|
||||
|
||||
// get told when controllers connect, this will be called right away for
|
||||
// already connected controllers
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(controllerWasConnected:)
|
||||
name:GCControllerDidConnectNotification
|
||||
object:nil];
|
||||
|
||||
// get told when controllers disconnect
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(controllerWasDisconnected:)
|
||||
name:GCControllerDidDisconnectNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)finishObserving {
|
||||
if (self.isObserving) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
self.isObserving = NO;
|
||||
self.isProcessing = NO;
|
||||
|
||||
self.connectedJoypads = nil;
|
||||
self.joypadsQueue = nil;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self finishObserving];
|
||||
}
|
||||
|
||||
- (int)getJoyIdForController:(GCController *)controller {
|
||||
NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
|
||||
|
||||
for (NSNumber *key in keys) {
|
||||
int joy_id = [key intValue];
|
||||
return joy_id;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (void)addiOSJoypad:(GCController *)controller {
|
||||
// get a new id for our controller
|
||||
int joy_id = Input::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
|
||||
Input::get_singleton()->joy_connection_changed(joy_id, true, String::utf8([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];
|
||||
Input::get_singleton()->joy_connection_changed(joy_id, false, "");
|
||||
|
||||
// and remove it from our dictionary
|
||||
[self.connectedJoypads removeObjectForKey:key];
|
||||
}
|
||||
}
|
||||
|
||||
- (GCControllerPlayerIndex)getFreePlayerIndex {
|
||||
bool have_player_1 = false;
|
||||
bool have_player_2 = false;
|
||||
bool have_player_3 = false;
|
||||
bool have_player_4 = false;
|
||||
|
||||
if (self.connectedJoypads == nil) {
|
||||
NSArray *keys = [self.connectedJoypads allKeys];
|
||||
for (NSNumber *key in keys) {
|
||||
GCController *controller = [self.connectedJoypads objectForKey:key];
|
||||
if (controller.playerIndex == GCControllerPlayerIndex1) {
|
||||
have_player_1 = true;
|
||||
} else if (controller.playerIndex == GCControllerPlayerIndex2) {
|
||||
have_player_2 = true;
|
||||
} else if (controller.playerIndex == GCControllerPlayerIndex3) {
|
||||
have_player_3 = true;
|
||||
} else if (controller.playerIndex == GCControllerPlayerIndex4) {
|
||||
have_player_4 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!have_player_1) {
|
||||
return GCControllerPlayerIndex1;
|
||||
} else if (!have_player_2) {
|
||||
return GCControllerPlayerIndex2;
|
||||
} else if (!have_player_3) {
|
||||
return GCControllerPlayerIndex3;
|
||||
} else if (!have_player_4) {
|
||||
return GCControllerPlayerIndex4;
|
||||
} else {
|
||||
return GCControllerPlayerIndexUnset;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setControllerInputHandler:(GCController *)controller {
|
||||
// Hook in the callback handler for the correct gamepad profile.
|
||||
// This is a bit of a weird design choice on Apples part.
|
||||
// You need to select the most capable gamepad profile for the
|
||||
// gamepad attached.
|
||||
if (controller.extendedGamepad != nil) {
|
||||
// The extended gamepad profile has all the input you could possibly find on
|
||||
// a gamepad but will only be active if your gamepad actually has all of
|
||||
// these...
|
||||
_weakify(self);
|
||||
_weakify(controller);
|
||||
|
||||
controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) {
|
||||
_strongify(self);
|
||||
_strongify(controller);
|
||||
|
||||
int joy_id = [self getJoyIdForController:controller];
|
||||
|
||||
if (element == gamepad.buttonA) {
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::A,
|
||||
gamepad.buttonA.isPressed);
|
||||
} else if (element == gamepad.buttonB) {
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::B,
|
||||
gamepad.buttonB.isPressed);
|
||||
} else if (element == gamepad.buttonX) {
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::X,
|
||||
gamepad.buttonX.isPressed);
|
||||
} else if (element == gamepad.buttonY) {
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::Y,
|
||||
gamepad.buttonY.isPressed);
|
||||
} else if (element == gamepad.leftShoulder) {
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::LEFT_SHOULDER,
|
||||
gamepad.leftShoulder.isPressed);
|
||||
} else if (element == gamepad.rightShoulder) {
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::RIGHT_SHOULDER,
|
||||
gamepad.rightShoulder.isPressed);
|
||||
} else if (element == gamepad.dpad) {
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP,
|
||||
gamepad.dpad.up.isPressed);
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN,
|
||||
gamepad.dpad.down.isPressed);
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT,
|
||||
gamepad.dpad.left.isPressed);
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT,
|
||||
gamepad.dpad.right.isPressed);
|
||||
}
|
||||
|
||||
if (element == gamepad.leftThumbstick) {
|
||||
float value = gamepad.leftThumbstick.xAxis.value;
|
||||
Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_X, value);
|
||||
value = -gamepad.leftThumbstick.yAxis.value;
|
||||
Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_Y, value);
|
||||
} else if (element == gamepad.rightThumbstick) {
|
||||
float value = gamepad.rightThumbstick.xAxis.value;
|
||||
Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_X, value);
|
||||
value = -gamepad.rightThumbstick.yAxis.value;
|
||||
Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_Y, value);
|
||||
} else if (element == gamepad.leftTrigger) {
|
||||
float value = gamepad.leftTrigger.value;
|
||||
Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_LEFT, value);
|
||||
} else if (element == gamepad.rightTrigger) {
|
||||
float value = gamepad.rightTrigger.value;
|
||||
Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_RIGHT, value);
|
||||
}
|
||||
};
|
||||
} 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) {
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::A,
|
||||
gamepad.buttonA.isPressed);
|
||||
} else if (element == gamepad.buttonX) {
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::X,
|
||||
gamepad.buttonX.isPressed);
|
||||
} else if (element == gamepad.dpad) {
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP,
|
||||
gamepad.dpad.up.isPressed);
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN,
|
||||
gamepad.dpad.down.isPressed);
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT, gamepad.dpad.left.isPressed);
|
||||
Input::get_singleton()->joy_button(joy_id, JoyButton::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
|
||||
37
platform/ios/keyboard_input_view.h
Normal file
37
platform/ios/keyboard_input_view.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*************************************************************************/
|
||||
/* keyboard_input_view.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface GodotKeyboardInputView : UITextView
|
||||
|
||||
- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end;
|
||||
|
||||
@end
|
||||
197
platform/ios/keyboard_input_view.mm
Normal file
197
platform/ios/keyboard_input_view.mm
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
/*************************************************************************/
|
||||
/* keyboard_input_view.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 "keyboard_input_view.h"
|
||||
|
||||
#include "core/os/keyboard.h"
|
||||
#include "display_server_ios.h"
|
||||
#include "os_ios.h"
|
||||
|
||||
@interface GodotKeyboardInputView () <UITextViewDelegate>
|
||||
|
||||
@property(nonatomic, copy) NSString *previousText;
|
||||
@property(nonatomic, assign) NSRange previousSelectedRange;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GodotKeyboardInputView
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
|
||||
if (self) {
|
||||
[self godot_commonInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer {
|
||||
self = [super initWithFrame:frame textContainer:textContainer];
|
||||
|
||||
if (self) {
|
||||
[self godot_commonInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)godot_commonInit {
|
||||
self.hidden = YES;
|
||||
self.delegate = self;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(observeTextChange:)
|
||||
name:UITextViewTextDidChangeNotification
|
||||
object:self];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
self.delegate = nil;
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
// MARK: Keyboard
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end {
|
||||
self.text = existingString;
|
||||
self.previousText = existingString;
|
||||
|
||||
NSInteger safeStartIndex = MAX(start, 0);
|
||||
|
||||
NSRange textRange;
|
||||
|
||||
// Either a simple cursor or a selection.
|
||||
if (end > 0) {
|
||||
textRange = NSMakeRange(safeStartIndex, end - start);
|
||||
} else {
|
||||
textRange = NSMakeRange(safeStartIndex, 0);
|
||||
}
|
||||
|
||||
self.selectedRange = textRange;
|
||||
self.previousSelectedRange = textRange;
|
||||
|
||||
return [self becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (BOOL)resignFirstResponder {
|
||||
self.text = nil;
|
||||
self.previousText = nil;
|
||||
return [super resignFirstResponder];
|
||||
}
|
||||
|
||||
// MARK: OS Messages
|
||||
|
||||
- (void)deleteText:(NSInteger)charactersToDelete {
|
||||
for (int i = 0; i < charactersToDelete; i++) {
|
||||
DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, true);
|
||||
DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, false);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)enterText:(NSString *)substring {
|
||||
String characters;
|
||||
characters.parse_utf8([substring UTF8String]);
|
||||
|
||||
for (int i = 0; i < characters.size(); i++) {
|
||||
int character = characters[i];
|
||||
|
||||
switch (character) {
|
||||
case 10:
|
||||
character = (int)Key::ENTER;
|
||||
break;
|
||||
case 8198:
|
||||
character = (int)Key::SPACE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
DisplayServerIOS::get_singleton()->key((Key)character, true);
|
||||
DisplayServerIOS::get_singleton()->key((Key)character, false);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Observer
|
||||
|
||||
- (void)observeTextChange:(NSNotification *)notification {
|
||||
if (notification.object != self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.previousSelectedRange.length == 0) {
|
||||
// We are deleting all text before cursor if no range was selected.
|
||||
// This way any inserted or changed text will be updated.
|
||||
NSString *substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location];
|
||||
[self deleteText:substringToDelete.length];
|
||||
} else {
|
||||
// If text was previously selected
|
||||
// we are sending only one `backspace`.
|
||||
// It will remove all text from text input.
|
||||
[self deleteText:1];
|
||||
}
|
||||
|
||||
NSString *substringToEnter;
|
||||
|
||||
if (self.selectedRange.length == 0) {
|
||||
// If previous cursor had a selection
|
||||
// we have to calculate an inserted text.
|
||||
if (self.previousSelectedRange.length != 0) {
|
||||
NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length;
|
||||
NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location);
|
||||
NSInteger rangeLength = MAX(0, rangeEnd - rangeStart);
|
||||
|
||||
NSRange calculatedRange;
|
||||
|
||||
if (rangeLength >= 0) {
|
||||
calculatedRange = NSMakeRange(rangeStart, rangeLength);
|
||||
} else {
|
||||
calculatedRange = NSMakeRange(rangeStart, 0);
|
||||
}
|
||||
|
||||
substringToEnter = [self.text substringWithRange:calculatedRange];
|
||||
} else {
|
||||
substringToEnter = [self.text substringToIndex:self.selectedRange.location];
|
||||
}
|
||||
} else {
|
||||
substringToEnter = [self.text substringWithRange:self.selectedRange];
|
||||
}
|
||||
|
||||
[self enterText:substringToEnter];
|
||||
|
||||
self.previousText = self.text;
|
||||
self.previousSelectedRange = self.selectedRange;
|
||||
}
|
||||
|
||||
@end
|
||||
BIN
platform/ios/logo.png
Normal file
BIN
platform/ios/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
56
platform/ios/main.m
Normal file
56
platform/ios/main.m
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*************************************************************************/
|
||||
/* main.m */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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_app_delegate.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int gargc;
|
||||
char **gargv;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
#if defined(VULKAN_ENABLED)
|
||||
//MoltenVK - enable full component swizzling support
|
||||
setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
|
||||
#endif
|
||||
|
||||
printf("*********** main.m\n");
|
||||
gargc = argc;
|
||||
gargv = argv;
|
||||
|
||||
printf("running app main\n");
|
||||
@autoreleasepool {
|
||||
NSString *className = NSStringFromClass([GodotApplicalitionDelegate class]);
|
||||
UIApplicationMain(argc, argv, nil, className);
|
||||
}
|
||||
printf("main done\n");
|
||||
return 0;
|
||||
}
|
||||
124
platform/ios/os_ios.h
Normal file
124
platform/ios/os_ios.h
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/*************************************************************************/
|
||||
/* os_ios.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifdef IOS_ENABLED
|
||||
|
||||
#ifndef OS_IOS_H
|
||||
#define OS_IOS_H
|
||||
|
||||
#include "drivers/coreaudio/audio_driver_coreaudio.h"
|
||||
#include "drivers/unix/os_unix.h"
|
||||
#include "ios.h"
|
||||
#include "joypad_ios.h"
|
||||
#include "servers/audio_server.h"
|
||||
#include "servers/rendering/renderer_compositor.h"
|
||||
|
||||
#if defined(VULKAN_ENABLED)
|
||||
#include "drivers/vulkan/rendering_device_vulkan.h"
|
||||
#include "platform/ios/vulkan_context_ios.h"
|
||||
#endif
|
||||
|
||||
class OS_IOS : public OS_Unix {
|
||||
private:
|
||||
static HashMap<String, void *> dynamic_symbol_lookup_table;
|
||||
friend void register_dynamic_symbol(char *name, void *address);
|
||||
|
||||
AudioDriverCoreAudio audio_driver;
|
||||
|
||||
iOS *ios = nullptr;
|
||||
|
||||
JoypadIOS *joypad_ios = nullptr;
|
||||
|
||||
MainLoop *main_loop = nullptr;
|
||||
|
||||
virtual void initialize_core() override;
|
||||
virtual void initialize() override;
|
||||
|
||||
virtual void initialize_joypads() override {
|
||||
}
|
||||
|
||||
virtual void set_main_loop(MainLoop *p_main_loop) override;
|
||||
virtual MainLoop *get_main_loop() const override;
|
||||
|
||||
virtual void delete_main_loop() override;
|
||||
|
||||
virtual void finalize() override;
|
||||
|
||||
String user_data_dir;
|
||||
String cache_dir;
|
||||
|
||||
bool is_focused = false;
|
||||
|
||||
void deinitialize_modules();
|
||||
|
||||
public:
|
||||
static OS_IOS *get_singleton();
|
||||
|
||||
OS_IOS(String p_data_dir, String p_cache_dir);
|
||||
~OS_IOS();
|
||||
|
||||
void initialize_modules();
|
||||
|
||||
bool iterate();
|
||||
|
||||
void start();
|
||||
|
||||
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
|
||||
|
||||
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
|
||||
virtual Error close_dynamic_library(void *p_library_handle) override;
|
||||
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
|
||||
|
||||
virtual String get_name() const override;
|
||||
virtual String get_model_name() const override;
|
||||
|
||||
virtual Error shell_open(String p_uri) override;
|
||||
|
||||
void set_user_data_dir(String p_dir);
|
||||
virtual String get_user_data_dir() const override;
|
||||
|
||||
virtual String get_cache_path() const override;
|
||||
|
||||
virtual String get_locale() const override;
|
||||
|
||||
virtual String get_unique_id() const override;
|
||||
virtual String get_processor_name() const override;
|
||||
|
||||
virtual void vibrate_handheld(int p_duration_ms = 500) override;
|
||||
|
||||
virtual bool _check_internal_feature_support(const String &p_feature) override;
|
||||
|
||||
void on_focus_out();
|
||||
void on_focus_in();
|
||||
};
|
||||
|
||||
#endif // OS_IOS_H
|
||||
|
||||
#endif // IOS_ENABLED
|
||||
346
platform/ios/os_ios.mm
Normal file
346
platform/ios/os_ios.mm
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
/*************************************************************************/
|
||||
/* os_ios.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifdef IOS_ENABLED
|
||||
|
||||
#include "os_ios.h"
|
||||
|
||||
#import "app_delegate.h"
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/file_access_pack.h"
|
||||
#include "display_server_ios.h"
|
||||
#include "drivers/unix/syslog_logger.h"
|
||||
#import "godot_view.h"
|
||||
#include "main/main.h"
|
||||
#import "view_controller.h"
|
||||
|
||||
#import <AudioToolbox/AudioServices.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <dlfcn.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#if defined(VULKAN_ENABLED)
|
||||
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#ifdef USE_VOLK
|
||||
#include <volk.h>
|
||||
#else
|
||||
#include <vulkan/vulkan.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// 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 = nullptr;
|
||||
static int ios_init_callbacks_count = 0;
|
||||
static int ios_init_callbacks_capacity = 0;
|
||||
HashMap<String, void *> OS_IOS::dynamic_symbol_lookup_table;
|
||||
|
||||
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);
|
||||
if (new_ptr) {
|
||||
ios_init_callbacks = (init_callback *)(new_ptr);
|
||||
ios_init_callbacks_capacity += 32;
|
||||
}
|
||||
}
|
||||
if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
|
||||
ios_init_callbacks[ios_init_callbacks_count] = cb;
|
||||
++ios_init_callbacks_count;
|
||||
}
|
||||
}
|
||||
|
||||
void register_dynamic_symbol(char *name, void *address) {
|
||||
OS_IOS::dynamic_symbol_lookup_table[String(name)] = address;
|
||||
}
|
||||
|
||||
OS_IOS *OS_IOS::get_singleton() {
|
||||
return (OS_IOS *)OS::get_singleton();
|
||||
}
|
||||
|
||||
OS_IOS::OS_IOS(String p_data_dir, String p_cache_dir) {
|
||||
for (int i = 0; i < ios_init_callbacks_count; ++i) {
|
||||
ios_init_callbacks[i]();
|
||||
}
|
||||
free(ios_init_callbacks);
|
||||
ios_init_callbacks = nullptr;
|
||||
ios_init_callbacks_count = 0;
|
||||
ios_init_callbacks_capacity = 0;
|
||||
|
||||
main_loop = nullptr;
|
||||
|
||||
// can't call set_data_dir from here, since it requires DirAccess
|
||||
// which is initialized in initialize_core
|
||||
user_data_dir = p_data_dir;
|
||||
cache_dir = p_cache_dir;
|
||||
|
||||
Vector<Logger *> loggers;
|
||||
loggers.push_back(memnew(SyslogLogger));
|
||||
#ifdef DEBUG_ENABLED
|
||||
// it seems iOS app's stdout/stderr is only obtainable if you launch it from
|
||||
// Xcode
|
||||
loggers.push_back(memnew(StdLogger));
|
||||
#endif
|
||||
_set_logger(memnew(CompositeLogger(loggers)));
|
||||
|
||||
AudioDriverManager::add_driver(&audio_driver);
|
||||
|
||||
DisplayServerIOS::register_ios_driver();
|
||||
}
|
||||
|
||||
OS_IOS::~OS_IOS() {}
|
||||
|
||||
void OS_IOS::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());
|
||||
}
|
||||
|
||||
void OS_IOS::initialize_core() {
|
||||
OS_Unix::initialize_core();
|
||||
|
||||
set_user_data_dir(user_data_dir);
|
||||
}
|
||||
|
||||
void OS_IOS::initialize() {
|
||||
initialize_core();
|
||||
}
|
||||
|
||||
void OS_IOS::initialize_modules() {
|
||||
ios = memnew(iOS);
|
||||
Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
|
||||
|
||||
joypad_ios = memnew(JoypadIOS);
|
||||
}
|
||||
|
||||
void OS_IOS::deinitialize_modules() {
|
||||
if (joypad_ios) {
|
||||
memdelete(joypad_ios);
|
||||
}
|
||||
|
||||
if (ios) {
|
||||
memdelete(ios);
|
||||
}
|
||||
}
|
||||
|
||||
void OS_IOS::set_main_loop(MainLoop *p_main_loop) {
|
||||
main_loop = p_main_loop;
|
||||
|
||||
if (main_loop) {
|
||||
main_loop->initialize();
|
||||
}
|
||||
}
|
||||
|
||||
MainLoop *OS_IOS::get_main_loop() const {
|
||||
return main_loop;
|
||||
}
|
||||
|
||||
void OS_IOS::delete_main_loop() {
|
||||
if (main_loop) {
|
||||
main_loop->finalize();
|
||||
memdelete(main_loop);
|
||||
}
|
||||
|
||||
main_loop = nullptr;
|
||||
}
|
||||
|
||||
bool OS_IOS::iterate() {
|
||||
if (!main_loop) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DisplayServer::get_singleton()) {
|
||||
DisplayServer::get_singleton()->process_events();
|
||||
}
|
||||
|
||||
return Main::iteration();
|
||||
}
|
||||
|
||||
void OS_IOS::start() {
|
||||
Main::start();
|
||||
|
||||
if (joypad_ios) {
|
||||
joypad_ios->start_processing();
|
||||
}
|
||||
}
|
||||
|
||||
void OS_IOS::finalize() {
|
||||
deinitialize_modules();
|
||||
|
||||
// Already gets called
|
||||
//delete_main_loop();
|
||||
}
|
||||
|
||||
// MARK: Dynamic Libraries
|
||||
|
||||
Error OS_IOS::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
|
||||
if (p_path.length() == 0) {
|
||||
p_library_handle = RTLD_SELF;
|
||||
|
||||
if (r_resolved_path != nullptr) {
|
||||
*r_resolved_path = p_path;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path, r_resolved_path);
|
||||
}
|
||||
|
||||
Error OS_IOS::close_dynamic_library(void *p_library_handle) {
|
||||
if (p_library_handle == RTLD_SELF) {
|
||||
return OK;
|
||||
}
|
||||
return OS_Unix::close_dynamic_library(p_library_handle);
|
||||
}
|
||||
|
||||
Error OS_IOS::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
|
||||
if (p_library_handle == RTLD_SELF) {
|
||||
void **ptr = OS_IOS::dynamic_symbol_lookup_table.getptr(p_name);
|
||||
if (ptr) {
|
||||
p_symbol_handle = *ptr;
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
|
||||
}
|
||||
|
||||
String OS_IOS::get_name() const {
|
||||
return "iOS";
|
||||
}
|
||||
|
||||
String OS_IOS::get_model_name() const {
|
||||
String model = ios->get_model();
|
||||
if (model != "") {
|
||||
return model;
|
||||
}
|
||||
|
||||
return OS_Unix::get_model_name();
|
||||
}
|
||||
|
||||
Error OS_IOS::shell_open(String 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 OS_IOS::set_user_data_dir(String p_dir) {
|
||||
Ref<DirAccess> da = DirAccess::open(p_dir);
|
||||
user_data_dir = da->get_current_dir();
|
||||
printf("setting data dir to %s from %s\n", user_data_dir.utf8().get_data(), p_dir.utf8().get_data());
|
||||
}
|
||||
|
||||
String OS_IOS::get_user_data_dir() const {
|
||||
return user_data_dir;
|
||||
}
|
||||
|
||||
String OS_IOS::get_cache_path() const {
|
||||
return cache_dir;
|
||||
}
|
||||
|
||||
String OS_IOS::get_locale() const {
|
||||
NSString *preferedLanguage = [NSLocale preferredLanguages].firstObject;
|
||||
|
||||
if (preferedLanguage) {
|
||||
return String::utf8([preferedLanguage UTF8String]).replace("-", "_");
|
||||
}
|
||||
|
||||
NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier];
|
||||
return String::utf8([localeIdentifier UTF8String]).replace("-", "_");
|
||||
}
|
||||
|
||||
String OS_IOS::get_unique_id() const {
|
||||
NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
|
||||
return String::utf8([uuid UTF8String]);
|
||||
}
|
||||
|
||||
String OS_IOS::get_processor_name() const {
|
||||
char buffer[256];
|
||||
size_t buffer_len = 256;
|
||||
if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, NULL, 0) == 0) {
|
||||
return String::utf8(buffer, buffer_len);
|
||||
}
|
||||
ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
|
||||
}
|
||||
|
||||
void OS_IOS::vibrate_handheld(int p_duration_ms) {
|
||||
if (ios->supports_haptic_engine()) {
|
||||
ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f);
|
||||
} else {
|
||||
// iOS <13 does not support duration for vibration
|
||||
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
|
||||
}
|
||||
}
|
||||
|
||||
bool OS_IOS::_check_internal_feature_support(const String &p_feature) {
|
||||
return p_feature == "mobile";
|
||||
}
|
||||
|
||||
void OS_IOS::on_focus_out() {
|
||||
if (is_focused) {
|
||||
is_focused = false;
|
||||
|
||||
if (DisplayServerIOS::get_singleton()) {
|
||||
DisplayServerIOS::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
|
||||
}
|
||||
|
||||
[AppDelegate.viewController.godotView stopRendering];
|
||||
|
||||
audio_driver.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void OS_IOS::on_focus_in() {
|
||||
if (!is_focused) {
|
||||
is_focused = true;
|
||||
|
||||
if (DisplayServerIOS::get_singleton()) {
|
||||
DisplayServerIOS::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
|
||||
}
|
||||
|
||||
[AppDelegate.viewController.godotView startRendering];
|
||||
|
||||
audio_driver.start();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // IOS_ENABLED
|
||||
44
platform/ios/platform_config.h
Normal file
44
platform/ios/platform_config.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/*************************************************************************/
|
||||
/* platform_config.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include <alloca.h>
|
||||
|
||||
#define OPENGL_INCLUDE_H <ES3/gl.h>
|
||||
|
||||
#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")
|
||||
63
platform/ios/tts_ios.h
Normal file
63
platform/ios/tts_ios.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*************************************************************************/
|
||||
/* tts_ios.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef TTS_IOS_H
|
||||
#define TTS_IOS_H
|
||||
|
||||
#if __has_include(<AVFAudio/AVSpeechSynthesis.h>)
|
||||
#import <AVFAudio/AVSpeechSynthesis.h>
|
||||
#else
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#endif
|
||||
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/templates/list.h"
|
||||
#include "core/templates/rb_map.h"
|
||||
#include "core/variant/array.h"
|
||||
#include "servers/display_server.h"
|
||||
|
||||
@interface TTS_IOS : NSObject <AVSpeechSynthesizerDelegate> {
|
||||
bool speaking;
|
||||
HashMap<id, int> ids;
|
||||
|
||||
AVSpeechSynthesizer *av_synth;
|
||||
List<DisplayServer::TTSUtterance> queue;
|
||||
}
|
||||
|
||||
- (void)pauseSpeaking;
|
||||
- (void)resumeSpeaking;
|
||||
- (void)stopSpeaking;
|
||||
- (bool)isSpeaking;
|
||||
- (bool)isPaused;
|
||||
- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt;
|
||||
- (Array)getVoices;
|
||||
@end
|
||||
|
||||
#endif // TTS_IOS_H
|
||||
164
platform/ios/tts_ios.mm
Normal file
164
platform/ios/tts_ios.mm
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/*************************************************************************/
|
||||
/* tts_ios.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "tts_ios.h"
|
||||
|
||||
@implementation TTS_IOS
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
self->speaking = false;
|
||||
self->av_synth = [[AVSpeechSynthesizer alloc] init];
|
||||
[self->av_synth setDelegate:self];
|
||||
print_verbose("Text-to-Speech: AVSpeechSynthesizer initialized.");
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance {
|
||||
NSString *string = [utterance speechString];
|
||||
|
||||
// Convert from UTF-16 to UTF-32 position.
|
||||
int pos = 0;
|
||||
for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) {
|
||||
unichar c = [string characterAtIndex:i];
|
||||
if ((c & 0xfffffc00) == 0xd800) {
|
||||
i++;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos);
|
||||
}
|
||||
|
||||
- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance {
|
||||
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[utterance]);
|
||||
ids.erase(utterance);
|
||||
speaking = false;
|
||||
[self update];
|
||||
}
|
||||
|
||||
- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didFinishSpeechUtterance:(AVSpeechUtterance *)utterance {
|
||||
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, ids[utterance]);
|
||||
ids.erase(utterance);
|
||||
speaking = false;
|
||||
[self update];
|
||||
}
|
||||
|
||||
- (void)update {
|
||||
if (!speaking && queue.size() > 0) {
|
||||
DisplayServer::TTSUtterance &message = queue.front()->get();
|
||||
|
||||
AVSpeechUtterance *new_utterance = [[AVSpeechUtterance alloc] initWithString:[NSString stringWithUTF8String:message.text.utf8().get_data()]];
|
||||
[new_utterance setVoice:[AVSpeechSynthesisVoice voiceWithIdentifier:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]];
|
||||
if (message.rate > 1.f) {
|
||||
[new_utterance setRate:Math::range_lerp(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)];
|
||||
} else if (message.rate < 1.f) {
|
||||
[new_utterance setRate:Math::range_lerp(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)];
|
||||
}
|
||||
[new_utterance setPitchMultiplier:message.pitch];
|
||||
[new_utterance setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))];
|
||||
|
||||
ids[new_utterance] = message.id;
|
||||
[av_synth speakUtterance:new_utterance];
|
||||
|
||||
queue.pop_front();
|
||||
|
||||
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id);
|
||||
speaking = true;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pauseSpeaking {
|
||||
[av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
|
||||
}
|
||||
|
||||
- (void)resumeSpeaking {
|
||||
[av_synth continueSpeaking];
|
||||
}
|
||||
|
||||
- (void)stopSpeaking {
|
||||
for (DisplayServer::TTSUtterance &message : queue) {
|
||||
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id);
|
||||
}
|
||||
queue.clear();
|
||||
[av_synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
|
||||
speaking = false;
|
||||
}
|
||||
|
||||
- (bool)isSpeaking {
|
||||
return speaking || (queue.size() > 0);
|
||||
}
|
||||
|
||||
- (bool)isPaused {
|
||||
return [av_synth isPaused];
|
||||
}
|
||||
|
||||
- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt {
|
||||
if (interrupt) {
|
||||
[self stopSpeaking];
|
||||
}
|
||||
|
||||
if (text.is_empty()) {
|
||||
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, utterance_id);
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayServer::TTSUtterance message;
|
||||
message.text = text;
|
||||
message.voice = voice;
|
||||
message.volume = CLAMP(volume, 0, 100);
|
||||
message.pitch = CLAMP(pitch, 0.f, 2.f);
|
||||
message.rate = CLAMP(rate, 0.1f, 10.f);
|
||||
message.id = utterance_id;
|
||||
queue.push_back(message);
|
||||
|
||||
if ([self isPaused]) {
|
||||
[self resumeSpeaking];
|
||||
} else {
|
||||
[self update];
|
||||
}
|
||||
}
|
||||
|
||||
- (Array)getVoices {
|
||||
Array list;
|
||||
for (AVSpeechSynthesisVoice *voice in [AVSpeechSynthesisVoice speechVoices]) {
|
||||
NSString *voiceIdentifierString = [voice identifier];
|
||||
NSString *voiceLocaleIdentifier = [voice language];
|
||||
NSString *voiceName = [voice name];
|
||||
Dictionary voice_d;
|
||||
voice_d["name"] = String::utf8([voiceName UTF8String]);
|
||||
voice_d["id"] = String::utf8([voiceIdentifierString UTF8String]);
|
||||
voice_d["language"] = String::utf8([voiceLocaleIdentifier UTF8String]);
|
||||
list.push_back(voice_d);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@end
|
||||
42
platform/ios/view_controller.h
Normal file
42
platform/ios/view_controller.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*************************************************************************/
|
||||
/* view_controller.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class GodotView;
|
||||
@class GodotNativeVideoView;
|
||||
@class GodotKeyboardInputView;
|
||||
|
||||
@interface ViewController : UIViewController
|
||||
|
||||
@property(nonatomic, readonly, strong) GodotView *godotView;
|
||||
@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView;
|
||||
|
||||
@end
|
||||
240
platform/ios/view_controller.mm
Normal file
240
platform/ios/view_controller.mm
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
/*************************************************************************/
|
||||
/* view_controller.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 "view_controller.h"
|
||||
#include "core/config/project_settings.h"
|
||||
#include "display_server_ios.h"
|
||||
#import "godot_view.h"
|
||||
#import "godot_view_renderer.h"
|
||||
#import "keyboard_input_view.h"
|
||||
#include "os_ios.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <GameController/GameController.h>
|
||||
|
||||
@interface ViewController () <GodotViewDelegate>
|
||||
|
||||
@property(strong, nonatomic) GodotViewRenderer *renderer;
|
||||
@property(strong, nonatomic) GodotKeyboardInputView *keyboardView;
|
||||
|
||||
@property(strong, nonatomic) UIView *godotLoadingOverlay;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ViewController
|
||||
|
||||
- (GodotView *)godotView {
|
||||
return (GodotView *)self.view;
|
||||
}
|
||||
|
||||
- (void)loadView {
|
||||
GodotView *view = [[GodotView alloc] init];
|
||||
GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
|
||||
|
||||
self.renderer = renderer;
|
||||
self.view = view;
|
||||
|
||||
view.renderer = self.renderer;
|
||||
view.delegate = self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
|
||||
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
||||
|
||||
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 {
|
||||
// Initialize view controller values.
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
[super didReceiveMemoryWarning];
|
||||
printf("*********** did receive memory warning!\n");
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
[self observeKeyboard];
|
||||
[self displayLoadingOverlay];
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)observeKeyboard {
|
||||
printf("******** setting up keyboard input view\n");
|
||||
self.keyboardView = [GodotKeyboardInputView new];
|
||||
[self.view addSubview:self.keyboardView];
|
||||
|
||||
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)displayLoadingOverlay {
|
||||
NSBundle *bundle = [NSBundle mainBundle];
|
||||
NSString *storyboardName = @"Launch Screen";
|
||||
|
||||
if ([bundle pathForResource:storyboardName ofType:@"storyboardc"] == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIStoryboard *launchStoryboard = [UIStoryboard storyboardWithName:storyboardName bundle:bundle];
|
||||
|
||||
UIViewController *controller = [launchStoryboard instantiateInitialViewController];
|
||||
self.godotLoadingOverlay = controller.view;
|
||||
self.godotLoadingOverlay.frame = self.view.bounds;
|
||||
self.godotLoadingOverlay.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||
|
||||
[self.view addSubview:self.godotLoadingOverlay];
|
||||
}
|
||||
|
||||
- (BOOL)godotViewFinishedSetup:(GodotView *)view {
|
||||
[self.godotLoadingOverlay removeFromSuperview];
|
||||
self.godotLoadingOverlay = nil;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
self.keyboardView = nil;
|
||||
|
||||
self.renderer = nil;
|
||||
|
||||
if (self.godotLoadingOverlay) {
|
||||
[self.godotLoadingOverlay removeFromSuperview];
|
||||
self.godotLoadingOverlay = nil;
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
// MARK: Orientation
|
||||
|
||||
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
|
||||
return UIRectEdgeAll;
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutorotate {
|
||||
if (!DisplayServerIOS::get_singleton()) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
switch (DisplayServerIOS::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
|
||||
case DisplayServer::SCREEN_SENSOR:
|
||||
case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
|
||||
case DisplayServer::SCREEN_SENSOR_PORTRAIT:
|
||||
return YES;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
|
||||
if (!DisplayServerIOS::get_singleton()) {
|
||||
return UIInterfaceOrientationMaskAll;
|
||||
}
|
||||
|
||||
switch (DisplayServerIOS::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
|
||||
case DisplayServer::SCREEN_PORTRAIT:
|
||||
return UIInterfaceOrientationMaskPortrait;
|
||||
case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
|
||||
return UIInterfaceOrientationMaskLandscapeRight;
|
||||
case DisplayServer::SCREEN_REVERSE_PORTRAIT:
|
||||
return UIInterfaceOrientationMaskPortraitUpsideDown;
|
||||
case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
|
||||
return UIInterfaceOrientationMaskLandscape;
|
||||
case DisplayServer::SCREEN_SENSOR_PORTRAIT:
|
||||
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
|
||||
case DisplayServer::SCREEN_SENSOR:
|
||||
return UIInterfaceOrientationMaskAll;
|
||||
case DisplayServer::SCREEN_LANDSCAPE:
|
||||
return UIInterfaceOrientationMaskLandscapeLeft;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)prefersHomeIndicatorAutoHidden {
|
||||
if (GLOBAL_GET("display/window/ios/hide_home_indicator")) {
|
||||
return YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (DisplayServerIOS::get_singleton()) {
|
||||
DisplayServerIOS::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)keyboardHidden:(NSNotification *)notification {
|
||||
if (DisplayServerIOS::get_singleton()) {
|
||||
DisplayServerIOS::get_singleton()->virtual_keyboard_set_height(0);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
48
platform/ios/vulkan_context_ios.h
Normal file
48
platform/ios/vulkan_context_ios.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*************************************************************************/
|
||||
/* vulkan_context_ios.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef VULKAN_CONTEXT_IOS_H
|
||||
#define VULKAN_CONTEXT_IOS_H
|
||||
|
||||
#include "drivers/vulkan/vulkan_context.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
class VulkanContextIOS : public VulkanContext {
|
||||
virtual const char *_get_platform_surface_extension() const;
|
||||
|
||||
public:
|
||||
Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, CALayer *p_metal_layer, int p_width, int p_height);
|
||||
|
||||
VulkanContextIOS();
|
||||
~VulkanContextIOS();
|
||||
};
|
||||
|
||||
#endif // VULKAN_CONTEXT_IOS_H
|
||||
59
platform/ios/vulkan_context_ios.mm
Normal file
59
platform/ios/vulkan_context_ios.mm
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*************************************************************************/
|
||||
/* vulkan_context_ios.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "vulkan_context_ios.h"
|
||||
#ifdef USE_VOLK
|
||||
#include <volk.h>
|
||||
#else
|
||||
#include <vulkan/vulkan.h>
|
||||
#endif
|
||||
|
||||
const char *VulkanContextIOS::_get_platform_surface_extension() const {
|
||||
return VK_MVK_IOS_SURFACE_EXTENSION_NAME;
|
||||
}
|
||||
|
||||
Error VulkanContextIOS::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, CALayer *p_metal_layer, int p_width, int p_height) {
|
||||
VkIOSSurfaceCreateInfoMVK createInfo;
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
|
||||
createInfo.pNext = nullptr;
|
||||
createInfo.flags = 0;
|
||||
createInfo.pView = (__bridge const void *)p_metal_layer;
|
||||
|
||||
VkSurfaceKHR surface;
|
||||
VkResult err =
|
||||
vkCreateIOSSurfaceMVK(get_instance(), &createInfo, nullptr, &surface);
|
||||
ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
|
||||
|
||||
return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height);
|
||||
}
|
||||
|
||||
VulkanContextIOS::VulkanContextIOS() {}
|
||||
|
||||
VulkanContextIOS::~VulkanContextIOS() {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue