#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_UIKIT
#include "../SDL_sysvideo.h"
#include "../SDL_pixels_c.h"
#include "../../events/SDL_events_c.h"
#include "SDL_uikitvideo.h"
#include "SDL_uikitevents.h"
#include "SDL_uikitmodes.h"
#include "SDL_uikitwindow.h"
#include "SDL_uikitappdelegate.h"
#include "SDL_uikitview.h"
#include "SDL_uikitopenglview.h"
#include <Foundation/Foundation.h>
@implementation SDL_UIKitWindowData
@synthesize uiwindow;
@synthesize viewcontroller;
@synthesize views;
- (instancetype)init
{
if ((self = [super init])) {
views = [NSMutableArray new];
}
return self;
}
@end
static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow *uiwindow, bool created)
{
SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
SDL_UIKitDisplayData *displaydata = (__bridge SDL_UIKitDisplayData *)display->internal;
SDL_uikitview *view;
#ifdef SDL_PLATFORM_VISIONOS
CGRect frame = UIKit_ComputeViewFrame(window);
#else
CGRect frame = UIKit_ComputeViewFrame(window, displaydata.uiscreen);
#endif
int width = (int)frame.size.width;
int height = (int)frame.size.height;
SDL_UIKitWindowData *data = [[SDL_UIKitWindowData alloc] init];
if (!data) {
return SDL_OutOfMemory();
}
window->internal = (SDL_WindowData *)CFBridgingRetain(data);
data.uiwindow = uiwindow;
#ifndef SDL_PLATFORM_VISIONOS
if (displaydata.uiscreen != [UIScreen mainScreen]) {
window->flags &= ~SDL_WINDOW_RESIZABLE; window->flags &= ~SDL_WINDOW_INPUT_FOCUS; window->flags |= SDL_WINDOW_BORDERLESS; }
#endif
#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
if (displaydata.uiscreen == [UIScreen mainScreen]) {
NSUInteger orients = UIKit_GetSupportedOrientations(window);
BOOL supportsLandscape = (orients & UIInterfaceOrientationMaskLandscape) != 0;
BOOL supportsPortrait = (orients & (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown)) != 0;
if ((width > height && !supportsLandscape) || (height > width && !supportsPortrait)) {
int temp = width;
width = height;
height = temp;
}
}
#endif
#if 0#endif
window->w = width;
window->h = height;
data.viewcontroller = [[SDL_uikitviewcontroller alloc] initWithSDLWindow:window];
view = [[SDL_uikitview alloc] initWithFrame:frame];
[view setSDLWindow:window];
SDL_PropertiesID props = SDL_GetWindowProperties(window);
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, (__bridge void *)data.uiwindow);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER, SDL_METALVIEW_TAG);
return true;
}
bool UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
{
@autoreleasepool {
SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal;
SDL_Window *other;
for (other = _this->windows; other; other = other->next) {
if (other != window && SDL_GetVideoDisplayForWindow(other) == display) {
return SDL_SetError("Only one window allowed per display.");
}
}
#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
const CGSize origsize = data.uiscreen.currentMode.size;
if ((origsize.width == 0.0f) && (origsize.height == 0.0f)) {
SDL_DisplayMode bestmode;
bool include_high_density_modes = false;
if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
include_high_density_modes = true;
}
if (SDL_GetClosestFullscreenDisplayMode(display->id, window->w, window->h, 0.0f, include_high_density_modes, &bestmode)) {
SDL_UIKitDisplayModeData *modedata = (__bridge SDL_UIKitDisplayModeData *)bestmode.internal;
[data.uiscreen setCurrentMode:modedata.uiscreenmode];
SDL_SetCurrentDisplayMode(display, &bestmode);
}
}
if (data.uiscreen == [UIScreen mainScreen]) {
if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) {
[UIApplication sharedApplication].statusBarHidden = YES;
} else {
[UIApplication sharedApplication].statusBarHidden = NO;
}
}
#endif
UIWindow *uiwindow = nil;
if (@available(iOS 13.0, tvOS 13.0, *)) {
UIWindowScene *scene = (__bridge UIWindowScene *)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_WINDOWSCENE_POINTER, NULL);
if (!scene) {
scene = UIKit_GetActiveWindowScene();
}
if (scene) {
uiwindow = [[UIWindow alloc] initWithWindowScene:scene];
}
}
if (!uiwindow) {
#ifdef SDL_PLATFORM_VISIONOS
uiwindow = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, SDL_XR_SCREENWIDTH, SDL_XR_SCREENHEIGHT)];
#else
uiwindow = [[UIWindow alloc] initWithFrame:data.uiscreen.bounds];
#endif
}
#ifndef SDL_PLATFORM_VISIONOS
if (data.uiscreen != [UIScreen mainScreen]) {
[uiwindow setScreen:data.uiscreen];
}
#endif
if (!SetupWindowData(_this, window, uiwindow, true)) {
return false;
}
#ifdef SDL_PLATFORM_VISIONOS
SDL_SetWindowSize(window, window->w, window->h);
#endif
}
return true;
}
void UIKit_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
data.viewcontroller.title = @(window->title);
}
}
void UIKit_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window)
{
#ifdef SDL_PLATFORM_VISIONOS
@autoreleasepool {
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
UIWindowScene *scene = data.uiwindow.windowScene;
CGSize size = { window->pending.w, window->pending.h };
UIWindowSceneGeometryPreferences *preferences = [[UIWindowSceneGeometryPreferencesVision alloc] initWithSize:size];
[scene requestGeometryUpdateWithPreferences:preferences errorHandler:^(NSError * _Nonnull error) {
}];
}
#endif
}
void UIKit_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
[data.uiwindow makeKeyAndVisible];
SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
SDL_UIKitDisplayData *displaydata = (__bridge SDL_UIKitDisplayData *)display->internal;
#ifndef SDL_PLATFORM_VISIONOS
if (displaydata.uiscreen == [UIScreen mainScreen])
#endif
{
SDL_SetMouseFocus(window);
SDL_SetKeyboardFocus(window);
}
}
}
void UIKit_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
data.uiwindow.hidden = YES;
}
}
void UIKit_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
_this->GL_MakeCurrent(_this, _this->current_glwin, _this->current_glctx);
#endif
}
static void UIKit_UpdateWindowBorder(SDL_VideoDevice *_this, SDL_Window *window)
{
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
SDL_uikitviewcontroller *viewcontroller = data.viewcontroller;
#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
if (data.uiwindow.screen == [UIScreen mainScreen]) {
if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) {
[UIApplication sharedApplication].statusBarHidden = YES;
} else {
[UIApplication sharedApplication].statusBarHidden = NO;
}
[viewcontroller setNeedsStatusBarAppearanceUpdate];
}
viewcontroller.view.frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen);
#endif
#ifdef SDL_IPHONE_KEYBOARD
[viewcontroller updateKeyboard];
#endif
[viewcontroller.view setNeedsLayout];
[viewcontroller.view layoutIfNeeded];
}
void UIKit_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered)
{
@autoreleasepool {
if (bordered) {
window->flags &= ~SDL_WINDOW_BORDERLESS;
} else {
window->flags |= SDL_WINDOW_BORDERLESS;
}
UIKit_UpdateWindowBorder(_this, window);
}
}
SDL_FullscreenResult UIKit_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen)
{
@autoreleasepool {
SDL_SendWindowEvent(window, fullscreen ? SDL_EVENT_WINDOW_ENTER_FULLSCREEN : SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0);
UIKit_UpdateWindowBorder(_this, window);
}
return SDL_FULLSCREEN_SUCCEEDED;
}
void UIKit_UpdatePointerLock(SDL_VideoDevice *_this, SDL_Window *window)
{
#ifndef SDL_PLATFORM_TVOS
@autoreleasepool {
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
SDL_uikitviewcontroller *viewcontroller = data.viewcontroller;
if (@available(iOS 14.0, *)) {
[viewcontroller setNeedsUpdateOfPrefersPointerLocked];
}
}
#endif }
void UIKit_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
if (window->internal != NULL) {
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
NSArray *views = nil;
[data.viewcontroller stopAnimation];
views = [data.views copy];
for (SDL_uikitview *view in views) {
[view setSDLWindow:NULL];
}
data.uiwindow.rootViewController = nil;
data.uiwindow.hidden = YES;
CFRelease(window->internal);
window->internal = NULL;
}
}
}
void UIKit_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h)
{
@autoreleasepool {
SDL_UIKitWindowData *windata = (__bridge SDL_UIKitWindowData *)window->internal;
UIView *view = windata.viewcontroller.view;
CGSize size = view.bounds.size;
CGFloat scale = 1.0;
if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
#ifndef SDL_PLATFORM_VISIONOS
scale = windata.uiwindow.screen.nativeScale;
#else
scale = 2.0;
#endif
}
*w = (int)(size.width * scale);
*h = (int)(size.height * scale);
}
}
#ifndef SDL_PLATFORM_TVOS
NSUInteger
UIKit_GetSupportedOrientations(SDL_Window *window)
{
const char *hint = SDL_GetHint(SDL_HINT_ORIENTATIONS);
NSUInteger validOrientations = UIInterfaceOrientationMaskAll;
NSUInteger orientationMask = 0;
@autoreleasepool {
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
UIApplication *app = [UIApplication sharedApplication];
if ([app.delegate respondsToSelector:@selector(application:supportedInterfaceOrientationsForWindow:)]) {
validOrientations = [app.delegate application:app supportedInterfaceOrientationsForWindow:data.uiwindow];
} else {
validOrientations = [app supportedInterfaceOrientationsForWindow:data.uiwindow];
}
if (hint != NULL) {
NSArray *orientations = [@(hint) componentsSeparatedByString:@" "];
if ([orientations containsObject:@"LandscapeLeft"]) {
orientationMask |= UIInterfaceOrientationMaskLandscapeLeft;
}
if ([orientations containsObject:@"LandscapeRight"]) {
orientationMask |= UIInterfaceOrientationMaskLandscapeRight;
}
if ([orientations containsObject:@"Portrait"]) {
orientationMask |= UIInterfaceOrientationMaskPortrait;
}
if ([orientations containsObject:@"PortraitUpsideDown"]) {
orientationMask |= UIInterfaceOrientationMaskPortraitUpsideDown;
}
}
if (orientationMask == 0 && (window->flags & SDL_WINDOW_RESIZABLE)) {
orientationMask = UIInterfaceOrientationMaskAll;
}
if (orientationMask == 0) {
if (window->floating.w >= window->floating.h) {
orientationMask |= UIInterfaceOrientationMaskLandscape;
}
if (window->floating.h >= window->floating.w) {
orientationMask |= (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
}
}
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
orientationMask &= ~UIInterfaceOrientationMaskPortraitUpsideDown;
}
if ((validOrientations & orientationMask) == 0) {
orientationMask = validOrientations;
}
}
return orientationMask;
}
#endif
bool SDL_SetiOSAnimationCallback(SDL_Window *window, int interval, SDL_iOSAnimationCallback callback, void *callbackParam)
{
if (!window || !window->internal) {
return SDL_SetError("Invalid window");
}
@autoreleasepool {
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
[data.viewcontroller setAnimationCallback:interval
callback:callback
callbackParam:callbackParam];
}
return true;
}
#endif