initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env_apple_embedded = env.Clone()
# Enable module support
env_apple_embedded.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
# Use bundled Vulkan headers
vulkan_dir = "#thirdparty/vulkan"
env_apple_embedded.Prepend(CPPPATH=[vulkan_dir, vulkan_dir + "/include"])
# Driver source files
env_apple_embedded.add_source_files(env.drivers_sources, "*.mm")

View File

@@ -0,0 +1,42 @@
/**************************************************************************/
/* app_delegate_service.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
@class GDTViewController;
@interface GDTAppDelegateService : NSObject <UIApplicationDelegate>
@property(strong, nonatomic) UIWindow *window;
@property(strong, class, readonly, nonatomic) GDTViewController *viewController;
@end

View File

@@ -0,0 +1,189 @@
/**************************************************************************/
/* app_delegate_service.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_service.h"
#import "godot_view_apple_embedded.h"
#import "os_apple_embedded.h"
#import "view_controller.h"
#include "core/config/project_settings.h"
#import "drivers/coreaudio/audio_driver_coreaudio.h"
#include "main/main.h"
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioServices.h>
#define kRenderingFrequency 60
extern int gargc;
extern char **gargv;
extern int apple_embedded_main(int, char **);
extern void apple_embedded_finish();
@implementation GDTAppDelegateService
enum {
SESSION_CATEGORY_AMBIENT,
SESSION_CATEGORY_MULTI_ROUTE,
SESSION_CATEGORY_PLAY_AND_RECORD,
SESSION_CATEGORY_PLAYBACK,
SESSION_CATEGORY_RECORD,
SESSION_CATEGORY_SOLO_AMBIENT
};
static GDTViewController *mainViewController = nil;
+ (GDTViewController *)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
#if !defined(VISIONOS_ENABLED)
// Create a full-screen window
CGRect windowBounds = [[UIScreen mainScreen] bounds];
self.window = [[UIWindow alloc] initWithFrame:windowBounds];
#else
self.window = [[UIWindow alloc] init];
#endif
int err = apple_embedded_main(gargc, gargv);
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;
}
GDTViewController *viewController = [[GDTViewController 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;
int sessionCategorySetting = GLOBAL_GET("audio/general/ios/session_category");
// Initialize with default Ambient category.
AVAudioSessionCategory category = AVAudioSessionCategoryAmbient;
AVAudioSessionCategoryOptions options = 0;
if (GLOBAL_GET("audio/general/ios/mix_with_others")) {
options |= AVAudioSessionCategoryOptionMixWithOthers;
}
if (sessionCategorySetting == SESSION_CATEGORY_MULTI_ROUTE) {
category = AVAudioSessionCategoryMultiRoute;
} else if (sessionCategorySetting == SESSION_CATEGORY_PLAY_AND_RECORD) {
category = AVAudioSessionCategoryPlayAndRecord;
options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP;
options |= AVAudioSessionCategoryOptionAllowAirPlay;
} else if (sessionCategorySetting == SESSION_CATEGORY_PLAYBACK) {
category = AVAudioSessionCategoryPlayback;
} else if (sessionCategorySetting == SESSION_CATEGORY_RECORD) {
category = AVAudioSessionCategoryRecord;
} else if (sessionCategorySetting == SESSION_CATEGORY_SOLO_AMBIENT) {
category = AVAudioSessionCategorySoloAmbient;
}
[[AVAudioSession sharedInstance] setCategory:category withOptions:options 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_AppleEmbedded::get_singleton()->on_focus_out();
} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
NSLog(@"Audio interruption ended");
OS_AppleEmbedded::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 {
apple_embedded_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_AppleEmbedded::get_singleton()->on_focus_out();
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
OS_AppleEmbedded::get_singleton()->on_focus_in();
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
OS_AppleEmbedded::get_singleton()->on_enter_background();
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
OS_AppleEmbedded::get_singleton()->on_exit_background();
}
- (void)dealloc {
self.window = nil;
}
@end

View File

@@ -0,0 +1,59 @@
/**************************************************************************/
/* apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#include "core/object/class_db.h"
#import <CoreHaptics/CoreHaptics.h>
class AppleEmbedded : public Object {
GDCLASS(AppleEmbedded, 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, float p_amplitude);
String get_model() const;
String get_rate_url(int p_app_id) const;
AppleEmbedded();
};

View File

@@ -0,0 +1,200 @@
/**************************************************************************/
/* apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "apple_embedded.h"
#import "app_delegate_service.h"
#import "view_controller.h"
#import <CoreHaptics/CoreHaptics.h>
#import <UIKit/UIKit.h>
#include <sys/sysctl.h>
void AppleEmbedded::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &AppleEmbedded::get_rate_url);
ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &AppleEmbedded::supports_haptic_engine);
ClassDB::bind_method(D_METHOD("start_haptic_engine"), &AppleEmbedded::start_haptic_engine);
ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &AppleEmbedded::stop_haptic_engine);
}
bool AppleEmbedded::supports_haptic_engine() {
if (@available(iOS 13, *)) {
id<CHHapticDeviceCapability> capabilities = [CHHapticEngine capabilitiesForHardware];
return capabilities.supportsHaptics;
}
return false;
}
CHHapticEngine *AppleEmbedded::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 AppleEmbedded::vibrate_haptic_engine(float p_duration_seconds, float p_amplitude) 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 *cur_haptic_engine = get_haptic_engine_instance();
if (cur_haptic_engine) {
NSDictionary *hapticDict;
if (p_amplitude < 0) {
hapticDict = @{
CHHapticPatternKeyPattern : @[
@{CHHapticPatternKeyEvent : @{
CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous,
CHHapticPatternKeyTime : @(CHHapticTimeImmediate),
CHHapticPatternKeyEventDuration : @(p_duration_seconds),
},
},
],
};
} else {
hapticDict = @{
CHHapticPatternKeyPattern : @[
@{CHHapticPatternKeyEvent : @{
CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous,
CHHapticPatternKeyTime : @(CHHapticTimeImmediate),
CHHapticPatternKeyEventDuration : @(p_duration_seconds),
CHHapticPatternKeyEventParameters : @[
@{
CHHapticPatternKeyParameterID : @("HapticIntensity"),
CHHapticPatternKeyParameterValue : @(p_amplitude)
},
],
},
},
],
};
}
NSError *error;
CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error];
[[cur_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");
}
void AppleEmbedded::start_haptic_engine() {
if (@available(iOS 13, *)) {
if (supports_haptic_engine()) {
CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance();
if (cur_haptic_engine) {
[cur_haptic_engine startWithCompletionHandler:^(NSError *returnedError) {
if (returnedError) {
NSLog(@"Could not start haptic engine: %@", returnedError);
}
}];
}
return;
}
}
NSLog(@"Haptic engine is not supported");
}
void AppleEmbedded::stop_haptic_engine() {
if (@available(iOS 13, *)) {
if (supports_haptic_engine()) {
CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance();
if (cur_haptic_engine) {
[cur_haptic_engine stopWithCompletionHandler:^(NSError *returnedError) {
if (returnedError) {
NSLog(@"Could not stop haptic engine: %@", returnedError);
}
}];
}
return;
}
}
NSLog(@"Haptic engine is not supported");
}
void AppleEmbedded::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];
[GDTAppDelegateService.viewController presentViewController:alert animated:YES completion:nil];
}
String AppleEmbedded::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 AppleEmbedded::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_int64(p_app_id));
print_verbose(vformat("Returning rate url %s", ret));
return ret;
}
AppleEmbedded::AppleEmbedded() {}

View File

@@ -0,0 +1,42 @@
/**************************************************************************/
/* display_layer_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#import <QuartzCore/CAMetalLayer.h>
@protocol GDTDisplayLayer <NSObject>
- (void)startRenderDisplayLayer;
- (void)stopRenderDisplayLayer;
- (void)initializeDisplayLayer;
- (void)layoutDisplayLayer;
@end

View File

@@ -0,0 +1,233 @@
/**************************************************************************/
/* display_server_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#include "core/input/input.h"
#include "servers/display_server.h"
#if defined(RD_ENABLED)
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#include "servers/rendering/rendering_device.h"
#if defined(VULKAN_ENABLED)
#import "rendering_context_driver_vulkan_apple_embedded.h"
#include "drivers/vulkan/godot_vulkan.h"
#endif // VULKAN_ENABLED
#if defined(METAL_ENABLED)
#import "drivers/metal/rendering_context_driver_metal.h"
#endif // METAL_ENABLED
#endif // RD_ENABLED
#if defined(GLES3_ENABLED)
#include "drivers/gles3/rasterizer_gles3.h"
#endif // GLES3_ENABLED
#import <Foundation/Foundation.h>
#import <QuartzCore/CAMetalLayer.h>
class DisplayServerAppleEmbedded : public DisplayServer {
GDSOFTCLASS(DisplayServerAppleEmbedded, DisplayServer);
_THREAD_SAFE_CLASS_
#if defined(RD_ENABLED)
RenderingContextDriver *rendering_context = nullptr;
RenderingDevice *rendering_device = nullptr;
#endif
NativeMenu *native_menu = nullptr;
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;
Callable system_theme_changed;
int virtual_keyboard_height = 0;
void perform_event(const Ref<InputEvent> &p_event);
void initialize_tts() const;
protected:
DisplayServerAppleEmbedded(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error);
~DisplayServerAppleEmbedded();
public:
String rendering_driver;
static DisplayServerAppleEmbedded *get_singleton();
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;
void emit_system_theme_changed();
// MARK: - Input
// MARK: Touches and Apple Pencil
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, float p_pressure, Vector2 p_tilt);
void touches_canceled(int p_idx);
// MARK: Keyboard
void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location);
bool is_keyboard_active() const;
// MARK: Motion
void update_gravity(const Vector3 &p_gravity);
void update_accelerometer(const Vector3 &p_accelerometer);
void update_magnetometer(const Vector3 &p_magnetometer);
void update_gyroscope(const Vector3 &p_gyroscope);
// MARK: -
virtual bool has_feature(Feature p_feature) const override;
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
virtual TypedArray<Dictionary> 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 bool is_dark_mode_supported() const override;
virtual bool is_dark_mode() const override;
virtual void set_system_theme_change_callback(const Callable &p_callable) override;
virtual Rect2i get_display_safe_area() const override;
virtual int get_screen_count() const override;
virtual int get_primary_screen() 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 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 Point2i window_get_position_with_decorations(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_size_with_decorations(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 bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const 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 is_touchscreen_available() const override;
virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, 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 bool has_hardware_keyboard() 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);
virtual void swap_buffers() override {}
};

View File

@@ -0,0 +1,820 @@
/**************************************************************************/
/* display_server_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_server_apple_embedded.h"
#import "app_delegate_service.h"
#import "apple_embedded.h"
#import "godot_view_apple_embedded.h"
#import "key_mapping_apple_embedded.h"
#import "keyboard_input_view.h"
#import "os_apple_embedded.h"
#import "tts_apple_embedded.h"
#import "view_controller.h"
#include "core/config/project_settings.h"
#include "core/io/file_access_pack.h"
#import <GameController/GameController.h>
static const float kDisplayServerIOSAcceleration = 1.f;
DisplayServerAppleEmbedded *DisplayServerAppleEmbedded::get_singleton() {
return (DisplayServerAppleEmbedded *)DisplayServer::get_singleton();
}
DisplayServerAppleEmbedded::DisplayServerAppleEmbedded(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) {
KeyMappingAppleEmbedded::initialize();
rendering_driver = p_rendering_driver;
// Init TTS
bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");
if (tts_enabled) {
initialize_tts();
}
native_menu = memnew(NativeMenu);
bool has_made_render_compositor_current = false;
#if defined(RD_ENABLED)
rendering_context = nullptr;
rendering_device = nullptr;
CALayer *layer = nullptr;
union {
#ifdef VULKAN_ENABLED
RenderingContextDriverVulkanAppleEmbedded::WindowPlatformData vulkan;
#endif
#ifdef METAL_ENABLED
GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wunguarded-availability")
// Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer".
RenderingContextDriverMetal::WindowPlatformData metal;
GODOT_CLANG_WARNING_POP
#endif
} wpd;
#if defined(VULKAN_ENABLED)
if (rendering_driver == "vulkan") {
layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"vulkan"];
if (!layer) {
ERR_FAIL_MSG("Failed to create iOS Vulkan rendering layer.");
}
wpd.vulkan.layer_ptr = (CAMetalLayer *const *)&layer;
rendering_context = memnew(RenderingContextDriverVulkanAppleEmbedded);
}
#endif
#ifdef METAL_ENABLED
if (rendering_driver == "metal") {
if (@available(iOS 14.0, *)) {
layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"metal"];
wpd.metal.layer = (CAMetalLayer *)layer;
rendering_context = memnew(RenderingContextDriverMetal);
} else {
OS::get_singleton()->alert("Metal is only supported on iOS 14.0 and later.");
r_error = ERR_UNAVAILABLE;
return;
}
}
#endif
if (rendering_context) {
if (rendering_context->initialize() != OK) {
memdelete(rendering_context);
rendering_context = nullptr;
#if defined(GLES3_ENABLED)
bool fallback_to_opengl3 = GLOBAL_GET("rendering/rendering_device/fallback_to_opengl3");
if (fallback_to_opengl3 && rendering_driver != "opengl3") {
WARN_PRINT("Your device does not seem to support MoltenVK or Metal, switching to OpenGL 3.");
rendering_driver = "opengl3";
OS::get_singleton()->set_current_rendering_method("gl_compatibility");
OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
} else
#endif
{
ERR_PRINT(vformat("Failed to initialize %s context", rendering_driver));
r_error = ERR_UNAVAILABLE;
return;
}
}
}
if (rendering_context) {
if (rendering_context->window_create(MAIN_WINDOW_ID, &wpd) != OK) {
ERR_PRINT(vformat("Failed to create %s window.", rendering_driver));
memdelete(rendering_context);
rendering_context = nullptr;
r_error = ERR_UNAVAILABLE;
return;
}
Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
rendering_context->window_set_size(MAIN_WINDOW_ID, size.width, size.height);
rendering_context->window_set_vsync_mode(MAIN_WINDOW_ID, p_vsync_mode);
rendering_device = memnew(RenderingDevice);
if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {
rendering_device = nullptr;
memdelete(rendering_context);
rendering_context = nullptr;
r_error = ERR_UNAVAILABLE;
return;
}
rendering_device->screen_create(MAIN_WINDOW_ID);
RendererCompositorRD::make_current();
has_made_render_compositor_current = true;
}
#endif
#if defined(GLES3_ENABLED)
if (rendering_driver == "opengl3") {
CALayer *layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"opengl3"];
if (!layer) {
ERR_FAIL_MSG("Failed to create iOS OpenGLES rendering layer.");
}
RasterizerGLES3::make_current(false);
has_made_render_compositor_current = true;
}
#endif
ERR_FAIL_COND_MSG(!has_made_render_compositor_current, vformat("Failed to make RendererCompositor current for rendering driver %s", rendering_driver));
bool keep_screen_on = bool(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
screen_set_keep_on(keep_screen_on);
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
r_error = OK;
}
DisplayServerAppleEmbedded::~DisplayServerAppleEmbedded() {
if (native_menu) {
memdelete(native_menu);
native_menu = nullptr;
}
#if defined(RD_ENABLED)
if (rendering_device) {
rendering_device->screen_free(MAIN_WINDOW_ID);
memdelete(rendering_device);
rendering_device = nullptr;
}
if (rendering_context) {
rendering_context->window_destroy(MAIN_WINDOW_ID);
memdelete(rendering_context);
rendering_context = nullptr;
}
#endif
}
Vector<String> DisplayServerAppleEmbedded::get_rendering_drivers_func() {
Vector<String> drivers;
#if defined(VULKAN_ENABLED)
drivers.push_back("vulkan");
#endif
#if defined(METAL_ENABLED)
if (@available(ios 14.0, *)) {
drivers.push_back("metal");
}
#endif
#if defined(GLES3_ENABLED)
drivers.push_back("opengl3");
#endif
return drivers;
}
// MARK: Events
void DisplayServerAppleEmbedded::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
window_resize_callback = p_callable;
}
void DisplayServerAppleEmbedded::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
window_event_callback = p_callable;
}
void DisplayServerAppleEmbedded::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
input_event_callback = p_callable;
}
void DisplayServerAppleEmbedded::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
input_text_callback = p_callable;
}
void DisplayServerAppleEmbedded::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
// Probably not supported for iOS
}
void DisplayServerAppleEmbedded::process_events() {
Input::get_singleton()->flush_buffered_events();
}
void DisplayServerAppleEmbedded::_dispatch_input_events(const Ref<InputEvent> &p_event) {
DisplayServerAppleEmbedded::get_singleton()->send_input_event(p_event);
}
void DisplayServerAppleEmbedded::send_input_event(const Ref<InputEvent> &p_event) const {
_window_callback(input_event_callback, p_event);
}
void DisplayServerAppleEmbedded::send_input_text(const String &p_text) const {
_window_callback(input_text_callback, p_text);
}
void DisplayServerAppleEmbedded::send_window_event(DisplayServer::WindowEvent p_event) const {
_window_callback(window_event_callback, int(p_event));
}
void DisplayServerAppleEmbedded::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
if (p_callable.is_valid()) {
p_callable.call(p_arg);
}
}
// MARK: - Input
// MARK: Touches
void DisplayServerAppleEmbedded::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) {
Ref<InputEventScreenTouch> ev;
ev.instantiate();
ev->set_index(p_idx);
ev->set_pressed(p_pressed);
ev->set_position(Vector2(p_x, p_y));
ev->set_double_tap(p_double_click);
perform_event(ev);
}
void DisplayServerAppleEmbedded::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_pressure, Vector2 p_tilt) {
Ref<InputEventScreenDrag> ev;
ev.instantiate();
ev->set_index(p_idx);
ev->set_pressure(p_pressure);
ev->set_tilt(p_tilt);
ev->set_position(Vector2(p_x, p_y));
ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
ev->set_relative_screen_position(ev->get_relative());
perform_event(ev);
}
void DisplayServerAppleEmbedded::perform_event(const Ref<InputEvent> &p_event) {
Input *input_singleton = Input::get_singleton();
if (input_singleton == nullptr) {
return;
}
input_singleton->parse_input_event(p_event);
}
void DisplayServerAppleEmbedded::touches_canceled(int p_idx) {
touch_press(p_idx, -1, -1, false, false);
}
// MARK: Keyboard
void DisplayServerAppleEmbedded::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) {
Ref<InputEventKey> ev;
ev.instantiate();
ev->set_echo(false);
ev->set_pressed(p_pressed);
ev->set_keycode(fix_keycode(p_char, p_key));
if (@available(iOS 13.4, *)) {
if (p_key != Key::SHIFT) {
ev->set_shift_pressed(p_modifier & UIKeyModifierShift);
}
if (p_key != Key::CTRL) {
ev->set_ctrl_pressed(p_modifier & UIKeyModifierControl);
}
if (p_key != Key::ALT) {
ev->set_alt_pressed(p_modifier & UIKeyModifierAlternate);
}
if (p_key != Key::META) {
ev->set_meta_pressed(p_modifier & UIKeyModifierCommand);
}
}
ev->set_key_label(p_unshifted);
ev->set_physical_keycode(p_physical);
ev->set_unicode(fix_unicode(p_char));
ev->set_location(p_location);
perform_event(ev);
}
// MARK: Motion
void DisplayServerAppleEmbedded::update_gravity(const Vector3 &p_gravity) {
Input::get_singleton()->set_gravity(p_gravity);
}
void DisplayServerAppleEmbedded::update_accelerometer(const Vector3 &p_accelerometer) {
Input::get_singleton()->set_accelerometer(p_accelerometer / kDisplayServerIOSAcceleration);
}
void DisplayServerAppleEmbedded::update_magnetometer(const Vector3 &p_magnetometer) {
Input::get_singleton()->set_magnetometer(p_magnetometer);
}
void DisplayServerAppleEmbedded::update_gyroscope(const Vector3 &p_gyroscope) {
Input::get_singleton()->set_gyroscope(p_gyroscope);
}
// MARK: -
bool DisplayServerAppleEmbedded::has_feature(Feature p_feature) const {
switch (p_feature) {
#ifndef DISABLE_DEPRECATED
case FEATURE_GLOBAL_MENU: {
return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
} break;
#endif
// case FEATURE_CURSOR_SHAPE:
// case FEATURE_CUSTOM_CURSOR_SHAPE:
// case FEATURE_HIDPI:
// case FEATURE_ICON:
// case FEATURE_IME:
// case FEATURE_MOUSE:
// case FEATURE_MOUSE_WARP:
// case FEATURE_NATIVE_DIALOG:
// case FEATURE_NATIVE_DIALOG_INPUT:
// case FEATURE_NATIVE_DIALOG_FILE:
// case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
// case FEATURE_NATIVE_DIALOG_FILE_MIME:
// 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;
}
}
void DisplayServerAppleEmbedded::initialize_tts() const {
const_cast<DisplayServerAppleEmbedded *>(this)->tts = [[GDTTTS alloc] init];
}
bool DisplayServerAppleEmbedded::tts_is_speaking() const {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL_V(tts, false);
return [tts isSpeaking];
}
bool DisplayServerAppleEmbedded::tts_is_paused() const {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL_V(tts, false);
return [tts isPaused];
}
TypedArray<Dictionary> DisplayServerAppleEmbedded::tts_get_voices() const {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL_V(tts, TypedArray<Dictionary>());
return [tts getVoices];
}
void DisplayServerAppleEmbedded::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) {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL(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 DisplayServerAppleEmbedded::tts_pause() {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL(tts);
[tts pauseSpeaking];
}
void DisplayServerAppleEmbedded::tts_resume() {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL(tts);
[tts resumeSpeaking];
}
void DisplayServerAppleEmbedded::tts_stop() {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL(tts);
[tts stopSpeaking];
}
bool DisplayServerAppleEmbedded::is_dark_mode_supported() const {
if (@available(iOS 13.0, *)) {
return true;
} else {
return false;
}
}
bool DisplayServerAppleEmbedded::is_dark_mode() const {
if (@available(iOS 13.0, *)) {
return [UITraitCollection currentTraitCollection].userInterfaceStyle == UIUserInterfaceStyleDark;
} else {
return false;
}
}
void DisplayServerAppleEmbedded::set_system_theme_change_callback(const Callable &p_callable) {
system_theme_changed = p_callable;
}
void DisplayServerAppleEmbedded::emit_system_theme_changed() {
if (system_theme_changed.is_valid()) {
Variant ret;
Callable::CallError ce;
system_theme_changed.callp(nullptr, 0, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_PRINT(vformat("Failed to execute system theme changed callback: %s.", Variant::get_callable_error_text(system_theme_changed, nullptr, 0, ce)));
}
}
}
Rect2i DisplayServerAppleEmbedded::get_display_safe_area() const {
UIEdgeInsets insets = UIEdgeInsetsZero;
UIView *view = GDTAppDelegateService.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);
}
int DisplayServerAppleEmbedded::get_screen_count() const {
return 1;
}
int DisplayServerAppleEmbedded::get_primary_screen() const {
return 0;
}
Point2i DisplayServerAppleEmbedded::screen_get_position(int p_screen) const {
p_screen = _get_screen_index(p_screen);
int screen_count = get_screen_count();
ERR_FAIL_INDEX_V(p_screen, screen_count, Point2i());
return Point2i(0, 0);
}
Size2i DisplayServerAppleEmbedded::screen_get_size(int p_screen) const {
p_screen = _get_screen_index(p_screen);
int screen_count = get_screen_count();
ERR_FAIL_INDEX_V(p_screen, screen_count, Size2i());
CALayer *layer = GDTAppDelegateService.viewController.godotView.renderingLayer;
if (!layer) {
return Size2i();
}
return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
}
Rect2i DisplayServerAppleEmbedded::screen_get_usable_rect(int p_screen) const {
p_screen = _get_screen_index(p_screen);
int screen_count = get_screen_count();
ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i());
return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
}
Vector<DisplayServer::WindowID> DisplayServerAppleEmbedded::get_window_list() const {
Vector<DisplayServer::WindowID> list;
list.push_back(MAIN_WINDOW_ID);
return list;
}
DisplayServer::WindowID DisplayServerAppleEmbedded::get_window_at_screen_position(const Point2i &p_position) const {
return MAIN_WINDOW_ID;
}
int64_t DisplayServerAppleEmbedded::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)GDTAppDelegateService.viewController;
}
case WINDOW_VIEW: {
return (int64_t)GDTAppDelegateService.viewController.godotView;
}
default: {
return 0;
}
}
}
void DisplayServerAppleEmbedded::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
window_attached_instance_id = p_instance;
}
ObjectID DisplayServerAppleEmbedded::window_get_attached_instance_id(WindowID p_window) const {
return window_attached_instance_id;
}
void DisplayServerAppleEmbedded::window_set_title(const String &p_title, WindowID p_window) {
// Probably not supported for iOS
}
int DisplayServerAppleEmbedded::window_get_current_screen(WindowID p_window) const {
ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, INVALID_SCREEN);
return 0;
}
void DisplayServerAppleEmbedded::window_set_current_screen(int p_screen, WindowID p_window) {
// Probably not supported for iOS
}
Point2i DisplayServerAppleEmbedded::window_get_position(WindowID p_window) const {
return Point2i();
}
Point2i DisplayServerAppleEmbedded::window_get_position_with_decorations(WindowID p_window) const {
return Point2i();
}
void DisplayServerAppleEmbedded::window_set_position(const Point2i &p_position, WindowID p_window) {
// Probably not supported for single window iOS app
}
void DisplayServerAppleEmbedded::window_set_transient(WindowID p_window, WindowID p_parent) {
// Probably not supported for iOS
}
void DisplayServerAppleEmbedded::window_set_max_size(const Size2i p_size, WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerAppleEmbedded::window_get_max_size(WindowID p_window) const {
return Size2i();
}
void DisplayServerAppleEmbedded::window_set_min_size(const Size2i p_size, WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerAppleEmbedded::window_get_min_size(WindowID p_window) const {
return Size2i();
}
void DisplayServerAppleEmbedded::window_set_size(const Size2i p_size, WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerAppleEmbedded::window_get_size(WindowID p_window) const {
id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
CGRect windowBounds = appDelegate.window.bounds;
return Size2i(windowBounds.size.width, windowBounds.size.height) * screen_get_max_scale();
}
Size2i DisplayServerAppleEmbedded::window_get_size_with_decorations(WindowID p_window) const {
return window_get_size(p_window);
}
void DisplayServerAppleEmbedded::window_set_mode(WindowMode p_mode, WindowID p_window) {
// Probably not supported for iOS
}
DisplayServer::WindowMode DisplayServerAppleEmbedded::window_get_mode(WindowID p_window) const {
return WindowMode::WINDOW_MODE_FULLSCREEN;
}
bool DisplayServerAppleEmbedded::window_is_maximize_allowed(WindowID p_window) const {
return false;
}
void DisplayServerAppleEmbedded::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
// Probably not supported for iOS
}
bool DisplayServerAppleEmbedded::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
return false;
}
void DisplayServerAppleEmbedded::window_request_attention(WindowID p_window) {
// Probably not supported for iOS
}
void DisplayServerAppleEmbedded::window_move_to_foreground(WindowID p_window) {
// Probably not supported for iOS
}
bool DisplayServerAppleEmbedded::window_is_focused(WindowID p_window) const {
return true;
}
float DisplayServerAppleEmbedded::screen_get_max_scale() const {
return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
}
void DisplayServerAppleEmbedded::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
p_screen = _get_screen_index(p_screen);
int screen_count = get_screen_count();
ERR_FAIL_INDEX(p_screen, screen_count);
screen_orientation = p_orientation;
if (@available(iOS 16.0, *)) {
[GDTAppDelegateService.viewController setNeedsUpdateOfSupportedInterfaceOrientations];
}
#if !defined(VISIONOS_ENABLED)
else {
[UIViewController attemptRotationToDeviceOrientation];
}
#endif
}
DisplayServer::ScreenOrientation DisplayServerAppleEmbedded::screen_get_orientation(int p_screen) const {
p_screen = _get_screen_index(p_screen);
int screen_count = get_screen_count();
ERR_FAIL_INDEX_V(p_screen, screen_count, SCREEN_LANDSCAPE);
return screen_orientation;
}
bool DisplayServerAppleEmbedded::window_can_draw(WindowID p_window) const {
return true;
}
bool DisplayServerAppleEmbedded::can_any_window_draw() const {
return true;
}
bool DisplayServerAppleEmbedded::is_touchscreen_available() const {
return true;
}
_FORCE_INLINE_ int _convert_utf32_offset_to_utf16(const String &p_existing_text, int p_pos) {
int limit = p_pos;
for (int i = 0; i < MIN(p_existing_text.length(), p_pos); i++) {
if (p_existing_text[i] > 0xffff) {
limit++;
}
}
return limit;
}
void DisplayServerAppleEmbedded::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) {
NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()];
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
GDTAppDelegateService.viewController.keyboardView.textContentType = nil;
switch (p_type) {
case KEYBOARD_TYPE_DEFAULT: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
} break;
case KEYBOARD_TYPE_MULTILINE: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
} break;
case KEYBOARD_TYPE_NUMBER: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeNumberPad;
} break;
case KEYBOARD_TYPE_NUMBER_DECIMAL: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDecimalPad;
} break;
case KEYBOARD_TYPE_PHONE: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypePhonePad;
GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeTelephoneNumber;
} break;
case KEYBOARD_TYPE_EMAIL_ADDRESS: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeEmailAddress;
GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeEmailAddress;
} break;
case KEYBOARD_TYPE_PASSWORD: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypePassword;
} break;
case KEYBOARD_TYPE_URL: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeWebSearch;
GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeURL;
} break;
}
[GDTAppDelegateService.viewController.keyboardView
becomeFirstResponderWithString:existingString
cursorStart:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_start)
cursorEnd:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_end)];
}
bool DisplayServerAppleEmbedded::is_keyboard_active() const {
return [GDTAppDelegateService.viewController.keyboardView isFirstResponder];
}
void DisplayServerAppleEmbedded::virtual_keyboard_hide() {
[GDTAppDelegateService.viewController.keyboardView resignFirstResponder];
}
void DisplayServerAppleEmbedded::virtual_keyboard_set_height(int height) {
virtual_keyboard_height = height * screen_get_max_scale();
}
int DisplayServerAppleEmbedded::virtual_keyboard_get_height() const {
return virtual_keyboard_height;
}
bool DisplayServerAppleEmbedded::has_hardware_keyboard() const {
if (@available(iOS 14.0, *)) {
return [GCKeyboard coalescedKeyboard];
} else {
return false;
}
}
void DisplayServerAppleEmbedded::clipboard_set(const String &p_text) {
[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8().get_data()];
}
String DisplayServerAppleEmbedded::clipboard_get() const {
NSString *text = [UIPasteboard generalPasteboard].string;
return String::utf8([text UTF8String]);
}
void DisplayServerAppleEmbedded::screen_set_keep_on(bool p_enable) {
[UIApplication sharedApplication].idleTimerDisabled = p_enable;
}
bool DisplayServerAppleEmbedded::screen_is_kept_on() const {
return [UIApplication sharedApplication].idleTimerDisabled;
}
void DisplayServerAppleEmbedded::resize_window(CGSize viewSize) {
Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
#if defined(RD_ENABLED)
if (rendering_context) {
rendering_context->window_set_size(MAIN_WINDOW_ID, size.x, size.y);
}
#endif
Variant resize_rect = Rect2i(Point2i(), size);
_window_callback(window_resize_callback, resize_rect);
}
void DisplayServerAppleEmbedded::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
_THREAD_SAFE_METHOD_
#if defined(RD_ENABLED)
if (rendering_context) {
rendering_context->window_set_vsync_mode(p_window, p_vsync_mode);
}
#endif
}
DisplayServer::VSyncMode DisplayServerAppleEmbedded::window_get_vsync_mode(WindowID p_window) const {
_THREAD_SAFE_METHOD_
#if defined(RD_ENABLED)
if (rendering_context) {
return rendering_context->window_get_vsync_mode(p_window);
}
#endif
return DisplayServer::VSYNC_ENABLED;
}

View File

@@ -0,0 +1,43 @@
/**************************************************************************/
/* godot_app_delegate.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
typedef NSObject<UIApplicationDelegate> GDTAppDelegateServiceProtocol;
@interface GDTApplicationDelegate : NSObject <UIApplicationDelegate>
@property(class, readonly, strong) NSArray<GDTAppDelegateServiceProtocol *> *services;
+ (void)addService:(GDTAppDelegateServiceProtocol *)service;
@end

View File

@@ -0,0 +1,463 @@
/**************************************************************************/
/* godot_app_delegate.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_service.h"
@implementation GDTApplicationDelegate
static NSMutableArray<GDTAppDelegateServiceProtocol *> *services = nil;
+ (NSArray<GDTAppDelegateServiceProtocol *> *)services {
return services;
}
+ (void)load {
services = [NSMutableArray new];
[services addObject:[GDTAppDelegateService new]];
}
+ (void)addService:(GDTAppDelegateServiceProtocol *)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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationDidBecomeActive:application];
}
}
- (void)applicationWillResignActive:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationWillResignActive:application];
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationDidEnterBackground:application];
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationWillEnterForeground:application];
}
}
- (void)applicationWillTerminate:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationWillTerminate:application];
}
}
// MARK: Environment Changes
- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationProtectedDataDidBecomeAvailable:application];
}
}
- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationProtectedDataWillBecomeUnavailable:application];
}
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationDidReceiveMemoryWarning:application];
}
}
- (void)applicationSignificantTimeChange:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application willEncodeRestorableStateWithCoder:coder];
}
}
- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder {
for (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application didUpdateUserActivity:userActivity];
}
}
- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error {
for (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application handleWatchKitExtensionRequest:userInfo reply:reply];
}
}
// MARK: HealthKit
- (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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 (GDTAppDelegateServiceProtocol *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

View File

@@ -0,0 +1,72 @@
/**************************************************************************/
/* godot_view_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
class String;
@class GDTView;
@protocol GDTDisplayLayer;
@protocol GDTViewRendererProtocol;
@protocol GDTViewDelegate
- (BOOL)godotViewFinishedSetup:(GDTView *)view;
@end
@interface GDTView : UIView
@property(assign, nonatomic) id<GDTViewRendererProtocol> renderer;
@property(assign, nonatomic) id<GDTViewDelegate> delegate;
@property(assign, readonly, nonatomic) BOOL isActive;
@property(assign, nonatomic) BOOL useCADisplayLink;
@property(strong, readonly, nonatomic) CALayer<GDTDisplayLayer> *renderingLayer;
@property(assign, readonly, nonatomic) BOOL canRender;
@property(assign, nonatomic) NSTimeInterval renderingInterval;
// Can be extended by subclasses
- (void)godot_commonInit;
// Implemented in subclasses
- (CALayer<GDTDisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
- (void)stopRendering;
- (void)startRendering;
@end
// Implemented in subclasses
extern GDTView *GDTViewCreate();

View File

@@ -0,0 +1,470 @@
/**************************************************************************/
/* godot_view_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_apple_embedded.h"
#import "display_layer_apple_embedded.h"
#import "display_server_apple_embedded.h"
#import "godot_view_renderer.h"
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#include "core/string/ustring.h"
#import <CoreMotion/CoreMotion.h>
static const int max_touches = 32;
static const float earth_gravity = 9.80665;
@interface GDTView () {
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<GDTDisplayLayer> *renderingLayer;
@property(strong, nonatomic) CMMotionManager *motionManager;
@end
@implementation GDTView
// Implemented in subclasses
- (CALayer<GDTDisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
return nil;
}
- (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;
}
}
- (void)godot_commonInit {
#if !defined(VISIONOS_ENABLED)
self.contentScaleFactor = [UIScreen mainScreen].scale;
#endif
if (@available(iOS 17.0, *)) {
[self registerForTraitChanges:@[ [UITraitUserInterfaceStyle class] ] withTarget:self action:@selector(traitCollectionDidChangeWithView:previousTraitCollection:)];
}
[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;
}
}
}
- (void)system_theme_changed {
DisplayServerAppleEmbedded *ds = (DisplayServerAppleEmbedded *)DisplayServer::get_singleton();
if (ds) {
ds->emit_system_theme_changed();
}
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
if (@available(iOS 13.0, *)) {
#if !defined(VISIONOS_ENABLED)
[super traitCollectionDidChange:previousTraitCollection];
#endif
[self traitCollectionDidChangeWithView:self
previousTraitCollection:previousTraitCollection];
}
}
- (void)traitCollectionDidChangeWithView:(UIView *)view previousTraitCollection:(UITraitCollection *)previousTraitCollection {
if (@available(iOS 13.0, *)) {
if ([UITraitCollection currentTraitCollection].userInterfaceStyle != previousTraitCollection.userInterfaceStyle) {
[self system_theme_changed];
}
}
}
- (void)stopRendering {
if (!self.isActive) {
return;
}
self.isActive = NO;
print_verbose("Stop animation!");
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;
print_verbose("Start animation!");
if (self.useCADisplayLink) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
if (GLOBAL_GET("display/window/ios/allow_high_refresh_rate")) {
self.displayLink.preferredFramesPerSecond = 120;
} else {
self.displayLink.preferredFramesPerSecond = 60;
}
// Setup DisplayLink in main thread
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
} else {
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60) target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}
}
- (void)drawView {
if (!self.isActive) {
print_verbose("Draw view not active!");
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 startRenderDisplayLayer];
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];
[self.renderingLayer stopRenderDisplayLayer];
}
- (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 (DisplayServerAppleEmbedded::get_singleton()) {
DisplayServerAppleEmbedded::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)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
CGPoint touchPoint = [touch locationInView:self];
DisplayServerAppleEmbedded::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
CGPoint touchPoint = [touch locationInView:self];
CGPoint prev_point = [touch previousLocationInView:self];
CGFloat alt = [touch altitudeAngle];
CGVector azim = [touch azimuthUnitVectorInView:self];
DisplayServerAppleEmbedded::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, [touch force] / [touch maximumPossibleForce], Vector2(azim.dx, azim.dy) * Math::cos(alt));
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
[self removeTouch:touch];
CGPoint touchPoint = [touch locationInView:self];
DisplayServerAppleEmbedded::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
DisplayServerAppleEmbedded::get_singleton()->touches_canceled(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 !defined(VISIONOS_ENABLED)
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 140000
interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
#else
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
}
#endif
#else
interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
#endif
switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeLeft: {
DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
} break;
case UIInterfaceOrientationLandscapeRight: {
DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
} break;
case UIInterfaceOrientationPortraitUpsideDown: {
DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI));
DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI));
DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI));
DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI));
} break;
default: { // assume portrait
DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z));
DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z));
DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z));
DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z));
} break;
}
}
@end

View File

@@ -0,0 +1,46 @@
/**************************************************************************/
/* godot_view_renderer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
@protocol GDTViewRendererProtocol <NSObject>
@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
- (BOOL)setupView:(UIView *)view;
- (void)renderOnView:(UIView *)view;
@end
@interface GDTViewRenderer : NSObject <GDTViewRendererProtocol>
@end

View File

@@ -0,0 +1,119 @@
/**************************************************************************/
/* godot_view_renderer.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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"
#import "display_server_apple_embedded.h"
#import "os_apple_embedded.h"
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#include "main/main.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 GDTViewRenderer ()
@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
@property(assign, nonatomic) BOOL hasStartedMain;
@property(assign, nonatomic) BOOL hasFinishedSetup;
@end
@implementation GDTViewRenderer
- (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_AppleEmbedded::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_AppleEmbedded::get_singleton()) {
return;
}
OS_AppleEmbedded::get_singleton()->iterate();
}
@end

View File

@@ -0,0 +1,44 @@
/**************************************************************************/
/* key_mapping_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#include "core/os/keyboard.h"
#import <UIKit/UIKit.h>
class KeyMappingAppleEmbedded {
KeyMappingAppleEmbedded() {}
public:
static void initialize();
static Key remap_key(CFIndex p_keycode);
static KeyLocation key_location(CFIndex p_keycode);
};

View File

@@ -0,0 +1,206 @@
/**************************************************************************/
/* key_mapping_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "key_mapping_apple_embedded.h"
#include "core/templates/hash_map.h"
struct HashMapHasherKeys {
static _FORCE_INLINE_ uint32_t hash(const Key p_key) { return hash_fmix32(static_cast<uint32_t>(p_key)); }
static _FORCE_INLINE_ uint32_t hash(const CFIndex p_key) { return hash_fmix32(p_key); }
};
HashMap<CFIndex, Key, HashMapHasherKeys> keyusage_map;
HashMap<CFIndex, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingAppleEmbedded::initialize() {
if (@available(iOS 13.4, *)) {
keyusage_map[UIKeyboardHIDUsageKeyboardA] = Key::A;
keyusage_map[UIKeyboardHIDUsageKeyboardB] = Key::B;
keyusage_map[UIKeyboardHIDUsageKeyboardC] = Key::C;
keyusage_map[UIKeyboardHIDUsageKeyboardD] = Key::D;
keyusage_map[UIKeyboardHIDUsageKeyboardE] = Key::E;
keyusage_map[UIKeyboardHIDUsageKeyboardF] = Key::F;
keyusage_map[UIKeyboardHIDUsageKeyboardG] = Key::G;
keyusage_map[UIKeyboardHIDUsageKeyboardH] = Key::H;
keyusage_map[UIKeyboardHIDUsageKeyboardI] = Key::I;
keyusage_map[UIKeyboardHIDUsageKeyboardJ] = Key::J;
keyusage_map[UIKeyboardHIDUsageKeyboardK] = Key::K;
keyusage_map[UIKeyboardHIDUsageKeyboardL] = Key::L;
keyusage_map[UIKeyboardHIDUsageKeyboardM] = Key::M;
keyusage_map[UIKeyboardHIDUsageKeyboardN] = Key::N;
keyusage_map[UIKeyboardHIDUsageKeyboardO] = Key::O;
keyusage_map[UIKeyboardHIDUsageKeyboardP] = Key::P;
keyusage_map[UIKeyboardHIDUsageKeyboardQ] = Key::Q;
keyusage_map[UIKeyboardHIDUsageKeyboardR] = Key::R;
keyusage_map[UIKeyboardHIDUsageKeyboardS] = Key::S;
keyusage_map[UIKeyboardHIDUsageKeyboardT] = Key::T;
keyusage_map[UIKeyboardHIDUsageKeyboardU] = Key::U;
keyusage_map[UIKeyboardHIDUsageKeyboardV] = Key::V;
keyusage_map[UIKeyboardHIDUsageKeyboardW] = Key::W;
keyusage_map[UIKeyboardHIDUsageKeyboardX] = Key::X;
keyusage_map[UIKeyboardHIDUsageKeyboardY] = Key::Y;
keyusage_map[UIKeyboardHIDUsageKeyboardZ] = Key::Z;
keyusage_map[UIKeyboardHIDUsageKeyboard0] = Key::KEY_0;
keyusage_map[UIKeyboardHIDUsageKeyboard1] = Key::KEY_1;
keyusage_map[UIKeyboardHIDUsageKeyboard2] = Key::KEY_2;
keyusage_map[UIKeyboardHIDUsageKeyboard3] = Key::KEY_3;
keyusage_map[UIKeyboardHIDUsageKeyboard4] = Key::KEY_4;
keyusage_map[UIKeyboardHIDUsageKeyboard5] = Key::KEY_5;
keyusage_map[UIKeyboardHIDUsageKeyboard6] = Key::KEY_6;
keyusage_map[UIKeyboardHIDUsageKeyboard7] = Key::KEY_7;
keyusage_map[UIKeyboardHIDUsageKeyboard8] = Key::KEY_8;
keyusage_map[UIKeyboardHIDUsageKeyboard9] = Key::KEY_9;
keyusage_map[UIKeyboardHIDUsageKeyboardBackslash] = Key::BACKSLASH;
keyusage_map[UIKeyboardHIDUsageKeyboardCloseBracket] = Key::BRACKETRIGHT;
keyusage_map[UIKeyboardHIDUsageKeyboardComma] = Key::COMMA;
keyusage_map[UIKeyboardHIDUsageKeyboardEqualSign] = Key::EQUAL;
keyusage_map[UIKeyboardHIDUsageKeyboardHyphen] = Key::MINUS;
keyusage_map[UIKeyboardHIDUsageKeyboardNonUSBackslash] = Key::SECTION;
keyusage_map[UIKeyboardHIDUsageKeyboardNonUSPound] = Key::ASCIITILDE;
keyusage_map[UIKeyboardHIDUsageKeyboardOpenBracket] = Key::BRACKETLEFT;
keyusage_map[UIKeyboardHIDUsageKeyboardPeriod] = Key::PERIOD;
keyusage_map[UIKeyboardHIDUsageKeyboardQuote] = Key::QUOTEDBL;
keyusage_map[UIKeyboardHIDUsageKeyboardSemicolon] = Key::SEMICOLON;
keyusage_map[UIKeyboardHIDUsageKeyboardSeparator] = Key::SECTION;
keyusage_map[UIKeyboardHIDUsageKeyboardSlash] = Key::SLASH;
keyusage_map[UIKeyboardHIDUsageKeyboardSpacebar] = Key::SPACE;
keyusage_map[UIKeyboardHIDUsageKeyboardCapsLock] = Key::CAPSLOCK;
keyusage_map[UIKeyboardHIDUsageKeyboardLeftAlt] = Key::ALT;
keyusage_map[UIKeyboardHIDUsageKeyboardLeftControl] = Key::CTRL;
keyusage_map[UIKeyboardHIDUsageKeyboardLeftShift] = Key::SHIFT;
keyusage_map[UIKeyboardHIDUsageKeyboardRightAlt] = Key::ALT;
keyusage_map[UIKeyboardHIDUsageKeyboardRightControl] = Key::CTRL;
keyusage_map[UIKeyboardHIDUsageKeyboardRightShift] = Key::SHIFT;
keyusage_map[UIKeyboardHIDUsageKeyboardScrollLock] = Key::SCROLLLOCK;
keyusage_map[UIKeyboardHIDUsageKeyboardLeftArrow] = Key::LEFT;
keyusage_map[UIKeyboardHIDUsageKeyboardRightArrow] = Key::RIGHT;
keyusage_map[UIKeyboardHIDUsageKeyboardUpArrow] = Key::UP;
keyusage_map[UIKeyboardHIDUsageKeyboardDownArrow] = Key::DOWN;
keyusage_map[UIKeyboardHIDUsageKeyboardPageUp] = Key::PAGEUP;
keyusage_map[UIKeyboardHIDUsageKeyboardPageDown] = Key::PAGEDOWN;
keyusage_map[UIKeyboardHIDUsageKeyboardHome] = Key::HOME;
keyusage_map[UIKeyboardHIDUsageKeyboardEnd] = Key::END;
keyusage_map[UIKeyboardHIDUsageKeyboardDeleteForward] = Key::KEY_DELETE;
keyusage_map[UIKeyboardHIDUsageKeyboardDeleteOrBackspace] = Key::BACKSPACE;
keyusage_map[UIKeyboardHIDUsageKeyboardEscape] = Key::ESCAPE;
keyusage_map[UIKeyboardHIDUsageKeyboardInsert] = Key::INSERT;
keyusage_map[UIKeyboardHIDUsageKeyboardReturn] = Key::ENTER;
keyusage_map[UIKeyboardHIDUsageKeyboardTab] = Key::TAB;
keyusage_map[UIKeyboardHIDUsageKeyboardF1] = Key::F1;
keyusage_map[UIKeyboardHIDUsageKeyboardF2] = Key::F2;
keyusage_map[UIKeyboardHIDUsageKeyboardF3] = Key::F3;
keyusage_map[UIKeyboardHIDUsageKeyboardF4] = Key::F4;
keyusage_map[UIKeyboardHIDUsageKeyboardF5] = Key::F5;
keyusage_map[UIKeyboardHIDUsageKeyboardF6] = Key::F6;
keyusage_map[UIKeyboardHIDUsageKeyboardF7] = Key::F7;
keyusage_map[UIKeyboardHIDUsageKeyboardF8] = Key::F8;
keyusage_map[UIKeyboardHIDUsageKeyboardF9] = Key::F9;
keyusage_map[UIKeyboardHIDUsageKeyboardF10] = Key::F10;
keyusage_map[UIKeyboardHIDUsageKeyboardF11] = Key::F11;
keyusage_map[UIKeyboardHIDUsageKeyboardF12] = Key::F12;
keyusage_map[UIKeyboardHIDUsageKeyboardF13] = Key::F13;
keyusage_map[UIKeyboardHIDUsageKeyboardF14] = Key::F14;
keyusage_map[UIKeyboardHIDUsageKeyboardF15] = Key::F15;
keyusage_map[UIKeyboardHIDUsageKeyboardF16] = Key::F16;
keyusage_map[UIKeyboardHIDUsageKeyboardF17] = Key::F17;
keyusage_map[UIKeyboardHIDUsageKeyboardF18] = Key::F18;
keyusage_map[UIKeyboardHIDUsageKeyboardF19] = Key::F19;
keyusage_map[UIKeyboardHIDUsageKeyboardF20] = Key::F20;
keyusage_map[UIKeyboardHIDUsageKeyboardF21] = Key::F21;
keyusage_map[UIKeyboardHIDUsageKeyboardF22] = Key::F22;
keyusage_map[UIKeyboardHIDUsageKeyboardF23] = Key::F23;
keyusage_map[UIKeyboardHIDUsageKeyboardF24] = Key::F24;
keyusage_map[UIKeyboardHIDUsageKeypad0] = Key::KP_0;
keyusage_map[UIKeyboardHIDUsageKeypad1] = Key::KP_1;
keyusage_map[UIKeyboardHIDUsageKeypad2] = Key::KP_2;
keyusage_map[UIKeyboardHIDUsageKeypad3] = Key::KP_3;
keyusage_map[UIKeyboardHIDUsageKeypad4] = Key::KP_4;
keyusage_map[UIKeyboardHIDUsageKeypad5] = Key::KP_5;
keyusage_map[UIKeyboardHIDUsageKeypad6] = Key::KP_6;
keyusage_map[UIKeyboardHIDUsageKeypad7] = Key::KP_7;
keyusage_map[UIKeyboardHIDUsageKeypad8] = Key::KP_8;
keyusage_map[UIKeyboardHIDUsageKeypad9] = Key::KP_9;
keyusage_map[UIKeyboardHIDUsageKeypadAsterisk] = Key::KP_MULTIPLY;
keyusage_map[UIKeyboardHIDUsageKeyboardGraveAccentAndTilde] = Key::BAR;
keyusage_map[UIKeyboardHIDUsageKeypadEnter] = Key::KP_ENTER;
keyusage_map[UIKeyboardHIDUsageKeypadHyphen] = Key::KP_SUBTRACT;
keyusage_map[UIKeyboardHIDUsageKeypadNumLock] = Key::NUMLOCK;
keyusage_map[UIKeyboardHIDUsageKeypadPeriod] = Key::KP_PERIOD;
keyusage_map[UIKeyboardHIDUsageKeypadPlus] = Key::KP_ADD;
keyusage_map[UIKeyboardHIDUsageKeypadSlash] = Key::KP_DIVIDE;
keyusage_map[UIKeyboardHIDUsageKeyboardPause] = Key::PAUSE;
keyusage_map[UIKeyboardHIDUsageKeyboardStop] = Key::STOP;
keyusage_map[UIKeyboardHIDUsageKeyboardMute] = Key::VOLUMEMUTE;
keyusage_map[UIKeyboardHIDUsageKeyboardVolumeUp] = Key::VOLUMEUP;
keyusage_map[UIKeyboardHIDUsageKeyboardVolumeDown] = Key::VOLUMEDOWN;
keyusage_map[UIKeyboardHIDUsageKeyboardFind] = Key::SEARCH;
keyusage_map[UIKeyboardHIDUsageKeyboardHelp] = Key::HELP;
keyusage_map[UIKeyboardHIDUsageKeyboardLeftGUI] = Key::META;
keyusage_map[UIKeyboardHIDUsageKeyboardRightGUI] = Key::META;
keyusage_map[UIKeyboardHIDUsageKeyboardMenu] = Key::MENU;
keyusage_map[UIKeyboardHIDUsageKeyboardPrintScreen] = Key::PRINT;
keyusage_map[UIKeyboardHIDUsageKeyboardReturnOrEnter] = Key::ENTER;
keyusage_map[UIKeyboardHIDUsageKeyboardSysReqOrAttention] = Key::SYSREQ;
keyusage_map[0x01AE] = Key::KEYBOARD; // On-screen keyboard key on smart connector keyboard.
keyusage_map[0x029D] = Key::GLOBE; // "Globe" key on smart connector / Mac keyboard.
keyusage_map[UIKeyboardHIDUsageKeyboardLANG1] = Key::JIS_EISU;
keyusage_map[UIKeyboardHIDUsageKeyboardLANG2] = Key::JIS_KANA;
location_map[UIKeyboardHIDUsageKeyboardLeftAlt] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightAlt] = KeyLocation::RIGHT;
location_map[UIKeyboardHIDUsageKeyboardLeftControl] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightControl] = KeyLocation::RIGHT;
location_map[UIKeyboardHIDUsageKeyboardLeftShift] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightShift] = KeyLocation::RIGHT;
location_map[UIKeyboardHIDUsageKeyboardLeftGUI] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightGUI] = KeyLocation::RIGHT;
}
}
Key KeyMappingAppleEmbedded::remap_key(CFIndex p_keycode) {
if (@available(iOS 13.4, *)) {
const Key *key = keyusage_map.getptr(p_keycode);
if (key) {
return *key;
}
}
return Key::NONE;
}
KeyLocation KeyMappingAppleEmbedded::key_location(CFIndex p_keycode) {
if (@available(iOS 13.4, *)) {
const KeyLocation *location = location_map.getptr(p_keycode);
if (location) {
return *location;
}
}
return KeyLocation::UNSPECIFIED;
}

View File

@@ -0,0 +1,39 @@
/**************************************************************************/
/* keyboard_input_view.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
@interface GDTKeyboardInputView : UITextView
- (BOOL)becomeFirstResponderWithString:(NSString *)existingString cursorStart:(NSInteger)start cursorEnd:(NSInteger)end;
@end

View File

@@ -0,0 +1,201 @@
/**************************************************************************/
/* keyboard_input_view.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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"
#import "display_server_apple_embedded.h"
#import "os_apple_embedded.h"
#include "core/os/keyboard.h"
@interface GDTKeyboardInputView () <UITextViewDelegate>
@property(nonatomic, copy) NSString *previousText;
@property(nonatomic, assign) NSRange previousSelectedRange;
@end
@implementation GDTKeyboardInputView
- (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 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++) {
DisplayServerAppleEmbedded::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
DisplayServerAppleEmbedded::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
}
}
- (void)enterText:(NSString *)substring {
String characters = String::utf8([substring UTF8String]);
for (int i = 0; i < characters.size(); i++) {
int character = characters[i];
Key key = Key::NONE;
if (character == '\t') { // 0x09
key = Key::TAB;
} else if (character == '\n') { // 0x0A
key = Key::ENTER;
} else if (character == 0x2006) {
key = Key::SPACE;
}
DisplayServerAppleEmbedded::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
DisplayServerAppleEmbedded::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
}
}
// MARK: Observer
- (void)observeTextChange:(NSNotification *)notification {
if (notification.object != self) {
return;
}
NSString *substringToDelete = nil;
if (self.previousSelectedRange.length == 0) {
// Get previous text to delete.
substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location];
} 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 = nil;
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];
}
NSInteger skip = 0;
if (substringToDelete != nil) {
for (NSUInteger i = 0; i < MIN([substringToDelete length], [substringToEnter length]); i++) {
if ([substringToDelete characterAtIndex:i] == [substringToEnter characterAtIndex:i]) {
skip++;
} else {
break;
}
}
[self deleteText:[substringToDelete length] - skip]; // Delete changed part of previous text.
}
[self enterText:[substringToEnter substringFromIndex:skip]]; // Enter changed part of new text.
self.previousText = self.text;
self.previousSelectedRange = self.selectedRange;
}
@end

View File

@@ -0,0 +1,35 @@
/**************************************************************************/
/* main_utilities.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
void change_to_launch_dir(char **p_args);
int process_args(int p_argc, char **p_args, char **r_args);

View File

@@ -0,0 +1,95 @@
/**************************************************************************/
/* main_utilities.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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"
#import <UIKit/UIKit.h>
#include <unistd.h>
#include <cstdio>
void change_to_launch_dir(char **p_args) {
size_t len = strlen(p_args[0]);
while (len--) {
if (p_args[0][len] == '/') {
break;
}
}
if (len >= 0) {
char path[512];
memcpy(path, p_args[0], len > sizeof(path) ? sizeof(path) : len);
path[len] = 0;
chdir(path);
}
}
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 process_args(int p_argc, char **p_args, char **r_args) {
for (int i = 0; i < p_argc; i++) {
r_args[i] = p_args[i];
}
r_args[p_argc] = nullptr;
p_argc = add_path(p_argc, r_args);
p_argc = add_cmdline(p_argc, r_args);
return p_argc;
}

View File

@@ -0,0 +1,145 @@
/**************************************************************************/
/* os_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#ifdef APPLE_EMBEDDED_ENABLED
#import "apple_embedded.h"
#import "drivers/apple/joypad_apple.h"
#import "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
#include "servers/audio_server.h"
#include "servers/rendering/renderer_compositor.h"
#if defined(RD_ENABLED)
#include "servers/rendering/rendering_device.h"
#if defined(VULKAN_ENABLED)
#import "rendering_context_driver_vulkan_apple_embedded.h"
#endif
#endif
class OS_AppleEmbedded : public OS_Unix {
private:
static HashMap<String, void *> dynamic_symbol_lookup_table;
friend void register_dynamic_symbol(char *name, void *address);
AudioDriverCoreAudio audio_driver;
AppleEmbedded *apple_embedded = nullptr;
JoypadApple *joypad_apple = 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;
bool is_focused = false;
CGFloat _weight_to_ct(int p_weight) const;
CGFloat _stretch_to_ct(int p_stretch) const;
String _get_default_fontname(const String &p_font_name) const;
static _FORCE_INLINE_ String get_framework_executable(const String &p_path);
void deinitialize_modules();
mutable String remote_fs_dir;
public:
static OS_AppleEmbedded *get_singleton();
OS_AppleEmbedded();
~OS_AppleEmbedded();
void initialize_modules();
bool iterate();
void start();
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
virtual Vector<String> get_system_fonts() const override;
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = 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_distribution_name() const override;
virtual String get_version() const override;
virtual String get_model_name() const override;
virtual Error shell_open(const String &p_uri) override;
virtual String get_user_data_dir(const String &p_user_dir) const override;
virtual String get_cache_path() const override;
virtual String get_temp_path() const override;
virtual String get_resource_dir() 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, float p_amplitude = -1.0) override;
virtual bool _check_internal_feature_support(const String &p_feature) override;
virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) override;
void on_focus_out();
void on_focus_in();
void on_enter_background();
void on_exit_background();
virtual Rect2 calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const override;
virtual bool request_permission(const String &p_name) override;
virtual Vector<String> get_granted_permissions() const override;
};
#endif // APPLE_EMBEDDED_ENABLED

View File

@@ -0,0 +1,768 @@
/**************************************************************************/
/* os_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "os_apple_embedded.h"
#ifdef APPLE_EMBEDDED_ENABLED
#import "app_delegate_service.h"
#import "display_server_apple_embedded.h"
#import "godot_view_apple_embedded.h"
#import "view_controller.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#import "drivers/apple/os_log_logger.h"
#include "main/main.h"
#import <AVFoundation/AVFAudio.h>
#import <AudioToolbox/AudioServices.h>
#import <CoreText/CoreText.h>
#import <UIKit/UIKit.h>
#import <dlfcn.h>
#include <sys/sysctl.h>
#if defined(RD_ENABLED)
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#import <QuartzCore/CAMetalLayer.h>
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/godot_vulkan.h"
#endif // VULKAN_ENABLED
#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 *apple_init_callbacks = nullptr;
static int apple_embedded_platform_init_callbacks_count = 0;
static int apple_embedded_platform_init_callbacks_capacity = 0;
HashMap<String, void *> OS_AppleEmbedded::dynamic_symbol_lookup_table;
void add_apple_embedded_platform_init_callback(init_callback cb) {
if (apple_embedded_platform_init_callbacks_count == apple_embedded_platform_init_callbacks_capacity) {
void *new_ptr = realloc(apple_init_callbacks, sizeof(cb) * (apple_embedded_platform_init_callbacks_capacity + 32));
if (new_ptr) {
apple_init_callbacks = (init_callback *)(new_ptr);
apple_embedded_platform_init_callbacks_capacity += 32;
} else {
ERR_FAIL_MSG("Unable to allocate memory for extension callbacks.");
}
}
apple_init_callbacks[apple_embedded_platform_init_callbacks_count++] = cb;
}
void register_dynamic_symbol(char *name, void *address) {
OS_AppleEmbedded::dynamic_symbol_lookup_table[String(name)] = address;
}
Rect2 fit_keep_aspect_centered(const Vector2 &p_container, const Vector2 &p_rect) {
real_t available_ratio = p_container.width / p_container.height;
real_t fit_ratio = p_rect.width / p_rect.height;
Rect2 result;
if (fit_ratio < available_ratio) {
// Fit height - we'll have horizontal gaps
result.size.height = p_container.height;
result.size.width = p_container.height * fit_ratio;
result.position.y = 0;
result.position.x = (p_container.width - result.size.width) * 0.5f;
} else {
// Fit width - we'll have vertical gaps
result.size.width = p_container.width;
result.size.height = p_container.width / fit_ratio;
result.position.x = 0;
result.position.y = (p_container.height - result.size.height) * 0.5f;
}
return result;
}
Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) {
real_t available_ratio = p_container.width / p_container.height;
real_t fit_ratio = p_rect.width / p_rect.height;
Rect2 result;
if (fit_ratio < available_ratio) {
// Need to scale up to fit width, and crop height
result.size.width = p_container.width;
result.size.height = p_container.width / fit_ratio;
result.position.x = 0;
result.position.y = (p_container.height - result.size.height) * 0.5f;
} else {
// Need to scale up to fit height, and crop width
result.size.width = p_container.height * fit_ratio;
result.size.height = p_container.height;
result.position.x = (p_container.width - result.size.width) * 0.5f;
result.position.y = 0;
}
return result;
}
OS_AppleEmbedded *OS_AppleEmbedded::get_singleton() {
return (OS_AppleEmbedded *)OS::get_singleton();
}
OS_AppleEmbedded::OS_AppleEmbedded() {
for (int i = 0; i < apple_embedded_platform_init_callbacks_count; ++i) {
apple_init_callbacks[i]();
}
free(apple_init_callbacks);
apple_init_callbacks = nullptr;
apple_embedded_platform_init_callbacks_count = 0;
apple_embedded_platform_init_callbacks_capacity = 0;
main_loop = nullptr;
Vector<Logger *> loggers;
loggers.push_back(memnew(OsLogLogger(NSBundle.mainBundle.bundleIdentifier.UTF8String)));
_set_logger(memnew(CompositeLogger(loggers)));
AudioDriverManager::add_driver(&audio_driver);
}
OS_AppleEmbedded::~OS_AppleEmbedded() {}
void OS_AppleEmbedded::alert(const String &p_alert, const String &p_title) {
const CharString utf8_alert = p_alert.utf8();
const CharString utf8_title = p_title.utf8();
AppleEmbedded::alert(utf8_alert.get_data(), utf8_title.get_data());
}
void OS_AppleEmbedded::initialize_core() {
OS_Unix::initialize_core();
}
void OS_AppleEmbedded::initialize() {
initialize_core();
}
void OS_AppleEmbedded::initialize_joypads() {
joypad_apple = memnew(JoypadApple);
}
void OS_AppleEmbedded::initialize_modules() {
apple_embedded = memnew(AppleEmbedded);
Engine::get_singleton()->add_singleton(Engine::Singleton("AppleEmbedded", apple_embedded));
}
void OS_AppleEmbedded::deinitialize_modules() {
if (joypad_apple) {
memdelete(joypad_apple);
}
if (apple_embedded) {
memdelete(apple_embedded);
}
}
void OS_AppleEmbedded::set_main_loop(MainLoop *p_main_loop) {
main_loop = p_main_loop;
}
MainLoop *OS_AppleEmbedded::get_main_loop() const {
return main_loop;
}
void OS_AppleEmbedded::delete_main_loop() {
if (main_loop) {
main_loop->finalize();
memdelete(main_loop);
}
main_loop = nullptr;
}
bool OS_AppleEmbedded::iterate() {
if (!main_loop) {
return true;
}
if (DisplayServer::get_singleton()) {
DisplayServer::get_singleton()->process_events();
}
joypad_apple->process_joypads();
return Main::iteration();
}
void OS_AppleEmbedded::start() {
if (Main::start() == EXIT_SUCCESS) {
main_loop->initialize();
}
}
void OS_AppleEmbedded::finalize() {
deinitialize_modules();
// Already gets called
//delete_main_loop();
}
// MARK: Dynamic Libraries
_FORCE_INLINE_ String OS_AppleEmbedded::get_framework_executable(const String &p_path) {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
// Read framework bundle to get executable name.
NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
NSBundle *bundle = [NSBundle bundleWithURL:url];
if (bundle) {
String exe_path = String::utf8([[bundle executablePath] UTF8String]);
if (da->file_exists(exe_path)) {
return exe_path;
}
}
// Try default executable name (invalid framework).
if (da->dir_exists(p_path) && da->file_exists(p_path.path_join(p_path.get_file().get_basename()))) {
return p_path.path_join(p_path.get_file().get_basename());
}
// Not a framework, try loading as .dylib.
return p_path;
}
Error OS_AppleEmbedded::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
if (p_path.length() == 0) {
// Static xcframework.
p_library_handle = RTLD_SELF;
if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
*p_data->r_resolved_path = p_path;
}
return OK;
}
String path = get_framework_executable(p_path);
if (!FileAccess::exists(path)) {
// Load .dylib or framework from within the executable path.
path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file()));
}
if (!FileAccess::exists(path)) {
// Load .dylib converted to framework from within the executable path.
path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file().get_basename() + ".framework"));
}
if (!FileAccess::exists(path)) {
// Load .dylib or framework from a standard iOS location.
path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file()));
}
if (!FileAccess::exists(path)) {
// Load .dylib converted to framework from a standard iOS location.
path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file().get_basename() + ".framework"));
}
ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
*p_data->r_resolved_path = path;
}
return OK;
}
Error OS_AppleEmbedded::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_AppleEmbedded::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_AppleEmbedded::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_AppleEmbedded::get_distribution_name() const {
return get_name();
}
String OS_AppleEmbedded::get_version() const {
NSOperatingSystemVersion ver = [NSProcessInfo processInfo].operatingSystemVersion;
return vformat("%d.%d.%d", (int64_t)ver.majorVersion, (int64_t)ver.minorVersion, (int64_t)ver.patchVersion);
}
String OS_AppleEmbedded::get_model_name() const {
String model = apple_embedded->get_model();
if (model != "") {
return model;
}
return OS_Unix::get_model_name();
}
Error OS_AppleEmbedded::shell_open(const 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;
}
print_verbose(vformat("Opening URL %s", p_uri));
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
return OK;
}
String OS_AppleEmbedded::get_user_data_dir(const String &p_user_dir) const {
static String ret;
if (ret.is_empty()) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
if (paths && [paths count] >= 1) {
ret.append_utf8([[paths firstObject] UTF8String]);
}
}
return ret;
}
String OS_AppleEmbedded::get_cache_path() const {
static String ret;
if (ret.is_empty()) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
if (paths && [paths count] >= 1) {
ret.append_utf8([[paths firstObject] UTF8String]);
}
}
return ret;
}
String OS_AppleEmbedded::get_temp_path() const {
static String ret;
if (ret.is_empty()) {
NSURL *url = [NSURL fileURLWithPath:NSTemporaryDirectory()
isDirectory:YES];
if (url) {
ret = String::utf8([url.path UTF8String]);
ret = ret.trim_prefix("file://");
}
}
return ret;
}
String OS_AppleEmbedded::get_resource_dir() const {
#ifdef TOOLS_ENABLED
return OS_Unix::get_resource_dir();
#else
if (remote_fs_dir.is_empty()) {
return OS_Unix::get_resource_dir();
} else {
return remote_fs_dir;
}
#endif
}
String OS_AppleEmbedded::get_locale() const {
NSString *preferredLanguage = [NSLocale preferredLanguages].firstObject;
if (preferredLanguage) {
return String::utf8([preferredLanguage UTF8String]).replace_char('-', '_');
}
NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier];
return String::utf8([localeIdentifier UTF8String]).replace_char('-', '_');
}
String OS_AppleEmbedded::get_unique_id() const {
NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
return String::utf8([uuid UTF8String]);
}
String OS_AppleEmbedded::get_processor_name() const {
char buffer[256];
size_t buffer_len = 256;
if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, nullptr, 0) == 0) {
return String::utf8(buffer, buffer_len);
}
ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
}
Vector<String> OS_AppleEmbedded::get_system_fonts() const {
HashSet<String> font_names;
CFArrayRef fonts = CTFontManagerCopyAvailableFontFamilyNames();
if (fonts) {
for (CFIndex i = 0; i < CFArrayGetCount(fonts); i++) {
CFStringRef cf_name = (CFStringRef)CFArrayGetValueAtIndex(fonts, i);
if (cf_name && (CFStringGetLength(cf_name) > 0) && (CFStringCompare(cf_name, CFSTR("LastResort"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) && (CFStringGetCharacterAtIndex(cf_name, 0) != '.')) {
NSString *ns_name = (__bridge NSString *)cf_name;
font_names.insert(String::utf8([ns_name UTF8String]));
}
}
CFRelease(fonts);
}
Vector<String> ret;
for (const String &E : font_names) {
ret.push_back(E);
}
return ret;
}
String OS_AppleEmbedded::_get_default_fontname(const String &p_font_name) const {
String font_name = p_font_name;
if (font_name.to_lower() == "sans-serif") {
font_name = "Helvetica";
} else if (font_name.to_lower() == "serif") {
font_name = "Times";
} else if (font_name.to_lower() == "monospace") {
font_name = "Courier";
} else if (font_name.to_lower() == "fantasy") {
font_name = "Papyrus";
} else if (font_name.to_lower() == "cursive") {
font_name = "Apple Chancery";
};
return font_name;
}
CGFloat OS_AppleEmbedded::_weight_to_ct(int p_weight) const {
if (p_weight < 150) {
return -0.80;
} else if (p_weight < 250) {
return -0.60;
} else if (p_weight < 350) {
return -0.40;
} else if (p_weight < 450) {
return 0.0;
} else if (p_weight < 550) {
return 0.23;
} else if (p_weight < 650) {
return 0.30;
} else if (p_weight < 750) {
return 0.40;
} else if (p_weight < 850) {
return 0.56;
} else if (p_weight < 925) {
return 0.62;
} else {
return 1.00;
}
}
CGFloat OS_AppleEmbedded::_stretch_to_ct(int p_stretch) const {
if (p_stretch < 56) {
return -0.5;
} else if (p_stretch < 69) {
return -0.37;
} else if (p_stretch < 81) {
return -0.25;
} else if (p_stretch < 93) {
return -0.13;
} else if (p_stretch < 106) {
return 0.0;
} else if (p_stretch < 137) {
return 0.13;
} else if (p_stretch < 144) {
return 0.25;
} else if (p_stretch < 162) {
return 0.37;
} else {
return 0.5;
}
}
Vector<String> OS_AppleEmbedded::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const {
Vector<String> ret;
String font_name = _get_default_fontname(p_font_name);
CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
CTFontSymbolicTraits traits = 0;
if (p_weight >= 700) {
traits |= kCTFontBoldTrait;
}
if (p_italic) {
traits |= kCTFontItalicTrait;
}
if (p_stretch < 100) {
traits |= kCTFontCondensedTrait;
} else if (p_stretch > 100) {
traits |= kCTFontExpandedTrait;
}
CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
CGFloat weight = _weight_to_ct(p_weight);
CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
CGFloat stretch = _stretch_to_ct(p_stretch);
CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
if (font) {
CTFontRef family = CTFontCreateWithFontDescriptor(font, 0, nullptr);
if (family) {
CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, p_text.utf8().get_data(), kCFStringEncodingUTF8);
CFRange range = CFRangeMake(0, CFStringGetLength(string));
CTFontRef fallback_family = CTFontCreateForString(family, string, range);
if (fallback_family) {
CTFontDescriptorRef fallback_font = CTFontCopyFontDescriptor(fallback_family);
if (fallback_font) {
CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fallback_font, kCTFontURLAttribute);
if (url) {
NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
ret.push_back(String::utf8([font_path UTF8String]));
CFRelease(url);
}
CFRelease(fallback_font);
}
CFRelease(fallback_family);
}
CFRelease(string);
CFRelease(family);
}
CFRelease(font);
}
CFRelease(attributes);
CFRelease(traits_dict);
CFRelease(sym_traits);
CFRelease(font_stretch);
CFRelease(font_weight);
CFRelease(name);
return ret;
}
String OS_AppleEmbedded::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const {
String ret;
String font_name = _get_default_fontname(p_font_name);
CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
CTFontSymbolicTraits traits = 0;
if (p_weight >= 700) {
traits |= kCTFontBoldTrait;
}
if (p_italic) {
traits |= kCTFontItalicTrait;
}
if (p_stretch < 100) {
traits |= kCTFontCondensedTrait;
} else if (p_stretch > 100) {
traits |= kCTFontExpandedTrait;
}
CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
CGFloat weight = _weight_to_ct(p_weight);
CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
CGFloat stretch = _stretch_to_ct(p_stretch);
CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
if (font) {
CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(font, kCTFontURLAttribute);
if (url) {
NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
ret = String::utf8([font_path UTF8String]);
CFRelease(url);
}
CFRelease(font);
}
CFRelease(attributes);
CFRelease(traits_dict);
CFRelease(sym_traits);
CFRelease(font_stretch);
CFRelease(font_weight);
CFRelease(name);
return ret;
}
void OS_AppleEmbedded::vibrate_handheld(int p_duration_ms, float p_amplitude) {
if (apple_embedded->supports_haptic_engine()) {
if (p_amplitude > 0.0) {
p_amplitude = CLAMP(p_amplitude, 0.0, 1.0);
}
apple_embedded->vibrate_haptic_engine((float)p_duration_ms / 1000.f, p_amplitude);
} else {
// iOS <13 does not support duration for vibration
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}
}
bool OS_AppleEmbedded::_check_internal_feature_support(const String &p_feature) {
if (p_feature == "system_fonts") {
return true;
}
if (p_feature == "mobile") {
return true;
}
return false;
}
Error OS_AppleEmbedded::setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) {
r_project_path = OS::get_user_data_dir();
Error err = OS_Unix::setup_remote_filesystem(p_server_host, p_port, p_password, r_project_path);
if (err == OK) {
remote_fs_dir = r_project_path;
}
return err;
}
void OS_AppleEmbedded::on_focus_out() {
if (is_focused) {
is_focused = false;
if (DisplayServerAppleEmbedded::get_singleton()) {
DisplayServerAppleEmbedded::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
}
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
}
[GDTAppDelegateService.viewController.godotView stopRendering];
audio_driver.stop();
}
}
void OS_AppleEmbedded::on_focus_in() {
if (!is_focused) {
is_focused = true;
if (DisplayServerAppleEmbedded::get_singleton()) {
DisplayServerAppleEmbedded::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
}
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
}
[GDTAppDelegateService.viewController.godotView startRendering];
audio_driver.start();
}
}
void OS_AppleEmbedded::on_enter_background() {
// Do not check for is_focused, because on_focus_out will always be fired first by applicationWillResignActive.
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED);
}
on_focus_out();
}
void OS_AppleEmbedded::on_exit_background() {
if (!is_focused) {
on_focus_in();
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED);
}
}
}
Rect2 OS_AppleEmbedded::calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const {
String scalemodestr = GLOBAL_GET("ios/launch_screen_image_mode");
if (scalemodestr == "scaleAspectFit") {
return fit_keep_aspect_centered(p_window_size, p_imgrect_size);
} else if (scalemodestr == "scaleAspectFill") {
return fit_keep_aspect_covered(p_window_size, p_imgrect_size);
} else if (scalemodestr == "scaleToFill") {
return Rect2(Point2(), p_window_size);
} else if (scalemodestr == "center") {
return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size);
} else {
WARN_PRINT(vformat("Boot screen scale mode mismatch between iOS and Godot: %s not supported", scalemodestr));
return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size);
}
}
bool OS_AppleEmbedded::request_permission(const String &p_name) {
if (p_name == "appleembedded.permission.AUDIO_RECORD") {
if (@available(iOS 17.0, *)) {
AVAudioApplicationRecordPermission permission = [AVAudioApplication sharedInstance].recordPermission;
if (permission == AVAudioApplicationRecordPermissionGranted) {
// Permission already granted, you can start recording.
return true;
} else if (permission == AVAudioApplicationRecordPermissionDenied) {
// Permission denied, or not yet granted.
return false;
} else {
// Request the permission, but for now return false as documented.
[AVAudioApplication requestRecordPermissionWithCompletionHandler:^(BOOL granted) {
get_main_loop()->emit_signal(SNAME("on_request_permissions_result"), p_name, granted);
}];
}
}
}
return false;
}
Vector<String> OS_AppleEmbedded::get_granted_permissions() const {
Vector<String> ret;
if (@available(iOS 17.0, *)) {
if ([AVAudioApplication sharedInstance].recordPermission == AVAudioApplicationRecordPermissionGranted) {
ret.push_back("appleembedded.permission.AUDIO_RECORD");
}
}
return ret;
}
#endif // APPLE_EMBEDDED_ENABLED

View File

@@ -0,0 +1,44 @@
/**************************************************************************/
/* platform_config.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#include <alloca.h>
#define PLATFORM_THREAD_OVERRIDE
#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")

View File

@@ -0,0 +1,55 @@
/**************************************************************************/
/* rendering_context_driver_vulkan_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#ifdef VULKAN_ENABLED
#include "drivers/vulkan/rendering_context_driver_vulkan.h"
#import <QuartzCore/CAMetalLayer.h>
class RenderingContextDriverVulkanAppleEmbedded : public RenderingContextDriverVulkan {
private:
virtual const char *_get_platform_surface_extension() const override final;
protected:
SurfaceID surface_create(const void *p_platform_data) override final;
public:
struct WindowPlatformData {
CAMetalLayer *const *layer_ptr;
};
RenderingContextDriverVulkanAppleEmbedded();
~RenderingContextDriverVulkanAppleEmbedded();
};
#endif // VULKAN_ENABLED

View File

@@ -0,0 +1,69 @@
/**************************************************************************/
/* rendering_context_driver_vulkan_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "rendering_context_driver_vulkan_apple_embedded.h"
#ifdef VULKAN_ENABLED
#ifdef USE_VOLK
#include <volk.h>
#else
#include <vulkan/vulkan_metal.h>
#endif
const char *RenderingContextDriverVulkanAppleEmbedded::_get_platform_surface_extension() const {
return VK_EXT_METAL_SURFACE_EXTENSION_NAME;
}
RenderingContextDriver::SurfaceID RenderingContextDriverVulkanAppleEmbedded::surface_create(const void *p_platform_data) {
const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
VkMetalSurfaceCreateInfoEXT create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
create_info.pLayer = *wpd->layer_ptr;
VkSurfaceKHR vk_surface = VK_NULL_HANDLE;
VkResult err = vkCreateMetalSurfaceEXT(instance_get(), &create_info, get_allocation_callbacks(VK_OBJECT_TYPE_SURFACE_KHR), &vk_surface);
ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID());
Surface *surface = memnew(Surface);
surface->vk_surface = vk_surface;
return SurfaceID(surface);
}
RenderingContextDriverVulkanAppleEmbedded::RenderingContextDriverVulkanAppleEmbedded() {
// Does nothing.
}
RenderingContextDriverVulkanAppleEmbedded::~RenderingContextDriverVulkanAppleEmbedded() {
// Does nothing.
}
#endif // VULKAN_ENABLED

View File

@@ -0,0 +1,60 @@
/**************************************************************************/
/* tts_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#include "core/string/ustring.h"
#include "core/templates/hash_map.h"
#include "core/templates/list.h"
#include "core/variant/array.h"
#include "servers/display_server.h"
#if __has_include(<AVFAudio/AVSpeechSynthesis.h>)
#import <AVFAudio/AVSpeechSynthesis.h>
#else
#import <AVFoundation/AVFoundation.h>
#endif
@interface GDTTTS : 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

View File

@@ -0,0 +1,164 @@
/**************************************************************************/
/* tts_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "tts_apple_embedded.h"
@implementation GDTTTS
- (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::remap(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)];
} else if (message.rate < 1.f) {
[new_utterance setRate:Math::remap(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)];
}
[new_utterance setPitchMultiplier:message.pitch];
[new_utterance setVolume:(Math::remap(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

View File

@@ -0,0 +1,43 @@
/**************************************************************************/
/* view_controller.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
@class GDTView;
@class GDTKeyboardInputView;
@interface GDTViewController : UIViewController
@property(nonatomic, readonly, strong) GDTView *godotView;
@property(nonatomic, readonly, strong) GDTKeyboardInputView *keyboardView;
@end

View File

@@ -0,0 +1,321 @@
/**************************************************************************/
/* view_controller.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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"
#import "display_server_apple_embedded.h"
#import "godot_view_apple_embedded.h"
#import "godot_view_renderer.h"
#import "key_mapping_apple_embedded.h"
#import "keyboard_input_view.h"
#import "os_apple_embedded.h"
#include "core/config/project_settings.h"
#import <AVFoundation/AVFoundation.h>
#import <GameController/GameController.h>
@interface GDTViewController () <GDTViewDelegate>
@property(strong, nonatomic) GDTViewRenderer *renderer;
@property(strong, nonatomic) GDTKeyboardInputView *keyboardView;
@property(strong, nonatomic) UIView *godotLoadingOverlay;
@end
@implementation GDTViewController
- (GDTView *)godotView {
return (GDTView *)self.view;
}
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
[super pressesBegan:presses withEvent:event];
if (!DisplayServerAppleEmbedded::get_singleton() || DisplayServerAppleEmbedded::get_singleton()->is_keyboard_active()) {
return;
}
if (@available(iOS 13.4, *)) {
for (UIPress *press in presses) {
String u32lbl = String::utf8([press.key.charactersIgnoringModifiers UTF8String]);
String u32text = String::utf8([press.key.characters UTF8String]);
Key key = KeyMappingAppleEmbedded::remap_key(press.key.keyCode);
if (press.key.keyCode == 0 && u32text.is_empty() && u32lbl.is_empty()) {
continue;
}
char32_t us = 0;
if (!u32lbl.is_empty() && !u32lbl.begins_with("UIKey")) {
us = u32lbl[0];
}
KeyLocation location = KeyMappingAppleEmbedded::key_location(press.key.keyCode);
if (!u32text.is_empty() && !u32text.begins_with("UIKey")) {
for (int i = 0; i < u32text.length(); i++) {
const char32_t c = u32text[i];
DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
}
} else {
DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
}
}
}
}
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
[super pressesEnded:presses withEvent:event];
if (!DisplayServerAppleEmbedded::get_singleton() || DisplayServerAppleEmbedded::get_singleton()->is_keyboard_active()) {
return;
}
if (@available(iOS 13.4, *)) {
for (UIPress *press in presses) {
String u32lbl = String::utf8([press.key.charactersIgnoringModifiers UTF8String]);
Key key = KeyMappingAppleEmbedded::remap_key(press.key.keyCode);
if (press.key.keyCode == 0 && u32lbl.is_empty()) {
continue;
}
char32_t us = 0;
if (!u32lbl.is_empty() && !u32lbl.begins_with("UIKey")) {
us = u32lbl[0];
}
KeyLocation location = KeyMappingAppleEmbedded::key_location(press.key.keyCode);
DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location);
}
}
}
- (void)loadView {
GDTView *view = GDTViewCreate();
GDTViewRenderer *renderer = [[GDTViewRenderer 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];
print_verbose("Did receive memory warning!");
}
- (void)viewDidLoad {
[super viewDidLoad];
[self observeKeyboard];
[self displayLoadingOverlay];
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
- (void)observeKeyboard {
print_verbose("Setting up keyboard input view.");
self.keyboardView = [GDTKeyboardInputView new];
[self.view addSubview:self.keyboardView];
print_verbose("Adding observer for keyboard show/hide.");
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardOnScreen:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardHidden:)
name:UIKeyboardDidHideNotification
object:nil];
}
- (void)displayLoadingOverlay {
#if !defined(VISIONOS_ENABLED)
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];
#endif
}
- (BOOL)godotViewFinishedSetup:(GDTView *)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 {
if (GLOBAL_GET("display/window/ios/suppress_ui_gesture")) {
return UIRectEdgeAll;
} else {
return UIRectEdgeNone;
}
}
- (BOOL)shouldAutorotate {
if (!DisplayServerAppleEmbedded::get_singleton()) {
return NO;
}
switch (DisplayServerAppleEmbedded::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 (!DisplayServerAppleEmbedded::get_singleton()) {
return UIInterfaceOrientationMaskAll;
}
switch (DisplayServerAppleEmbedded::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
case DisplayServer::SCREEN_PORTRAIT:
return UIInterfaceOrientationMaskPortrait;
case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
return UIInterfaceOrientationMaskLandscapeLeft;
} else {
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:
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
return UIInterfaceOrientationMaskLandscapeRight;
} else {
return UIInterfaceOrientationMaskLandscapeLeft;
}
}
}
- (BOOL)prefersStatusBarHidden {
if (GLOBAL_GET("display/window/ios/hide_status_bar")) {
return YES;
} else {
return NO;
}
}
- (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 (DisplayServerAppleEmbedded::get_singleton()) {
DisplayServerAppleEmbedded::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
}
}
- (void)keyboardHidden:(NSNotification *)notification {
if (DisplayServerAppleEmbedded::get_singleton()) {
DisplayServerAppleEmbedded::get_singleton()->virtual_keyboard_set_height(0);
}
}
@end