diff options
| author | 3gg <3gg@shellblade.net> | 2025-12-27 12:03:39 -0800 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2025-12-27 12:03:39 -0800 |
| commit | 5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch) | |
| tree | 8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/video/uikit | |
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/uikit')
28 files changed, 5821 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitappdelegate.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitappdelegate.h new file mode 100644 index 0000000..77ccbfd --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitappdelegate.h | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | #import <UIKit/UIKit.h> | ||
| 23 | |||
| 24 | @interface SDLLaunchScreenController : UIViewController | ||
| 25 | |||
| 26 | - (instancetype)init; | ||
| 27 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil; | ||
| 28 | - (void)loadView; | ||
| 29 | |||
| 30 | @end | ||
| 31 | |||
| 32 | @interface SDLUIKitDelegate : NSObject <UIApplicationDelegate> | ||
| 33 | |||
| 34 | + (id)sharedAppDelegate; | ||
| 35 | + (NSString *)getAppDelegateClassName; | ||
| 36 | |||
| 37 | - (void)hideLaunchScreen; | ||
| 38 | |||
| 39 | /* This property is marked as optional, and is only intended to be used when | ||
| 40 | * the app's UI is storyboard-based. SDL is not storyboard-based, however | ||
| 41 | * several major third-party ad APIs (e.g. Google admob) incorrectly assume this | ||
| 42 | * property always exists, and will crash if it doesn't. */ | ||
| 43 | @property(nonatomic) UIWindow *window; | ||
| 44 | |||
| 45 | @end | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitappdelegate.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitappdelegate.m new file mode 100644 index 0000000..6a37e51 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitappdelegate.m | |||
| @@ -0,0 +1,519 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_VIDEO_DRIVER_UIKIT | ||
| 24 | |||
| 25 | #include "../SDL_sysvideo.h" | ||
| 26 | |||
| 27 | #import "SDL_uikitappdelegate.h" | ||
| 28 | #import "SDL_uikitmodes.h" | ||
| 29 | #import "SDL_uikitwindow.h" | ||
| 30 | |||
| 31 | #include "../../events/SDL_events_c.h" | ||
| 32 | |||
| 33 | #ifdef main | ||
| 34 | #undef main | ||
| 35 | #endif | ||
| 36 | |||
| 37 | static SDL_main_func forward_main; | ||
| 38 | static int forward_argc; | ||
| 39 | static char **forward_argv; | ||
| 40 | static int exit_status; | ||
| 41 | |||
| 42 | int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) | ||
| 43 | { | ||
| 44 | int i; | ||
| 45 | |||
| 46 | // store arguments | ||
| 47 | /* Note that we need to be careful about how we allocate/free memory here. | ||
| 48 | * If the application calls SDL_SetMemoryFunctions(), we can't rely on | ||
| 49 | * SDL_free() to use the same allocator after SDL_main() returns. | ||
| 50 | */ | ||
| 51 | forward_main = mainFunction; | ||
| 52 | forward_argc = argc; | ||
| 53 | forward_argv = (char **)malloc((argc + 1) * sizeof(char *)); // This should NOT be SDL_malloc() | ||
| 54 | for (i = 0; i < argc; i++) { | ||
| 55 | forward_argv[i] = malloc((strlen(argv[i]) + 1) * sizeof(char)); // This should NOT be SDL_malloc() | ||
| 56 | strcpy(forward_argv[i], argv[i]); | ||
| 57 | } | ||
| 58 | forward_argv[i] = NULL; | ||
| 59 | |||
| 60 | // Give over control to run loop, SDLUIKitDelegate will handle most things from here | ||
| 61 | @autoreleasepool { | ||
| 62 | UIApplicationMain(argc, argv, nil, [SDLUIKitDelegate getAppDelegateClassName]); | ||
| 63 | } | ||
| 64 | |||
| 65 | // free the memory we used to hold copies of argc and argv | ||
| 66 | for (i = 0; i < forward_argc; i++) { | ||
| 67 | free(forward_argv[i]); // This should NOT be SDL_free() | ||
| 68 | } | ||
| 69 | free(forward_argv); // This should NOT be SDL_free() | ||
| 70 | |||
| 71 | return exit_status; | ||
| 72 | } | ||
| 73 | |||
| 74 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 75 | // Load a launch image using the old UILaunchImageFile-era naming rules. | ||
| 76 | static UIImage *SDL_LoadLaunchImageNamed(NSString *name, int screenh) | ||
| 77 | { | ||
| 78 | UIInterfaceOrientation curorient = [UIApplication sharedApplication].statusBarOrientation; | ||
| 79 | UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom; | ||
| 80 | UIImage *image = nil; | ||
| 81 | |||
| 82 | if (idiom == UIUserInterfaceIdiomPhone && screenh == 568) { | ||
| 83 | // The image name for the iPhone 5 uses its height as a suffix. | ||
| 84 | image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-568h", name]]; | ||
| 85 | } else if (idiom == UIUserInterfaceIdiomPad) { | ||
| 86 | // iPad apps can launch in any orientation. | ||
| 87 | if (UIInterfaceOrientationIsLandscape(curorient)) { | ||
| 88 | if (curorient == UIInterfaceOrientationLandscapeLeft) { | ||
| 89 | image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-LandscapeLeft", name]]; | ||
| 90 | } else { | ||
| 91 | image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-LandscapeRight", name]]; | ||
| 92 | } | ||
| 93 | if (!image) { | ||
| 94 | image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-Landscape", name]]; | ||
| 95 | } | ||
| 96 | } else { | ||
| 97 | if (curorient == UIInterfaceOrientationPortraitUpsideDown) { | ||
| 98 | image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-PortraitUpsideDown", name]]; | ||
| 99 | } | ||
| 100 | if (!image) { | ||
| 101 | image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-Portrait", name]]; | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | if (!image) { | ||
| 107 | image = [UIImage imageNamed:name]; | ||
| 108 | } | ||
| 109 | |||
| 110 | return image; | ||
| 111 | } | ||
| 112 | |||
| 113 | @interface SDLLaunchStoryboardViewController : UIViewController | ||
| 114 | @property(nonatomic, strong) UIViewController *storyboardViewController; | ||
| 115 | - (instancetype)initWithStoryboardViewController:(UIViewController *)storyboardViewController; | ||
| 116 | @end | ||
| 117 | |||
| 118 | @implementation SDLLaunchStoryboardViewController | ||
| 119 | |||
| 120 | - (instancetype)initWithStoryboardViewController:(UIViewController *)storyboardViewController | ||
| 121 | { | ||
| 122 | self = [super init]; | ||
| 123 | self.storyboardViewController = storyboardViewController; | ||
| 124 | return self; | ||
| 125 | } | ||
| 126 | |||
| 127 | - (void)viewDidLoad | ||
| 128 | { | ||
| 129 | [super viewDidLoad]; | ||
| 130 | |||
| 131 | [self addChildViewController:self.storyboardViewController]; | ||
| 132 | [self.view addSubview:self.storyboardViewController.view]; | ||
| 133 | self.storyboardViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | ||
| 134 | self.storyboardViewController.view.frame = self.view.bounds; | ||
| 135 | [self.storyboardViewController didMoveToParentViewController:self]; | ||
| 136 | |||
| 137 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 138 | UIApplication.sharedApplication.statusBarHidden = self.prefersStatusBarHidden; | ||
| 139 | UIApplication.sharedApplication.statusBarStyle = self.preferredStatusBarStyle; | ||
| 140 | #endif | ||
| 141 | } | ||
| 142 | |||
| 143 | - (BOOL)prefersStatusBarHidden | ||
| 144 | { | ||
| 145 | return [[NSBundle.mainBundle objectForInfoDictionaryKey:@"UIStatusBarHidden"] boolValue]; | ||
| 146 | } | ||
| 147 | |||
| 148 | - (UIStatusBarStyle)preferredStatusBarStyle | ||
| 149 | { | ||
| 150 | NSString *statusBarStyle = [NSBundle.mainBundle objectForInfoDictionaryKey:@"UIStatusBarStyle"]; | ||
| 151 | if ([statusBarStyle isEqualToString:@"UIStatusBarStyleLightContent"]) { | ||
| 152 | return UIStatusBarStyleLightContent; | ||
| 153 | } | ||
| 154 | if (@available(iOS 13.0, *)) { | ||
| 155 | if ([statusBarStyle isEqualToString:@"UIStatusBarStyleDarkContent"]) { | ||
| 156 | return UIStatusBarStyleDarkContent; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | return UIStatusBarStyleDefault; | ||
| 160 | } | ||
| 161 | |||
| 162 | @end | ||
| 163 | #endif // !SDL_PLATFORM_TVOS | ||
| 164 | |||
| 165 | @interface SDLLaunchScreenController () | ||
| 166 | |||
| 167 | #ifndef SDL_PLATFORM_TVOS | ||
| 168 | - (NSUInteger)supportedInterfaceOrientations; | ||
| 169 | #endif | ||
| 170 | |||
| 171 | @end | ||
| 172 | |||
| 173 | @implementation SDLLaunchScreenController | ||
| 174 | |||
| 175 | - (instancetype)init | ||
| 176 | { | ||
| 177 | return [self initWithNibName:nil bundle:[NSBundle mainBundle]]; | ||
| 178 | } | ||
| 179 | |||
| 180 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil | ||
| 181 | { | ||
| 182 | if (!(self = [super initWithNibName:nil bundle:nil])) { | ||
| 183 | return nil; | ||
| 184 | } | ||
| 185 | |||
| 186 | NSString *screenname = nibNameOrNil; | ||
| 187 | NSBundle *bundle = nibBundleOrNil; | ||
| 188 | |||
| 189 | // A launch screen may not exist. Fall back to launch images in that case. | ||
| 190 | if (screenname) { | ||
| 191 | @try { | ||
| 192 | self.view = [bundle loadNibNamed:screenname owner:self options:nil][0]; | ||
| 193 | } | ||
| 194 | @catch (NSException *exception) { | ||
| 195 | /* If a launch screen name is specified but it fails to load, iOS | ||
| 196 | * displays a blank screen rather than falling back to an image. */ | ||
| 197 | return nil; | ||
| 198 | } | ||
| 199 | } | ||
| 200 | |||
| 201 | if (!self.view) { | ||
| 202 | NSArray *launchimages = [bundle objectForInfoDictionaryKey:@"UILaunchImages"]; | ||
| 203 | NSString *imagename = nil; | ||
| 204 | UIImage *image = nil; | ||
| 205 | |||
| 206 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 207 | int screenw = SDL_XR_SCREENWIDTH; | ||
| 208 | int screenh = SDL_XR_SCREENHEIGHT; | ||
| 209 | #else | ||
| 210 | int screenw = (int)([UIScreen mainScreen].bounds.size.width + 0.5); | ||
| 211 | int screenh = (int)([UIScreen mainScreen].bounds.size.height + 0.5); | ||
| 212 | #endif | ||
| 213 | |||
| 214 | |||
| 215 | |||
| 216 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 217 | UIInterfaceOrientation curorient = [UIApplication sharedApplication].statusBarOrientation; | ||
| 218 | |||
| 219 | // We always want portrait-oriented size, to match UILaunchImageSize. | ||
| 220 | if (screenw > screenh) { | ||
| 221 | int width = screenw; | ||
| 222 | screenw = screenh; | ||
| 223 | screenh = width; | ||
| 224 | } | ||
| 225 | #endif | ||
| 226 | |||
| 227 | // Xcode 5 introduced a dictionary of launch images in Info.plist. | ||
| 228 | if (launchimages) { | ||
| 229 | for (NSDictionary *dict in launchimages) { | ||
| 230 | NSString *minversion = dict[@"UILaunchImageMinimumOSVersion"]; | ||
| 231 | NSString *sizestring = dict[@"UILaunchImageSize"]; | ||
| 232 | |||
| 233 | // Ignore this image if the current version is too low. | ||
| 234 | if (minversion && !UIKit_IsSystemVersionAtLeast(minversion.doubleValue)) { | ||
| 235 | continue; | ||
| 236 | } | ||
| 237 | |||
| 238 | // Ignore this image if the size doesn't match. | ||
| 239 | if (sizestring) { | ||
| 240 | CGSize size = CGSizeFromString(sizestring); | ||
| 241 | if ((int)(size.width + 0.5) != screenw || (int)(size.height + 0.5) != screenh) { | ||
| 242 | continue; | ||
| 243 | } | ||
| 244 | } | ||
| 245 | |||
| 246 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 247 | UIInterfaceOrientationMask orientmask = UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; | ||
| 248 | NSString *orientstring = dict[@"UILaunchImageOrientation"]; | ||
| 249 | |||
| 250 | if (orientstring) { | ||
| 251 | if ([orientstring isEqualToString:@"PortraitUpsideDown"]) { | ||
| 252 | orientmask = UIInterfaceOrientationMaskPortraitUpsideDown; | ||
| 253 | } else if ([orientstring isEqualToString:@"Landscape"]) { | ||
| 254 | orientmask = UIInterfaceOrientationMaskLandscape; | ||
| 255 | } else if ([orientstring isEqualToString:@"LandscapeLeft"]) { | ||
| 256 | orientmask = UIInterfaceOrientationMaskLandscapeLeft; | ||
| 257 | } else if ([orientstring isEqualToString:@"LandscapeRight"]) { | ||
| 258 | orientmask = UIInterfaceOrientationMaskLandscapeRight; | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | // Ignore this image if the orientation doesn't match. | ||
| 263 | if ((orientmask & (1 << curorient)) == 0) { | ||
| 264 | continue; | ||
| 265 | } | ||
| 266 | #endif | ||
| 267 | |||
| 268 | imagename = dict[@"UILaunchImageName"]; | ||
| 269 | } | ||
| 270 | |||
| 271 | if (imagename) { | ||
| 272 | image = [UIImage imageNamed:imagename]; | ||
| 273 | } | ||
| 274 | } | ||
| 275 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 276 | else { | ||
| 277 | imagename = [bundle objectForInfoDictionaryKey:@"UILaunchImageFile"]; | ||
| 278 | |||
| 279 | if (imagename) { | ||
| 280 | image = SDL_LoadLaunchImageNamed(imagename, screenh); | ||
| 281 | } | ||
| 282 | |||
| 283 | if (!image) { | ||
| 284 | image = SDL_LoadLaunchImageNamed(@"Default", screenh); | ||
| 285 | } | ||
| 286 | } | ||
| 287 | #endif | ||
| 288 | |||
| 289 | if (image) { | ||
| 290 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 291 | CGRect viewFrame = CGRectMake(0, 0, screenw, screenh); | ||
| 292 | #else | ||
| 293 | CGRect viewFrame = [UIScreen mainScreen].bounds; | ||
| 294 | #endif | ||
| 295 | UIImageView *view = [[UIImageView alloc] initWithFrame:viewFrame]; | ||
| 296 | UIImageOrientation imageorient = UIImageOrientationUp; | ||
| 297 | |||
| 298 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 299 | // Bugs observed / workaround tested in iOS 8.3. | ||
| 300 | if (UIInterfaceOrientationIsLandscape(curorient)) { | ||
| 301 | if (image.size.width < image.size.height) { | ||
| 302 | /* On iOS 8, portrait launch images displayed in forced- | ||
| 303 | * landscape mode (e.g. a standard Default.png on an iPhone | ||
| 304 | * when Info.plist only supports landscape orientations) need | ||
| 305 | * to be rotated to display in the expected orientation. */ | ||
| 306 | if (curorient == UIInterfaceOrientationLandscapeLeft) { | ||
| 307 | imageorient = UIImageOrientationRight; | ||
| 308 | } else if (curorient == UIInterfaceOrientationLandscapeRight) { | ||
| 309 | imageorient = UIImageOrientationLeft; | ||
| 310 | } | ||
| 311 | } | ||
| 312 | } | ||
| 313 | #endif | ||
| 314 | |||
| 315 | // Create the properly oriented image. | ||
| 316 | view.image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:imageorient]; | ||
| 317 | |||
| 318 | self.view = view; | ||
| 319 | } | ||
| 320 | } | ||
| 321 | |||
| 322 | return self; | ||
| 323 | } | ||
| 324 | |||
| 325 | - (void)loadView | ||
| 326 | { | ||
| 327 | // Do nothing. | ||
| 328 | } | ||
| 329 | |||
| 330 | #ifndef SDL_PLATFORM_TVOS | ||
| 331 | - (BOOL)shouldAutorotate | ||
| 332 | { | ||
| 333 | // If YES, the launch image will be incorrectly rotated in some cases. | ||
| 334 | return NO; | ||
| 335 | } | ||
| 336 | |||
| 337 | - (NSUInteger)supportedInterfaceOrientations | ||
| 338 | { | ||
| 339 | /* We keep the supported orientations unrestricted to avoid the case where | ||
| 340 | * there are no common orientations between the ones set in Info.plist and | ||
| 341 | * the ones set here (it will cause an exception in that case.) */ | ||
| 342 | return UIInterfaceOrientationMaskAll; | ||
| 343 | } | ||
| 344 | #endif // !SDL_PLATFORM_TVOS | ||
| 345 | |||
| 346 | @end | ||
| 347 | |||
| 348 | @implementation SDLUIKitDelegate | ||
| 349 | { | ||
| 350 | UIWindow *launchWindow; | ||
| 351 | } | ||
| 352 | |||
| 353 | // convenience method | ||
| 354 | + (id)sharedAppDelegate | ||
| 355 | { | ||
| 356 | /* the delegate is set in UIApplicationMain(), which is guaranteed to be | ||
| 357 | * called before this method */ | ||
| 358 | return [UIApplication sharedApplication].delegate; | ||
| 359 | } | ||
| 360 | |||
| 361 | + (NSString *)getAppDelegateClassName | ||
| 362 | { | ||
| 363 | /* subclassing notice: when you subclass this appdelegate, make sure to add | ||
| 364 | * a category to override this method and return the actual name of the | ||
| 365 | * delegate */ | ||
| 366 | return @"SDLUIKitDelegate"; | ||
| 367 | } | ||
| 368 | |||
| 369 | - (void)hideLaunchScreen | ||
| 370 | { | ||
| 371 | UIWindow *window = launchWindow; | ||
| 372 | |||
| 373 | if (!window || window.hidden) { | ||
| 374 | return; | ||
| 375 | } | ||
| 376 | |||
| 377 | launchWindow = nil; | ||
| 378 | |||
| 379 | // Do a nice animated fade-out (roughly matches the real launch behavior.) | ||
| 380 | [UIView animateWithDuration:0.2 | ||
| 381 | animations:^{ | ||
| 382 | window.alpha = 0.0; | ||
| 383 | } | ||
| 384 | completion:^(BOOL finished) { | ||
| 385 | window.hidden = YES; | ||
| 386 | UIKit_ForceUpdateHomeIndicator(); // Wait for launch screen to hide so settings are applied to the actual view controller. | ||
| 387 | }]; | ||
| 388 | } | ||
| 389 | |||
| 390 | - (void)postFinishLaunch | ||
| 391 | { | ||
| 392 | /* Hide the launch screen the next time the run loop is run. SDL apps will | ||
| 393 | * have a chance to load resources while the launch screen is still up. */ | ||
| 394 | [self performSelector:@selector(hideLaunchScreen) withObject:nil afterDelay:0.0]; | ||
| 395 | |||
| 396 | // run the user's application, passing argc and argv | ||
| 397 | SDL_SetiOSEventPump(true); | ||
| 398 | exit_status = forward_main(forward_argc, forward_argv); | ||
| 399 | SDL_SetiOSEventPump(false); | ||
| 400 | |||
| 401 | if (launchWindow) { | ||
| 402 | launchWindow.hidden = YES; | ||
| 403 | launchWindow = nil; | ||
| 404 | } | ||
| 405 | |||
| 406 | // exit, passing the return status from the user's application | ||
| 407 | /* We don't actually exit to support applications that do setup in their | ||
| 408 | * main function and then allow the Cocoa event loop to run. */ | ||
| 409 | // exit(exit_status); | ||
| 410 | } | ||
| 411 | |||
| 412 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions | ||
| 413 | { | ||
| 414 | NSBundle *bundle = [NSBundle mainBundle]; | ||
| 415 | |||
| 416 | #ifdef SDL_IPHONE_LAUNCHSCREEN | ||
| 417 | /* The normal launch screen is displayed until didFinishLaunching returns, | ||
| 418 | * but SDL_main is called after that happens and there may be a noticeable | ||
| 419 | * delay between the start of SDL_main and when the first real frame is | ||
| 420 | * displayed (e.g. if resources are loaded before SDL_GL_SwapWindow is | ||
| 421 | * called), so we show the launch screen programmatically until the first | ||
| 422 | * time events are pumped. */ | ||
| 423 | UIViewController *vc = nil; | ||
| 424 | NSString *screenname = nil; | ||
| 425 | |||
| 426 | // tvOS only uses a plain launch image. | ||
| 427 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 428 | screenname = [bundle objectForInfoDictionaryKey:@"UILaunchStoryboardName"]; | ||
| 429 | |||
| 430 | if (screenname) { | ||
| 431 | @try { | ||
| 432 | /* The launch storyboard is actually a nib in some older versions of | ||
| 433 | * Xcode. We'll try to load it as a storyboard first, as it's more | ||
| 434 | * modern. */ | ||
| 435 | UIStoryboard *storyboard = [UIStoryboard storyboardWithName:screenname bundle:bundle]; | ||
| 436 | __auto_type storyboardVc = [storyboard instantiateInitialViewController]; | ||
| 437 | vc = [[SDLLaunchStoryboardViewController alloc] initWithStoryboardViewController:storyboardVc]; | ||
| 438 | } | ||
| 439 | @catch (NSException *exception) { | ||
| 440 | // Do nothing (there's more code to execute below). | ||
| 441 | } | ||
| 442 | } | ||
| 443 | #endif | ||
| 444 | |||
| 445 | if (vc == nil) { | ||
| 446 | vc = [[SDLLaunchScreenController alloc] initWithNibName:screenname bundle:bundle]; | ||
| 447 | } | ||
| 448 | |||
| 449 | if (vc.view) { | ||
| 450 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 451 | CGRect viewFrame = CGRectMake(0, 0, SDL_XR_SCREENWIDTH, SDL_XR_SCREENHEIGHT); | ||
| 452 | #else | ||
| 453 | CGRect viewFrame = [UIScreen mainScreen].bounds; | ||
| 454 | #endif | ||
| 455 | launchWindow = [[UIWindow alloc] initWithFrame:viewFrame]; | ||
| 456 | |||
| 457 | /* We don't want the launch window immediately hidden when a real SDL | ||
| 458 | * window is shown - we fade it out ourselves when we're ready. */ | ||
| 459 | launchWindow.windowLevel = UIWindowLevelNormal + 1.0; | ||
| 460 | |||
| 461 | /* Show the window but don't make it key. Events should always go to | ||
| 462 | * other windows when possible. */ | ||
| 463 | launchWindow.hidden = NO; | ||
| 464 | |||
| 465 | launchWindow.rootViewController = vc; | ||
| 466 | } | ||
| 467 | #endif | ||
| 468 | |||
| 469 | // Set working directory to resource path | ||
| 470 | [[NSFileManager defaultManager] changeCurrentDirectoryPath:[bundle resourcePath]]; | ||
| 471 | |||
| 472 | SDL_SetMainReady(); | ||
| 473 | [self performSelector:@selector(postFinishLaunch) withObject:nil afterDelay:0.0]; | ||
| 474 | |||
| 475 | return YES; | ||
| 476 | } | ||
| 477 | |||
| 478 | - (UIWindow *)window | ||
| 479 | { | ||
| 480 | SDL_VideoDevice *_this = SDL_GetVideoDevice(); | ||
| 481 | if (_this) { | ||
| 482 | SDL_Window *window = NULL; | ||
| 483 | for (window = _this->windows; window != NULL; window = window->next) { | ||
| 484 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 485 | if (data != nil) { | ||
| 486 | return data.uiwindow; | ||
| 487 | } | ||
| 488 | } | ||
| 489 | } | ||
| 490 | return nil; | ||
| 491 | } | ||
| 492 | |||
| 493 | - (void)setWindow:(UIWindow *)window | ||
| 494 | { | ||
| 495 | // Do nothing. | ||
| 496 | } | ||
| 497 | |||
| 498 | - (void)sendDropFileForURL:(NSURL *)url fromSourceApplication:(NSString *)sourceApplication | ||
| 499 | { | ||
| 500 | NSURL *fileURL = url.filePathURL; | ||
| 501 | const char *sourceApplicationCString = sourceApplication ? [sourceApplication UTF8String] : NULL; | ||
| 502 | if (fileURL != nil) { | ||
| 503 | SDL_SendDropFile(NULL, sourceApplicationCString, fileURL.path.UTF8String); | ||
| 504 | } else { | ||
| 505 | SDL_SendDropFile(NULL, sourceApplicationCString, url.absoluteString.UTF8String); | ||
| 506 | } | ||
| 507 | SDL_SendDropComplete(NULL); | ||
| 508 | } | ||
| 509 | |||
| 510 | - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options | ||
| 511 | { | ||
| 512 | // TODO: Handle options | ||
| 513 | [self sendDropFileForURL:url fromSourceApplication:NULL]; | ||
| 514 | return YES; | ||
| 515 | } | ||
| 516 | |||
| 517 | @end | ||
| 518 | |||
| 519 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.h new file mode 100644 index 0000000..1c538c2 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.h | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #ifndef SDL_uikitclipboard_h_ | ||
| 22 | #define SDL_uikitclipboard_h_ | ||
| 23 | |||
| 24 | #include "../SDL_sysvideo.h" | ||
| 25 | |||
| 26 | extern bool UIKit_SetClipboardText(SDL_VideoDevice *_this, const char *text); | ||
| 27 | extern char *UIKit_GetClipboardText(SDL_VideoDevice *_this); | ||
| 28 | extern bool UIKit_HasClipboardText(SDL_VideoDevice *_this); | ||
| 29 | |||
| 30 | extern void UIKit_InitClipboard(SDL_VideoDevice *_this); | ||
| 31 | extern void UIKit_QuitClipboard(SDL_VideoDevice *_this); | ||
| 32 | |||
| 33 | #endif // SDL_uikitclipboard_h_ | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.m new file mode 100644 index 0000000..8ed4eb0 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.m | |||
| @@ -0,0 +1,105 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_VIDEO_DRIVER_UIKIT | ||
| 24 | |||
| 25 | #include "SDL_uikitvideo.h" | ||
| 26 | #include "../../events/SDL_clipboardevents_c.h" | ||
| 27 | |||
| 28 | #import <UIKit/UIPasteboard.h> | ||
| 29 | |||
| 30 | bool UIKit_SetClipboardText(SDL_VideoDevice *_this, const char *text) | ||
| 31 | { | ||
| 32 | #ifdef SDL_PLATFORM_TVOS | ||
| 33 | return SDL_SetError("The clipboard is not available on tvOS"); | ||
| 34 | #else | ||
| 35 | @autoreleasepool { | ||
| 36 | [UIPasteboard generalPasteboard].string = @(text); | ||
| 37 | return true; | ||
| 38 | } | ||
| 39 | #endif | ||
| 40 | } | ||
| 41 | |||
| 42 | char *UIKit_GetClipboardText(SDL_VideoDevice *_this) | ||
| 43 | { | ||
| 44 | #ifdef SDL_PLATFORM_TVOS | ||
| 45 | return SDL_strdup(""); // Unsupported. | ||
| 46 | #else | ||
| 47 | @autoreleasepool { | ||
| 48 | UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; | ||
| 49 | NSString *string = pasteboard.string; | ||
| 50 | |||
| 51 | if (string != nil) { | ||
| 52 | return SDL_strdup(string.UTF8String); | ||
| 53 | } else { | ||
| 54 | return SDL_strdup(""); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | #endif | ||
| 58 | } | ||
| 59 | |||
| 60 | bool UIKit_HasClipboardText(SDL_VideoDevice *_this) | ||
| 61 | { | ||
| 62 | @autoreleasepool { | ||
| 63 | #ifndef SDL_PLATFORM_TVOS | ||
| 64 | if ([UIPasteboard generalPasteboard].string != nil) { | ||
| 65 | return true; | ||
| 66 | } | ||
| 67 | #endif | ||
| 68 | return false; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | void UIKit_InitClipboard(SDL_VideoDevice *_this) | ||
| 73 | { | ||
| 74 | #ifndef SDL_PLATFORM_TVOS | ||
| 75 | @autoreleasepool { | ||
| 76 | SDL_UIKitVideoData *data = (__bridge SDL_UIKitVideoData *)_this->internal; | ||
| 77 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; | ||
| 78 | |||
| 79 | id observer = [center addObserverForName:UIPasteboardChangedNotification | ||
| 80 | object:nil | ||
| 81 | queue:nil | ||
| 82 | usingBlock:^(NSNotification *note) { | ||
| 83 | // TODO: compute mime types | ||
| 84 | SDL_SendClipboardUpdate(false, NULL, 0); | ||
| 85 | }]; | ||
| 86 | |||
| 87 | data.pasteboardObserver = observer; | ||
| 88 | } | ||
| 89 | #endif | ||
| 90 | } | ||
| 91 | |||
| 92 | void UIKit_QuitClipboard(SDL_VideoDevice *_this) | ||
| 93 | { | ||
| 94 | @autoreleasepool { | ||
| 95 | SDL_UIKitVideoData *data = (__bridge SDL_UIKitVideoData *)_this->internal; | ||
| 96 | |||
| 97 | if (data.pasteboardObserver != nil) { | ||
| 98 | [[NSNotificationCenter defaultCenter] removeObserver:data.pasteboardObserver]; | ||
| 99 | } | ||
| 100 | |||
| 101 | data.pasteboardObserver = nil; | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.h new file mode 100644 index 0000000..c767fb1 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.h | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #ifndef SDL_uikitevents_h_ | ||
| 22 | #define SDL_uikitevents_h_ | ||
| 23 | |||
| 24 | #import <UIKit/UIKit.h> | ||
| 25 | |||
| 26 | #include "../SDL_sysvideo.h" | ||
| 27 | |||
| 28 | extern void SDL_UpdateLifecycleObserver(void); | ||
| 29 | |||
| 30 | extern Uint64 UIKit_GetEventTimestamp(NSTimeInterval nsTimestamp); | ||
| 31 | extern void UIKit_PumpEvents(SDL_VideoDevice *_this); | ||
| 32 | |||
| 33 | extern void SDL_InitGCKeyboard(void); | ||
| 34 | extern void SDL_QuitGCKeyboard(void); | ||
| 35 | |||
| 36 | extern void SDL_InitGCMouse(void); | ||
| 37 | extern bool SDL_GCMouseRelativeMode(void); | ||
| 38 | extern void SDL_QuitGCMouse(void); | ||
| 39 | |||
| 40 | #endif // SDL_uikitevents_h_ | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m new file mode 100644 index 0000000..86224f7 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m | |||
| @@ -0,0 +1,461 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_VIDEO_DRIVER_UIKIT | ||
| 24 | |||
| 25 | #include "../../events/SDL_events_c.h" | ||
| 26 | #include "../../main/SDL_main_callbacks.h" | ||
| 27 | |||
| 28 | #include "SDL_uikitevents.h" | ||
| 29 | #include "SDL_uikitopengles.h" | ||
| 30 | #include "SDL_uikitvideo.h" | ||
| 31 | #include "SDL_uikitwindow.h" | ||
| 32 | |||
| 33 | #import <Foundation/Foundation.h> | ||
| 34 | #import <GameController/GameController.h> | ||
| 35 | |||
| 36 | static BOOL UIKit_EventPumpEnabled = YES; | ||
| 37 | |||
| 38 | @interface SDL_LifecycleObserver : NSObject | ||
| 39 | @property(nonatomic, assign) BOOL isObservingNotifications; | ||
| 40 | @end | ||
| 41 | |||
| 42 | @implementation SDL_LifecycleObserver | ||
| 43 | |||
| 44 | - (void)update | ||
| 45 | { | ||
| 46 | NSNotificationCenter *notificationCenter = NSNotificationCenter.defaultCenter; | ||
| 47 | bool wants_observation = (UIKit_EventPumpEnabled || SDL_HasMainCallbacks()); | ||
| 48 | if (!wants_observation) { | ||
| 49 | // Make sure no windows have active animation callbacks | ||
| 50 | int num_windows = 0; | ||
| 51 | SDL_free(SDL_GetWindows(&num_windows)); | ||
| 52 | if (num_windows > 0) { | ||
| 53 | wants_observation = true; | ||
| 54 | } | ||
| 55 | } | ||
| 56 | if (wants_observation && !self.isObservingNotifications) { | ||
| 57 | self.isObservingNotifications = YES; | ||
| 58 | [notificationCenter addObserver:self selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; | ||
| 59 | [notificationCenter addObserver:self selector:@selector(applicationWillResignActive) name:UIApplicationWillResignActiveNotification object:nil]; | ||
| 60 | [notificationCenter addObserver:self selector:@selector(applicationDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; | ||
| 61 | [notificationCenter addObserver:self selector:@selector(applicationWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil]; | ||
| 62 | [notificationCenter addObserver:self selector:@selector(applicationWillTerminate) name:UIApplicationWillTerminateNotification object:nil]; | ||
| 63 | [notificationCenter addObserver:self selector:@selector(applicationDidReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; | ||
| 64 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 65 | [notificationCenter addObserver:self | ||
| 66 | selector:@selector(applicationDidChangeStatusBarOrientation) | ||
| 67 | name:UIApplicationDidChangeStatusBarOrientationNotification | ||
| 68 | object:nil]; | ||
| 69 | #endif | ||
| 70 | } else if (!wants_observation && self.isObservingNotifications) { | ||
| 71 | self.isObservingNotifications = NO; | ||
| 72 | [notificationCenter removeObserver:self]; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | - (void)applicationDidBecomeActive | ||
| 77 | { | ||
| 78 | SDL_OnApplicationDidEnterForeground(); | ||
| 79 | } | ||
| 80 | |||
| 81 | - (void)applicationWillResignActive | ||
| 82 | { | ||
| 83 | SDL_OnApplicationWillEnterBackground(); | ||
| 84 | } | ||
| 85 | |||
| 86 | - (void)applicationDidEnterBackground | ||
| 87 | { | ||
| 88 | SDL_OnApplicationDidEnterBackground(); | ||
| 89 | } | ||
| 90 | |||
| 91 | - (void)applicationWillEnterForeground | ||
| 92 | { | ||
| 93 | SDL_OnApplicationWillEnterForeground(); | ||
| 94 | } | ||
| 95 | |||
| 96 | - (void)applicationWillTerminate | ||
| 97 | { | ||
| 98 | SDL_OnApplicationWillTerminate(); | ||
| 99 | } | ||
| 100 | |||
| 101 | - (void)applicationDidReceiveMemoryWarning | ||
| 102 | { | ||
| 103 | SDL_OnApplicationDidReceiveMemoryWarning(); | ||
| 104 | } | ||
| 105 | |||
| 106 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 107 | - (void)applicationDidChangeStatusBarOrientation | ||
| 108 | { | ||
| 109 | SDL_OnApplicationDidChangeStatusBarOrientation(); | ||
| 110 | } | ||
| 111 | #endif | ||
| 112 | |||
| 113 | @end | ||
| 114 | |||
| 115 | void SDL_UpdateLifecycleObserver(void) | ||
| 116 | { | ||
| 117 | static SDL_LifecycleObserver *lifecycleObserver; | ||
| 118 | static dispatch_once_t onceToken; | ||
| 119 | dispatch_once(&onceToken, ^{ | ||
| 120 | lifecycleObserver = [SDL_LifecycleObserver new]; | ||
| 121 | }); | ||
| 122 | [lifecycleObserver update]; | ||
| 123 | } | ||
| 124 | |||
| 125 | void SDL_SetiOSEventPump(bool enabled) | ||
| 126 | { | ||
| 127 | UIKit_EventPumpEnabled = enabled; | ||
| 128 | |||
| 129 | SDL_UpdateLifecycleObserver(); | ||
| 130 | } | ||
| 131 | |||
| 132 | Uint64 UIKit_GetEventTimestamp(NSTimeInterval nsTimestamp) | ||
| 133 | { | ||
| 134 | static Uint64 timestamp_offset; | ||
| 135 | Uint64 timestamp = (Uint64)(nsTimestamp * SDL_NS_PER_SECOND); | ||
| 136 | Uint64 now = SDL_GetTicksNS(); | ||
| 137 | |||
| 138 | if (!timestamp_offset) { | ||
| 139 | timestamp_offset = (now - timestamp); | ||
| 140 | } | ||
| 141 | timestamp += timestamp_offset; | ||
| 142 | |||
| 143 | if (timestamp > now) { | ||
| 144 | timestamp_offset -= (timestamp - now); | ||
| 145 | timestamp = now; | ||
| 146 | } | ||
| 147 | return timestamp; | ||
| 148 | } | ||
| 149 | |||
| 150 | void UIKit_PumpEvents(SDL_VideoDevice *_this) | ||
| 151 | { | ||
| 152 | if (!UIKit_EventPumpEnabled) { | ||
| 153 | return; | ||
| 154 | } | ||
| 155 | |||
| 156 | /* Let the run loop run for a short amount of time: long enough for | ||
| 157 | touch events to get processed (which is important to get certain | ||
| 158 | elements of Game Center's GKLeaderboardViewController to respond | ||
| 159 | to touch input), but not long enough to introduce a significant | ||
| 160 | delay in the rest of the app. | ||
| 161 | */ | ||
| 162 | const CFTimeInterval seconds = 0.000002; | ||
| 163 | |||
| 164 | // Pump most event types. | ||
| 165 | SInt32 result; | ||
| 166 | do { | ||
| 167 | result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, TRUE); | ||
| 168 | } while (result == kCFRunLoopRunHandledSource); | ||
| 169 | |||
| 170 | // Make sure UIScrollView objects scroll properly. | ||
| 171 | do { | ||
| 172 | result = CFRunLoopRunInMode((CFStringRef)UITrackingRunLoopMode, seconds, TRUE); | ||
| 173 | } while (result == kCFRunLoopRunHandledSource); | ||
| 174 | |||
| 175 | // See the comment in the function definition. | ||
| 176 | #if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2) | ||
| 177 | UIKit_GL_RestoreCurrentContext(); | ||
| 178 | #endif | ||
| 179 | } | ||
| 180 | |||
| 181 | static id keyboard_connect_observer = nil; | ||
| 182 | static id keyboard_disconnect_observer = nil; | ||
| 183 | |||
| 184 | static void OnGCKeyboardConnected(GCKeyboard *keyboard) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) | ||
| 185 | { | ||
| 186 | SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)keyboard; | ||
| 187 | |||
| 188 | SDL_AddKeyboard(keyboardID, NULL, true); | ||
| 189 | |||
| 190 | keyboard.keyboardInput.keyChangedHandler = ^(GCKeyboardInput *kbrd, GCControllerButtonInput *key, GCKeyCode keyCode, BOOL pressed) { | ||
| 191 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 192 | SDL_SendKeyboardKey(timestamp, keyboardID, 0, (SDL_Scancode)keyCode, pressed); | ||
| 193 | }; | ||
| 194 | |||
| 195 | dispatch_queue_t queue = dispatch_queue_create("org.libsdl.input.keyboard", DISPATCH_QUEUE_SERIAL); | ||
| 196 | dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); | ||
| 197 | keyboard.handlerQueue = queue; | ||
| 198 | } | ||
| 199 | |||
| 200 | static void OnGCKeyboardDisconnected(GCKeyboard *keyboard) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) | ||
| 201 | { | ||
| 202 | SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)keyboard; | ||
| 203 | |||
| 204 | SDL_RemoveKeyboard(keyboardID, true); | ||
| 205 | |||
| 206 | keyboard.keyboardInput.keyChangedHandler = nil; | ||
| 207 | } | ||
| 208 | |||
| 209 | void SDL_InitGCKeyboard(void) | ||
| 210 | { | ||
| 211 | @autoreleasepool { | ||
| 212 | if (@available(iOS 14.0, tvOS 14.0, *)) { | ||
| 213 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; | ||
| 214 | |||
| 215 | keyboard_connect_observer = [center addObserverForName:GCKeyboardDidConnectNotification | ||
| 216 | object:nil | ||
| 217 | queue:nil | ||
| 218 | usingBlock:^(NSNotification *note) { | ||
| 219 | GCKeyboard *keyboard = note.object; | ||
| 220 | OnGCKeyboardConnected(keyboard); | ||
| 221 | }]; | ||
| 222 | |||
| 223 | keyboard_disconnect_observer = [center addObserverForName:GCKeyboardDidDisconnectNotification | ||
| 224 | object:nil | ||
| 225 | queue:nil | ||
| 226 | usingBlock:^(NSNotification *note) { | ||
| 227 | GCKeyboard *keyboard = note.object; | ||
| 228 | OnGCKeyboardDisconnected(keyboard); | ||
| 229 | }]; | ||
| 230 | |||
| 231 | if (GCKeyboard.coalescedKeyboard != nil) { | ||
| 232 | OnGCKeyboardConnected(GCKeyboard.coalescedKeyboard); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | void SDL_QuitGCKeyboard(void) | ||
| 239 | { | ||
| 240 | @autoreleasepool { | ||
| 241 | if (@available(iOS 14.0, tvOS 14.0, *)) { | ||
| 242 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; | ||
| 243 | |||
| 244 | if (keyboard_connect_observer) { | ||
| 245 | [center removeObserver:keyboard_connect_observer name:GCKeyboardDidConnectNotification object:nil]; | ||
| 246 | keyboard_connect_observer = nil; | ||
| 247 | } | ||
| 248 | |||
| 249 | if (keyboard_disconnect_observer) { | ||
| 250 | [center removeObserver:keyboard_disconnect_observer name:GCKeyboardDidDisconnectNotification object:nil]; | ||
| 251 | keyboard_disconnect_observer = nil; | ||
| 252 | } | ||
| 253 | |||
| 254 | if (GCKeyboard.coalescedKeyboard != nil) { | ||
| 255 | OnGCKeyboardDisconnected(GCKeyboard.coalescedKeyboard); | ||
| 256 | } | ||
| 257 | } | ||
| 258 | } | ||
| 259 | } | ||
| 260 | |||
| 261 | static id mouse_connect_observer = nil; | ||
| 262 | static id mouse_disconnect_observer = nil; | ||
| 263 | static bool mouse_relative_mode = false; | ||
| 264 | static SDL_MouseWheelDirection mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL; | ||
| 265 | |||
| 266 | static void UpdateScrollDirection(void) | ||
| 267 | { | ||
| 268 | #if 0 // This code doesn't work for some reason | ||
| 269 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; | ||
| 270 | if ([userDefaults boolForKey:@"com.apple.swipescrolldirection"]) { | ||
| 271 | mouse_scroll_direction = SDL_MOUSEWHEEL_FLIPPED; | ||
| 272 | } else { | ||
| 273 | mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL; | ||
| 274 | } | ||
| 275 | #else | ||
| 276 | Boolean keyExistsAndHasValidFormat = NO; | ||
| 277 | Boolean naturalScrollDirection = CFPreferencesGetAppBooleanValue(CFSTR("com.apple.swipescrolldirection"), kCFPreferencesAnyApplication, &keyExistsAndHasValidFormat); | ||
| 278 | if (!keyExistsAndHasValidFormat) { | ||
| 279 | // Couldn't read the preference, assume natural scrolling direction | ||
| 280 | naturalScrollDirection = YES; | ||
| 281 | } | ||
| 282 | if (naturalScrollDirection) { | ||
| 283 | mouse_scroll_direction = SDL_MOUSEWHEEL_FLIPPED; | ||
| 284 | } else { | ||
| 285 | mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL; | ||
| 286 | } | ||
| 287 | #endif | ||
| 288 | } | ||
| 289 | |||
| 290 | static void UpdatePointerLock(void) | ||
| 291 | { | ||
| 292 | SDL_VideoDevice *_this = SDL_GetVideoDevice(); | ||
| 293 | SDL_Window *window; | ||
| 294 | |||
| 295 | for (window = _this->windows; window != NULL; window = window->next) { | ||
| 296 | UIKit_UpdatePointerLock(_this, window); | ||
| 297 | } | ||
| 298 | } | ||
| 299 | |||
| 300 | static bool SetGCMouseRelativeMode(bool enabled) | ||
| 301 | { | ||
| 302 | mouse_relative_mode = enabled; | ||
| 303 | UpdatePointerLock(); | ||
| 304 | return true; | ||
| 305 | } | ||
| 306 | |||
| 307 | static void OnGCMouseButtonChanged(SDL_MouseID mouseID, Uint8 button, BOOL pressed) | ||
| 308 | { | ||
| 309 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 310 | SDL_SendMouseButton(timestamp, SDL_GetMouseFocus(), mouseID, button, pressed); | ||
| 311 | } | ||
| 312 | |||
| 313 | static void OnGCMouseConnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) | ||
| 314 | { | ||
| 315 | SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)mouse; | ||
| 316 | |||
| 317 | SDL_AddMouse(mouseID, NULL, true); | ||
| 318 | |||
| 319 | mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { | ||
| 320 | OnGCMouseButtonChanged(mouseID, SDL_BUTTON_LEFT, pressed); | ||
| 321 | }; | ||
| 322 | mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { | ||
| 323 | OnGCMouseButtonChanged(mouseID, SDL_BUTTON_MIDDLE, pressed); | ||
| 324 | }; | ||
| 325 | mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { | ||
| 326 | OnGCMouseButtonChanged(mouseID, SDL_BUTTON_RIGHT, pressed); | ||
| 327 | }; | ||
| 328 | |||
| 329 | int auxiliary_button = SDL_BUTTON_X1; | ||
| 330 | for (GCControllerButtonInput *btn in mouse.mouseInput.auxiliaryButtons) { | ||
| 331 | btn.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { | ||
| 332 | OnGCMouseButtonChanged(mouseID, auxiliary_button, pressed); | ||
| 333 | }; | ||
| 334 | ++auxiliary_button; | ||
| 335 | } | ||
| 336 | |||
| 337 | mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput *mouseInput, float deltaX, float deltaY) { | ||
| 338 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 339 | |||
| 340 | if (SDL_GCMouseRelativeMode()) { | ||
| 341 | SDL_SendMouseMotion(timestamp, SDL_GetMouseFocus(), mouseID, true, deltaX, -deltaY); | ||
| 342 | } | ||
| 343 | }; | ||
| 344 | |||
| 345 | mouse.mouseInput.scroll.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) { | ||
| 346 | Uint64 timestamp = SDL_GetTicksNS(); | ||
| 347 | |||
| 348 | /* Raw scroll values come in here, vertical values in the first axis, horizontal values in the second axis. | ||
| 349 | * The vertical values are negative moving the mouse wheel up and positive moving it down. | ||
| 350 | * The horizontal values are negative moving the mouse wheel left and positive moving it right. | ||
| 351 | * The vertical values are inverted compared to SDL, and the horizontal values are as expected. | ||
| 352 | */ | ||
| 353 | float vertical = -xValue; | ||
| 354 | float horizontal = yValue; | ||
| 355 | |||
| 356 | if (mouse_scroll_direction == SDL_MOUSEWHEEL_FLIPPED) { | ||
| 357 | // Since these are raw values, we need to flip them ourselves | ||
| 358 | vertical = -vertical; | ||
| 359 | horizontal = -horizontal; | ||
| 360 | } | ||
| 361 | SDL_SendMouseWheel(timestamp, SDL_GetMouseFocus(), mouseID, horizontal, vertical, mouse_scroll_direction); | ||
| 362 | }; | ||
| 363 | UpdateScrollDirection(); | ||
| 364 | |||
| 365 | dispatch_queue_t queue = dispatch_queue_create("org.libsdl.input.mouse", DISPATCH_QUEUE_SERIAL); | ||
| 366 | dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); | ||
| 367 | mouse.handlerQueue = queue; | ||
| 368 | |||
| 369 | UpdatePointerLock(); | ||
| 370 | } | ||
| 371 | |||
| 372 | static void OnGCMouseDisconnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) | ||
| 373 | { | ||
| 374 | SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)mouse; | ||
| 375 | |||
| 376 | mouse.mouseInput.mouseMovedHandler = nil; | ||
| 377 | |||
| 378 | mouse.mouseInput.leftButton.pressedChangedHandler = nil; | ||
| 379 | mouse.mouseInput.middleButton.pressedChangedHandler = nil; | ||
| 380 | mouse.mouseInput.rightButton.pressedChangedHandler = nil; | ||
| 381 | |||
| 382 | for (GCControllerButtonInput *button in mouse.mouseInput.auxiliaryButtons) { | ||
| 383 | button.pressedChangedHandler = nil; | ||
| 384 | } | ||
| 385 | |||
| 386 | UpdatePointerLock(); | ||
| 387 | |||
| 388 | SDL_RemoveMouse(mouseID, true); | ||
| 389 | } | ||
| 390 | |||
| 391 | void SDL_InitGCMouse(void) | ||
| 392 | { | ||
| 393 | @autoreleasepool { | ||
| 394 | // There is a bug where mouse accumulates duplicate deltas over time in iOS 14.0 | ||
| 395 | if (@available(iOS 14.1, tvOS 14.1, *)) { | ||
| 396 | /* iOS will not send the new pointer touch events if you don't have this key, | ||
| 397 | * and we need them to differentiate between mouse events and real touch events. | ||
| 398 | */ | ||
| 399 | BOOL indirect_input_available = [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"UIApplicationSupportsIndirectInputEvents"] boolValue]; | ||
| 400 | if (indirect_input_available) { | ||
| 401 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; | ||
| 402 | |||
| 403 | mouse_connect_observer = [center addObserverForName:GCMouseDidConnectNotification | ||
| 404 | object:nil | ||
| 405 | queue:nil | ||
| 406 | usingBlock:^(NSNotification *note) { | ||
| 407 | GCMouse *mouse = note.object; | ||
| 408 | OnGCMouseConnected(mouse); | ||
| 409 | }]; | ||
| 410 | |||
| 411 | mouse_disconnect_observer = [center addObserverForName:GCMouseDidDisconnectNotification | ||
| 412 | object:nil | ||
| 413 | queue:nil | ||
| 414 | usingBlock:^(NSNotification *note) { | ||
| 415 | GCMouse *mouse = note.object; | ||
| 416 | OnGCMouseDisconnected(mouse); | ||
| 417 | }]; | ||
| 418 | |||
| 419 | for (GCMouse *mouse in [GCMouse mice]) { | ||
| 420 | OnGCMouseConnected(mouse); | ||
| 421 | } | ||
| 422 | |||
| 423 | SDL_GetMouse()->SetRelativeMouseMode = SetGCMouseRelativeMode; | ||
| 424 | } else { | ||
| 425 | NSLog(@"You need UIApplicationSupportsIndirectInputEvents in your Info.plist for mouse support"); | ||
| 426 | } | ||
| 427 | } | ||
| 428 | } | ||
| 429 | } | ||
| 430 | |||
| 431 | bool SDL_GCMouseRelativeMode(void) | ||
| 432 | { | ||
| 433 | return mouse_relative_mode; | ||
| 434 | } | ||
| 435 | |||
| 436 | void SDL_QuitGCMouse(void) | ||
| 437 | { | ||
| 438 | @autoreleasepool { | ||
| 439 | if (@available(iOS 14.1, tvOS 14.1, *)) { | ||
| 440 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; | ||
| 441 | |||
| 442 | if (mouse_connect_observer) { | ||
| 443 | [center removeObserver:mouse_connect_observer name:GCMouseDidConnectNotification object:nil]; | ||
| 444 | mouse_connect_observer = nil; | ||
| 445 | } | ||
| 446 | |||
| 447 | if (mouse_disconnect_observer) { | ||
| 448 | [center removeObserver:mouse_disconnect_observer name:GCMouseDidDisconnectNotification object:nil]; | ||
| 449 | mouse_disconnect_observer = nil; | ||
| 450 | } | ||
| 451 | |||
| 452 | for (GCMouse *mouse in [GCMouse mice]) { | ||
| 453 | OnGCMouseDisconnected(mouse); | ||
| 454 | } | ||
| 455 | |||
| 456 | SDL_GetMouse()->SetRelativeMouseMode = NULL; | ||
| 457 | } | ||
| 458 | } | ||
| 459 | } | ||
| 460 | |||
| 461 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.h new file mode 100644 index 0000000..5c21f7c --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.h | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_VIDEO_DRIVER_UIKIT | ||
| 24 | |||
| 25 | extern bool UIKit_ShowingMessageBox(void); | ||
| 26 | extern bool UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID); | ||
| 27 | |||
| 28 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.m new file mode 100644 index 0000000..a57b3f7 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.m | |||
| @@ -0,0 +1,154 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_VIDEO_DRIVER_UIKIT | ||
| 24 | |||
| 25 | #include "SDL_uikitvideo.h" | ||
| 26 | #include "SDL_uikitwindow.h" | ||
| 27 | |||
| 28 | // Display a UIKit message box | ||
| 29 | |||
| 30 | static bool s_showingMessageBox = false; | ||
| 31 | |||
| 32 | bool UIKit_ShowingMessageBox(void) | ||
| 33 | { | ||
| 34 | return s_showingMessageBox; | ||
| 35 | } | ||
| 36 | |||
| 37 | static void UIKit_WaitUntilMessageBoxClosed(const SDL_MessageBoxData *messageboxdata, int *clickedindex) | ||
| 38 | { | ||
| 39 | *clickedindex = messageboxdata->numbuttons; | ||
| 40 | |||
| 41 | @autoreleasepool { | ||
| 42 | // Run the main event loop until the alert has finished | ||
| 43 | // Note that this needs to be done on the main thread | ||
| 44 | s_showingMessageBox = true; | ||
| 45 | while ((*clickedindex) == messageboxdata->numbuttons) { | ||
| 46 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; | ||
| 47 | } | ||
| 48 | s_showingMessageBox = false; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | static BOOL UIKit_ShowMessageBoxAlertController(const SDL_MessageBoxData *messageboxdata, int *buttonID) | ||
| 53 | { | ||
| 54 | int i; | ||
| 55 | int __block clickedindex = messageboxdata->numbuttons; | ||
| 56 | UIWindow *window = nil; | ||
| 57 | UIWindow *alertwindow = nil; | ||
| 58 | |||
| 59 | if (![UIAlertController class]) { | ||
| 60 | return NO; | ||
| 61 | } | ||
| 62 | |||
| 63 | UIAlertController *alert; | ||
| 64 | alert = [UIAlertController alertControllerWithTitle:@(messageboxdata->title) | ||
| 65 | message:@(messageboxdata->message) | ||
| 66 | preferredStyle:UIAlertControllerStyleAlert]; | ||
| 67 | |||
| 68 | for (i = 0; i < messageboxdata->numbuttons; i++) { | ||
| 69 | UIAlertAction *action; | ||
| 70 | UIAlertActionStyle style = UIAlertActionStyleDefault; | ||
| 71 | const SDL_MessageBoxButtonData *sdlButton; | ||
| 72 | |||
| 73 | if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT) { | ||
| 74 | sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i]; | ||
| 75 | } else { | ||
| 76 | sdlButton = &messageboxdata->buttons[i]; | ||
| 77 | } | ||
| 78 | |||
| 79 | if (sdlButton->flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) { | ||
| 80 | style = UIAlertActionStyleCancel; | ||
| 81 | } | ||
| 82 | |||
| 83 | action = [UIAlertAction actionWithTitle:@(sdlButton->text) | ||
| 84 | style:style | ||
| 85 | handler:^(UIAlertAction *alertAction) { | ||
| 86 | clickedindex = (int)(sdlButton - messageboxdata->buttons); | ||
| 87 | }]; | ||
| 88 | [alert addAction:action]; | ||
| 89 | |||
| 90 | if (sdlButton->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) { | ||
| 91 | alert.preferredAction = action; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | if (messageboxdata->window) { | ||
| 96 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)messageboxdata->window->internal; | ||
| 97 | window = data.uiwindow; | ||
| 98 | } | ||
| 99 | |||
| 100 | if (window == nil || window.rootViewController == nil) { | ||
| 101 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 102 | alertwindow = [[UIWindow alloc] init]; | ||
| 103 | #else | ||
| 104 | alertwindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; | ||
| 105 | #endif | ||
| 106 | alertwindow.rootViewController = [UIViewController new]; | ||
| 107 | alertwindow.windowLevel = UIWindowLevelAlert; | ||
| 108 | |||
| 109 | window = alertwindow; | ||
| 110 | |||
| 111 | [alertwindow makeKeyAndVisible]; | ||
| 112 | } | ||
| 113 | |||
| 114 | [window.rootViewController presentViewController:alert animated:YES completion:nil]; | ||
| 115 | UIKit_WaitUntilMessageBoxClosed(messageboxdata, &clickedindex); | ||
| 116 | |||
| 117 | if (alertwindow) { | ||
| 118 | alertwindow.hidden = YES; | ||
| 119 | } | ||
| 120 | |||
| 121 | UIKit_ForceUpdateHomeIndicator(); | ||
| 122 | |||
| 123 | *buttonID = messageboxdata->buttons[clickedindex].buttonID; | ||
| 124 | return YES; | ||
| 125 | } | ||
| 126 | |||
| 127 | static void UIKit_ShowMessageBoxImpl(const SDL_MessageBoxData *messageboxdata, int *buttonID, int *result) | ||
| 128 | { | ||
| 129 | @autoreleasepool { | ||
| 130 | if (UIKit_ShowMessageBoxAlertController(messageboxdata, buttonID)) { | ||
| 131 | *result = true; | ||
| 132 | } else { | ||
| 133 | *result = SDL_SetError("Could not show message box."); | ||
| 134 | } | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | bool UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) | ||
| 139 | { | ||
| 140 | @autoreleasepool { | ||
| 141 | __block int result = true; | ||
| 142 | |||
| 143 | if ([NSThread isMainThread]) { | ||
| 144 | UIKit_ShowMessageBoxImpl(messageboxdata, buttonID, &result); | ||
| 145 | } else { | ||
| 146 | dispatch_sync(dispatch_get_main_queue(), ^{ | ||
| 147 | UIKit_ShowMessageBoxImpl(messageboxdata, buttonID, &result); | ||
| 148 | }); | ||
| 149 | } | ||
| 150 | return result; | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.h new file mode 100644 index 0000000..20bcf7c --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.h | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | /* | ||
| 23 | * @author Mark Callow, www.edgewise-consulting.com. | ||
| 24 | * | ||
| 25 | * Thanks to @slime73 on GitHub for their gist showing how to add a CAMetalLayer | ||
| 26 | * backed view. | ||
| 27 | */ | ||
| 28 | |||
| 29 | #ifndef SDL_uikitmetalview_h_ | ||
| 30 | #define SDL_uikitmetalview_h_ | ||
| 31 | |||
| 32 | #include "../SDL_sysvideo.h" | ||
| 33 | #include "SDL_uikitwindow.h" | ||
| 34 | |||
| 35 | #if defined(SDL_VIDEO_DRIVER_UIKIT) && (defined(SDL_VIDEO_VULKAN) || defined(SDL_VIDEO_METAL)) | ||
| 36 | |||
| 37 | #import <UIKit/UIKit.h> | ||
| 38 | #import <Metal/Metal.h> | ||
| 39 | #import <QuartzCore/CAMetalLayer.h> | ||
| 40 | |||
| 41 | @interface SDL_uikitmetalview : SDL_uikitview | ||
| 42 | |||
| 43 | - (instancetype)initWithFrame:(CGRect)frame | ||
| 44 | scale:(CGFloat)scale; | ||
| 45 | |||
| 46 | @end | ||
| 47 | |||
| 48 | SDL_MetalView UIKit_Metal_CreateView(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 49 | void UIKit_Metal_DestroyView(SDL_VideoDevice *_this, SDL_MetalView view); | ||
| 50 | void *UIKit_Metal_GetLayer(SDL_VideoDevice *_this, SDL_MetalView view); | ||
| 51 | |||
| 52 | #endif // SDL_VIDEO_DRIVER_UIKIT && (SDL_VIDEO_VULKAN || SDL_VIDEO_METAL) | ||
| 53 | |||
| 54 | #endif // SDL_uikitmetalview_h_ | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.m new file mode 100644 index 0000000..010dae4 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.m | |||
| @@ -0,0 +1,140 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | /* | ||
| 23 | * @author Mark Callow, www.edgewise-consulting.com. | ||
| 24 | * | ||
| 25 | * Thanks to @slime73 on GitHub for their gist showing how to add a CAMetalLayer | ||
| 26 | * backed view. | ||
| 27 | */ | ||
| 28 | |||
| 29 | #include "SDL_internal.h" | ||
| 30 | |||
| 31 | #if defined(SDL_VIDEO_DRIVER_UIKIT) && (defined(SDL_VIDEO_VULKAN) || defined(SDL_VIDEO_METAL)) | ||
| 32 | |||
| 33 | #include "../SDL_sysvideo.h" | ||
| 34 | #include "../../events/SDL_windowevents_c.h" | ||
| 35 | |||
| 36 | #import "SDL_uikitwindow.h" | ||
| 37 | #import "SDL_uikitmetalview.h" | ||
| 38 | |||
| 39 | @implementation SDL_uikitmetalview | ||
| 40 | |||
| 41 | // Returns a Metal-compatible layer. | ||
| 42 | + (Class)layerClass | ||
| 43 | { | ||
| 44 | return [CAMetalLayer class]; | ||
| 45 | } | ||
| 46 | |||
| 47 | - (instancetype)initWithFrame:(CGRect)frame | ||
| 48 | scale:(CGFloat)scale | ||
| 49 | { | ||
| 50 | if ((self = [super initWithFrame:frame])) { | ||
| 51 | self.tag = SDL_METALVIEW_TAG; | ||
| 52 | self.layer.contentsScale = scale; | ||
| 53 | [self updateDrawableSize]; | ||
| 54 | } | ||
| 55 | |||
| 56 | return self; | ||
| 57 | } | ||
| 58 | |||
| 59 | // Set the size of the metal drawables when the view is resized. | ||
| 60 | - (void)layoutSubviews | ||
| 61 | { | ||
| 62 | [super layoutSubviews]; | ||
| 63 | [self updateDrawableSize]; | ||
| 64 | } | ||
| 65 | |||
| 66 | - (void)updateDrawableSize | ||
| 67 | { | ||
| 68 | CGSize size = self.bounds.size; | ||
| 69 | size.width *= self.layer.contentsScale; | ||
| 70 | size.height *= self.layer.contentsScale; | ||
| 71 | |||
| 72 | CAMetalLayer *metallayer = ((CAMetalLayer *)self.layer); | ||
| 73 | if (metallayer.drawableSize.width != size.width || | ||
| 74 | metallayer.drawableSize.height != size.height) { | ||
| 75 | metallayer.drawableSize = size; | ||
| 76 | SDL_SendWindowEvent([self getSDLWindow], SDL_EVENT_WINDOW_METAL_VIEW_RESIZED, 0, 0); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | @end | ||
| 81 | |||
| 82 | SDL_MetalView UIKit_Metal_CreateView(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 83 | { | ||
| 84 | @autoreleasepool { | ||
| 85 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 86 | CGFloat scale = 1.0; | ||
| 87 | SDL_uikitmetalview *metalview; | ||
| 88 | |||
| 89 | if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { | ||
| 90 | /* Set the scale to the natural scale factor of the screen - then | ||
| 91 | * the backing dimensions of the Metal view will match the pixel | ||
| 92 | * dimensions of the screen rather than the dimensions in points | ||
| 93 | * yielding high resolution on retine displays. | ||
| 94 | */ | ||
| 95 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 96 | scale = data.uiwindow.screen.nativeScale; | ||
| 97 | #else | ||
| 98 | // VisionOS doesn't use the concept of "nativeScale" like other iOS devices. | ||
| 99 | // We use a fixed scale factor of 2.0 to achieve better pixel density. | ||
| 100 | // This is because VisionOS presents a virtual 1280x720 "screen", but we need | ||
| 101 | // to render at a higher resolution for optimal visual quality. | ||
| 102 | // TODO: Consider making this configurable or determining it dynamically | ||
| 103 | // based on the specific visionOS device capabilities. | ||
| 104 | scale = 2.0; | ||
| 105 | #endif | ||
| 106 | } | ||
| 107 | |||
| 108 | metalview = [[SDL_uikitmetalview alloc] initWithFrame:data.uiwindow.bounds | ||
| 109 | scale:scale]; | ||
| 110 | if (metalview == nil) { | ||
| 111 | SDL_OutOfMemory(); | ||
| 112 | return NULL; | ||
| 113 | } | ||
| 114 | |||
| 115 | [metalview setSDLWindow:window]; | ||
| 116 | |||
| 117 | return (void *)CFBridgingRetain(metalview); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | void UIKit_Metal_DestroyView(SDL_VideoDevice *_this, SDL_MetalView view) | ||
| 122 | { | ||
| 123 | @autoreleasepool { | ||
| 124 | SDL_uikitmetalview *metalview = CFBridgingRelease(view); | ||
| 125 | |||
| 126 | if ([metalview isKindOfClass:[SDL_uikitmetalview class]]) { | ||
| 127 | [metalview setSDLWindow:NULL]; | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | void *UIKit_Metal_GetLayer(SDL_VideoDevice *_this, SDL_MetalView view) | ||
| 133 | { | ||
| 134 | @autoreleasepool { | ||
| 135 | SDL_uikitview *uiview = (__bridge SDL_uikitview *)view; | ||
| 136 | return (__bridge void *)uiview.layer; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | #endif // SDL_VIDEO_DRIVER_UIKIT && (SDL_VIDEO_VULKAN || SDL_VIDEO_METAL) | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.h new file mode 100644 index 0000000..1b25cfd --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.h | |||
| @@ -0,0 +1,67 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifndef SDL_uikitmodes_h_ | ||
| 24 | #define SDL_uikitmodes_h_ | ||
| 25 | |||
| 26 | #include "SDL_uikitvideo.h" | ||
| 27 | |||
| 28 | @interface SDL_UIKitDisplayData : NSObject | ||
| 29 | |||
| 30 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 31 | - (instancetype)initWithScreen:(UIScreen *)screen; | ||
| 32 | @property(nonatomic, strong) UIScreen *uiscreen; | ||
| 33 | #endif | ||
| 34 | |||
| 35 | @end | ||
| 36 | |||
| 37 | @interface SDL_UIKitDisplayModeData : NSObject | ||
| 38 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 39 | @property(nonatomic, strong) UIScreenMode *uiscreenmode; | ||
| 40 | #endif | ||
| 41 | |||
| 42 | @end | ||
| 43 | |||
| 44 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 45 | extern bool UIKit_IsDisplayLandscape(UIScreen *uiscreen); | ||
| 46 | #endif | ||
| 47 | |||
| 48 | extern bool UIKit_InitModes(SDL_VideoDevice *_this); | ||
| 49 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 50 | extern bool UIKit_AddDisplay(UIScreen *uiscreen, bool send_event); | ||
| 51 | extern void UIKit_DelDisplay(UIScreen *uiscreen, bool send_event); | ||
| 52 | #endif | ||
| 53 | extern bool UIKit_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display); | ||
| 54 | extern bool UIKit_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode); | ||
| 55 | extern void UIKit_QuitModes(SDL_VideoDevice *_this); | ||
| 56 | extern bool UIKit_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect); | ||
| 57 | |||
| 58 | // because visionOS does not have a screen | ||
| 59 | // we create a fake display to maintain compatibility. | ||
| 60 | // By default, a window measures 1280x720 pt. | ||
| 61 | // https://developer.apple.com/design/human-interface-guidelines/windows#visionOS | ||
| 62 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 63 | #define SDL_XR_SCREENWIDTH 1280 | ||
| 64 | #define SDL_XR_SCREENHEIGHT 720 | ||
| 65 | #endif | ||
| 66 | |||
| 67 | #endif // SDL_uikitmodes_h_ | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.m new file mode 100644 index 0000000..d3247db --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.m | |||
| @@ -0,0 +1,543 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_VIDEO_DRIVER_UIKIT | ||
| 24 | |||
| 25 | #include "SDL_uikitmodes.h" | ||
| 26 | |||
| 27 | #include "../../events/SDL_events_c.h" | ||
| 28 | |||
| 29 | #import <sys/utsname.h> | ||
| 30 | |||
| 31 | @implementation SDL_UIKitDisplayData | ||
| 32 | |||
| 33 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 34 | - (instancetype)initWithScreen:(UIScreen *)screen | ||
| 35 | { | ||
| 36 | if (self = [super init]) { | ||
| 37 | self.uiscreen = screen; | ||
| 38 | } | ||
| 39 | return self; | ||
| 40 | } | ||
| 41 | @synthesize uiscreen; | ||
| 42 | #endif | ||
| 43 | |||
| 44 | @end | ||
| 45 | |||
| 46 | @implementation SDL_UIKitDisplayModeData | ||
| 47 | |||
| 48 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 49 | @synthesize uiscreenmode; | ||
| 50 | #endif | ||
| 51 | |||
| 52 | @end | ||
| 53 | |||
| 54 | @interface SDL_DisplayWatch : NSObject | ||
| 55 | @end | ||
| 56 | |||
| 57 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 58 | @implementation SDL_DisplayWatch | ||
| 59 | |||
| 60 | + (void)start | ||
| 61 | { | ||
| 62 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; | ||
| 63 | |||
| 64 | [center addObserver:self | ||
| 65 | selector:@selector(screenConnected:) | ||
| 66 | name:UIScreenDidConnectNotification | ||
| 67 | object:nil]; | ||
| 68 | [center addObserver:self | ||
| 69 | selector:@selector(screenDisconnected:) | ||
| 70 | name:UIScreenDidDisconnectNotification | ||
| 71 | object:nil]; | ||
| 72 | } | ||
| 73 | |||
| 74 | + (void)stop | ||
| 75 | { | ||
| 76 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; | ||
| 77 | |||
| 78 | [center removeObserver:self | ||
| 79 | name:UIScreenDidConnectNotification | ||
| 80 | object:nil]; | ||
| 81 | [center removeObserver:self | ||
| 82 | name:UIScreenDidDisconnectNotification | ||
| 83 | object:nil]; | ||
| 84 | } | ||
| 85 | |||
| 86 | + (void)screenConnected:(NSNotification *)notification | ||
| 87 | { | ||
| 88 | UIScreen *uiscreen = [notification object]; | ||
| 89 | UIKit_AddDisplay(uiscreen, true); | ||
| 90 | } | ||
| 91 | |||
| 92 | + (void)screenDisconnected:(NSNotification *)notification | ||
| 93 | { | ||
| 94 | UIScreen *uiscreen = [notification object]; | ||
| 95 | UIKit_DelDisplay(uiscreen, true); | ||
| 96 | } | ||
| 97 | |||
| 98 | @end | ||
| 99 | #endif | ||
| 100 | |||
| 101 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 102 | static bool UIKit_AllocateDisplayModeData(SDL_DisplayMode *mode, | ||
| 103 | UIScreenMode *uiscreenmode) | ||
| 104 | { | ||
| 105 | SDL_UIKitDisplayModeData *data = nil; | ||
| 106 | |||
| 107 | if (uiscreenmode != nil) { | ||
| 108 | // Allocate the display mode data | ||
| 109 | data = [[SDL_UIKitDisplayModeData alloc] init]; | ||
| 110 | if (!data) { | ||
| 111 | return SDL_OutOfMemory(); | ||
| 112 | } | ||
| 113 | |||
| 114 | data.uiscreenmode = uiscreenmode; | ||
| 115 | } | ||
| 116 | |||
| 117 | mode->internal = (void *)CFBridgingRetain(data); | ||
| 118 | |||
| 119 | return true; | ||
| 120 | } | ||
| 121 | #endif | ||
| 122 | |||
| 123 | static void UIKit_FreeDisplayModeData(SDL_DisplayMode *mode) | ||
| 124 | { | ||
| 125 | if (mode->internal != NULL) { | ||
| 126 | CFRelease(mode->internal); | ||
| 127 | mode->internal = NULL; | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 132 | static float UIKit_GetDisplayModeRefreshRate(UIScreen *uiscreen) | ||
| 133 | { | ||
| 134 | return (float)uiscreen.maximumFramesPerSecond; | ||
| 135 | } | ||
| 136 | |||
| 137 | static bool UIKit_AddSingleDisplayMode(SDL_VideoDisplay *display, int w, int h, | ||
| 138 | UIScreen *uiscreen, UIScreenMode *uiscreenmode) | ||
| 139 | { | ||
| 140 | SDL_DisplayMode mode; | ||
| 141 | |||
| 142 | SDL_zero(mode); | ||
| 143 | if (!UIKit_AllocateDisplayModeData(&mode, uiscreenmode)) { | ||
| 144 | return false; | ||
| 145 | } | ||
| 146 | |||
| 147 | mode.w = w; | ||
| 148 | mode.h = h; | ||
| 149 | mode.pixel_density = uiscreen.nativeScale; | ||
| 150 | mode.refresh_rate = UIKit_GetDisplayModeRefreshRate(uiscreen); | ||
| 151 | mode.format = SDL_PIXELFORMAT_ABGR8888; | ||
| 152 | |||
| 153 | if (SDL_AddFullscreenDisplayMode(display, &mode)) { | ||
| 154 | return true; | ||
| 155 | } else { | ||
| 156 | UIKit_FreeDisplayModeData(&mode); | ||
| 157 | return false; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | static bool UIKit_AddDisplayMode(SDL_VideoDisplay *display, int w, int h, | ||
| 162 | UIScreen *uiscreen, UIScreenMode *uiscreenmode, bool addRotation) | ||
| 163 | { | ||
| 164 | if (!UIKit_AddSingleDisplayMode(display, w, h, uiscreen, uiscreenmode)) { | ||
| 165 | return false; | ||
| 166 | } | ||
| 167 | |||
| 168 | if (addRotation) { | ||
| 169 | // Add the rotated version | ||
| 170 | if (!UIKit_AddSingleDisplayMode(display, h, w, uiscreen, uiscreenmode)) { | ||
| 171 | return false; | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | return true; | ||
| 176 | } | ||
| 177 | |||
| 178 | static CGSize GetUIScreenModeSize(UIScreen *uiscreen, UIScreenMode *mode) | ||
| 179 | { | ||
| 180 | /* For devices such as iPhone 6/7/8 Plus, the UIScreenMode reported by iOS | ||
| 181 | * isn't the physical pixels of the display, but rather the point size times | ||
| 182 | * the scale. For example, on iOS 12.2 on iPhone 8 Plus the physical pixel | ||
| 183 | * resolution is 1080x1920, the size reported by mode.size is 1242x2208, | ||
| 184 | * the size in points is 414x736, the scale property is 3.0, and the | ||
| 185 | * nativeScale property is ~2.6087 (ie 1920.0 / 736.0). | ||
| 186 | * | ||
| 187 | * What we want for the mode size is the point size, and the pixel density | ||
| 188 | * is the native scale. | ||
| 189 | * | ||
| 190 | * Note that the iOS Simulator doesn't have this behavior for those devices. | ||
| 191 | * https://github.com/libsdl-org/SDL/issues/3220 | ||
| 192 | */ | ||
| 193 | CGSize size = mode.size; | ||
| 194 | |||
| 195 | size.width = SDL_round(size.width / uiscreen.scale); | ||
| 196 | size.height = SDL_round(size.height / uiscreen.scale); | ||
| 197 | |||
| 198 | return size; | ||
| 199 | } | ||
| 200 | |||
| 201 | bool UIKit_AddDisplay(UIScreen *uiscreen, bool send_event) | ||
| 202 | { | ||
| 203 | UIScreenMode *uiscreenmode = uiscreen.currentMode; | ||
| 204 | CGSize size = GetUIScreenModeSize(uiscreen, uiscreenmode); | ||
| 205 | SDL_VideoDisplay display; | ||
| 206 | SDL_DisplayMode mode; | ||
| 207 | |||
| 208 | // Make sure the width/height are oriented correctly | ||
| 209 | if (UIKit_IsDisplayLandscape(uiscreen) != (size.width > size.height)) { | ||
| 210 | CGFloat height = size.width; | ||
| 211 | size.width = size.height; | ||
| 212 | size.height = height; | ||
| 213 | } | ||
| 214 | |||
| 215 | SDL_zero(mode); | ||
| 216 | mode.w = (int)size.width; | ||
| 217 | mode.h = (int)size.height; | ||
| 218 | mode.pixel_density = uiscreen.nativeScale; | ||
| 219 | mode.format = SDL_PIXELFORMAT_ABGR8888; | ||
| 220 | mode.refresh_rate = UIKit_GetDisplayModeRefreshRate(uiscreen); | ||
| 221 | |||
| 222 | if (!UIKit_AllocateDisplayModeData(&mode, uiscreenmode)) { | ||
| 223 | return false; | ||
| 224 | } | ||
| 225 | |||
| 226 | SDL_zero(display); | ||
| 227 | #ifndef SDL_PLATFORM_TVOS | ||
| 228 | if (uiscreen == [UIScreen mainScreen]) { | ||
| 229 | // The natural orientation (used by sensors) is portrait | ||
| 230 | display.natural_orientation = SDL_ORIENTATION_PORTRAIT; | ||
| 231 | } else | ||
| 232 | #endif | ||
| 233 | if (UIKit_IsDisplayLandscape(uiscreen)) { | ||
| 234 | display.natural_orientation = SDL_ORIENTATION_LANDSCAPE; | ||
| 235 | } else { | ||
| 236 | display.natural_orientation = SDL_ORIENTATION_PORTRAIT; | ||
| 237 | } | ||
| 238 | display.desktop_mode = mode; | ||
| 239 | |||
| 240 | display.HDR.SDR_white_level = 1.0f; | ||
| 241 | display.HDR.HDR_headroom = 1.0f; | ||
| 242 | |||
| 243 | #ifndef SDL_PLATFORM_TVOS | ||
| 244 | if (@available(iOS 16.0, *)) { | ||
| 245 | if (uiscreen.currentEDRHeadroom > 1.0f) { | ||
| 246 | display.HDR.HDR_headroom = uiscreen.currentEDRHeadroom; | ||
| 247 | } else { | ||
| 248 | display.HDR.HDR_headroom = uiscreen.potentialEDRHeadroom; | ||
| 249 | } | ||
| 250 | } | ||
| 251 | #endif // !SDL_PLATFORM_TVOS | ||
| 252 | |||
| 253 | // Allocate the display data | ||
| 254 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 255 | SDL_UIKitDisplayData *data = [[SDL_UIKitDisplayData alloc] init]; | ||
| 256 | #else | ||
| 257 | SDL_UIKitDisplayData *data = [[SDL_UIKitDisplayData alloc] initWithScreen:uiscreen]; | ||
| 258 | #endif | ||
| 259 | if (!data) { | ||
| 260 | UIKit_FreeDisplayModeData(&display.desktop_mode); | ||
| 261 | return SDL_OutOfMemory(); | ||
| 262 | } | ||
| 263 | |||
| 264 | display.internal = (SDL_DisplayData *)CFBridgingRetain(data); | ||
| 265 | if (SDL_AddVideoDisplay(&display, send_event) == 0) { | ||
| 266 | return false; | ||
| 267 | } | ||
| 268 | return true; | ||
| 269 | } | ||
| 270 | #endif | ||
| 271 | |||
| 272 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 273 | bool UIKit_AddDisplay(bool send_event){ | ||
| 274 | CGSize size = CGSizeMake(SDL_XR_SCREENWIDTH, SDL_XR_SCREENHEIGHT); | ||
| 275 | SDL_VideoDisplay display; | ||
| 276 | SDL_DisplayMode mode; | ||
| 277 | |||
| 278 | SDL_zero(mode); | ||
| 279 | mode.w = (int)size.width; | ||
| 280 | mode.h = (int)size.height; | ||
| 281 | mode.pixel_density = 1; | ||
| 282 | mode.format = SDL_PIXELFORMAT_ABGR8888; | ||
| 283 | mode.refresh_rate = 60.0f; | ||
| 284 | |||
| 285 | display.natural_orientation = SDL_ORIENTATION_LANDSCAPE; | ||
| 286 | |||
| 287 | display.desktop_mode = mode; | ||
| 288 | |||
| 289 | SDL_UIKitDisplayData *data = [[SDL_UIKitDisplayData alloc] init]; | ||
| 290 | |||
| 291 | if (!data) { | ||
| 292 | UIKit_FreeDisplayModeData(&display.desktop_mode); | ||
| 293 | return SDL_OutOfMemory(); | ||
| 294 | } | ||
| 295 | |||
| 296 | display.internal = (SDL_DisplayData *)CFBridgingRetain(data); | ||
| 297 | if (SDL_AddVideoDisplay(&display, send_event) == 0) { | ||
| 298 | return false; | ||
| 299 | } | ||
| 300 | return true; | ||
| 301 | } | ||
| 302 | #endif | ||
| 303 | |||
| 304 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 305 | |||
| 306 | void UIKit_DelDisplay(UIScreen *uiscreen, bool send_event) | ||
| 307 | { | ||
| 308 | SDL_DisplayID *displays; | ||
| 309 | int i; | ||
| 310 | |||
| 311 | displays = SDL_GetDisplays(NULL); | ||
| 312 | if (displays) { | ||
| 313 | for (i = 0; displays[i]; ++i) { | ||
| 314 | SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]); | ||
| 315 | SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal; | ||
| 316 | |||
| 317 | if (data && data.uiscreen == uiscreen) { | ||
| 318 | CFRelease(display->internal); | ||
| 319 | display->internal = NULL; | ||
| 320 | SDL_DelVideoDisplay(displays[i], send_event); | ||
| 321 | break; | ||
| 322 | } | ||
| 323 | } | ||
| 324 | SDL_free(displays); | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | bool UIKit_IsDisplayLandscape(UIScreen *uiscreen) | ||
| 329 | { | ||
| 330 | #ifndef SDL_PLATFORM_TVOS | ||
| 331 | if (uiscreen == [UIScreen mainScreen]) { | ||
| 332 | return UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation); | ||
| 333 | } else | ||
| 334 | #endif // !SDL_PLATFORM_TVOS | ||
| 335 | { | ||
| 336 | CGSize size = uiscreen.bounds.size; | ||
| 337 | return (size.width > size.height); | ||
| 338 | } | ||
| 339 | } | ||
| 340 | #endif | ||
| 341 | bool UIKit_InitModes(SDL_VideoDevice *_this) | ||
| 342 | { | ||
| 343 | @autoreleasepool { | ||
| 344 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 345 | UIKit_AddDisplay(false); | ||
| 346 | #else | ||
| 347 | for (UIScreen *uiscreen in [UIScreen screens]) { | ||
| 348 | if (!UIKit_AddDisplay(uiscreen, false)) { | ||
| 349 | return false; | ||
| 350 | } | ||
| 351 | } | ||
| 352 | #endif | ||
| 353 | |||
| 354 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 355 | SDL_OnApplicationDidChangeStatusBarOrientation(); | ||
| 356 | #endif | ||
| 357 | |||
| 358 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 359 | [SDL_DisplayWatch start]; | ||
| 360 | #endif | ||
| 361 | } | ||
| 362 | |||
| 363 | return true; | ||
| 364 | } | ||
| 365 | |||
| 366 | bool UIKit_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display) | ||
| 367 | { | ||
| 368 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 369 | @autoreleasepool { | ||
| 370 | SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal; | ||
| 371 | |||
| 372 | bool isLandscape = UIKit_IsDisplayLandscape(data.uiscreen); | ||
| 373 | bool addRotation = (data.uiscreen == [UIScreen mainScreen]); | ||
| 374 | NSArray *availableModes = nil; | ||
| 375 | |||
| 376 | #ifdef SDL_PLATFORM_TVOS | ||
| 377 | addRotation = false; | ||
| 378 | availableModes = @[ data.uiscreen.currentMode ]; | ||
| 379 | #else | ||
| 380 | availableModes = data.uiscreen.availableModes; | ||
| 381 | #endif | ||
| 382 | |||
| 383 | for (UIScreenMode *uimode in availableModes) { | ||
| 384 | CGSize size = GetUIScreenModeSize(data.uiscreen, uimode); | ||
| 385 | int w = (int)size.width; | ||
| 386 | int h = (int)size.height; | ||
| 387 | |||
| 388 | // Make sure the width/height are oriented correctly | ||
| 389 | if (isLandscape != (w > h)) { | ||
| 390 | int tmp = w; | ||
| 391 | w = h; | ||
| 392 | h = tmp; | ||
| 393 | } | ||
| 394 | |||
| 395 | UIKit_AddDisplayMode(display, w, h, data.uiscreen, uimode, addRotation); | ||
| 396 | } | ||
| 397 | } | ||
| 398 | #endif | ||
| 399 | return true; | ||
| 400 | } | ||
| 401 | |||
| 402 | bool UIKit_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode) | ||
| 403 | { | ||
| 404 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 405 | @autoreleasepool { | ||
| 406 | SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal; | ||
| 407 | |||
| 408 | #ifndef SDL_PLATFORM_TVOS | ||
| 409 | SDL_UIKitDisplayModeData *modedata = (__bridge SDL_UIKitDisplayModeData *)mode->internal; | ||
| 410 | [data.uiscreen setCurrentMode:modedata.uiscreenmode]; | ||
| 411 | #endif | ||
| 412 | |||
| 413 | if (data.uiscreen == [UIScreen mainScreen]) { | ||
| 414 | /* [UIApplication setStatusBarOrientation:] no longer works reliably | ||
| 415 | * in recent iOS versions, so we can't rotate the screen when setting | ||
| 416 | * the display mode. */ | ||
| 417 | if (mode->w > mode->h) { | ||
| 418 | if (!UIKit_IsDisplayLandscape(data.uiscreen)) { | ||
| 419 | return SDL_SetError("Screen orientation does not match display mode size"); | ||
| 420 | } | ||
| 421 | } else if (mode->w < mode->h) { | ||
| 422 | if (UIKit_IsDisplayLandscape(data.uiscreen)) { | ||
| 423 | return SDL_SetError("Screen orientation does not match display mode size"); | ||
| 424 | } | ||
| 425 | } | ||
| 426 | } | ||
| 427 | } | ||
| 428 | #endif | ||
| 429 | return true; | ||
| 430 | } | ||
| 431 | |||
| 432 | bool UIKit_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect) | ||
| 433 | { | ||
| 434 | @autoreleasepool { | ||
| 435 | SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal; | ||
| 436 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 437 | CGRect frame = CGRectMake(0, 0, SDL_XR_SCREENWIDTH, SDL_XR_SCREENHEIGHT); | ||
| 438 | #else | ||
| 439 | CGRect frame = data.uiscreen.bounds; | ||
| 440 | #endif | ||
| 441 | |||
| 442 | /* the default function iterates displays to make a fake offset, | ||
| 443 | as if all the displays were side-by-side, which is fine for iOS. */ | ||
| 444 | if (!SDL_GetDisplayBounds(display->id, rect)) { | ||
| 445 | return false; | ||
| 446 | } | ||
| 447 | |||
| 448 | rect->x += (int)frame.origin.x; | ||
| 449 | rect->y += (int)frame.origin.y; | ||
| 450 | rect->w = (int)frame.size.width; | ||
| 451 | rect->h = (int)frame.size.height; | ||
| 452 | } | ||
| 453 | |||
| 454 | return true; | ||
| 455 | } | ||
| 456 | |||
| 457 | void UIKit_QuitModes(SDL_VideoDevice *_this) | ||
| 458 | { | ||
| 459 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 460 | [SDL_DisplayWatch stop]; | ||
| 461 | #endif | ||
| 462 | |||
| 463 | // Release Objective-C objects, so higher level doesn't free() them. | ||
| 464 | int i, j; | ||
| 465 | @autoreleasepool { | ||
| 466 | for (i = 0; i < _this->num_displays; i++) { | ||
| 467 | SDL_VideoDisplay *display = _this->displays[i]; | ||
| 468 | |||
| 469 | UIKit_FreeDisplayModeData(&display->desktop_mode); | ||
| 470 | for (j = 0; j < display->num_fullscreen_modes; j++) { | ||
| 471 | SDL_DisplayMode *mode = &display->fullscreen_modes[j]; | ||
| 472 | UIKit_FreeDisplayModeData(mode); | ||
| 473 | } | ||
| 474 | |||
| 475 | if (display->internal != NULL) { | ||
| 476 | CFRelease(display->internal); | ||
| 477 | display->internal = NULL; | ||
| 478 | } | ||
| 479 | } | ||
| 480 | } | ||
| 481 | } | ||
| 482 | |||
| 483 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 484 | void SDL_OnApplicationDidChangeStatusBarOrientation(void) | ||
| 485 | { | ||
| 486 | BOOL isLandscape = UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation); | ||
| 487 | SDL_VideoDisplay *display = SDL_GetVideoDisplay(SDL_GetPrimaryDisplay()); | ||
| 488 | |||
| 489 | if (display) { | ||
| 490 | SDL_DisplayMode *mode = &display->desktop_mode; | ||
| 491 | SDL_DisplayOrientation orientation = SDL_ORIENTATION_UNKNOWN; | ||
| 492 | int i; | ||
| 493 | |||
| 494 | /* The desktop display mode should be kept in sync with the screen | ||
| 495 | * orientation so that updating a window's fullscreen state to | ||
| 496 | * fullscreen desktop keeps the window dimensions in the | ||
| 497 | * correct orientation. */ | ||
| 498 | if (isLandscape != (mode->w > mode->h)) { | ||
| 499 | SDL_DisplayMode new_mode; | ||
| 500 | SDL_copyp(&new_mode, mode); | ||
| 501 | new_mode.w = mode->h; | ||
| 502 | new_mode.h = mode->w; | ||
| 503 | |||
| 504 | // Make sure we don't free the current display mode data | ||
| 505 | mode->internal = NULL; | ||
| 506 | |||
| 507 | SDL_SetDesktopDisplayMode(display, &new_mode); | ||
| 508 | } | ||
| 509 | |||
| 510 | // Same deal with the fullscreen modes | ||
| 511 | for (i = 0; i < display->num_fullscreen_modes; ++i) { | ||
| 512 | mode = &display->fullscreen_modes[i]; | ||
| 513 | if (isLandscape != (mode->w > mode->h)) { | ||
| 514 | int height = mode->w; | ||
| 515 | mode->w = mode->h; | ||
| 516 | mode->h = height; | ||
| 517 | } | ||
| 518 | } | ||
| 519 | |||
| 520 | switch ([UIApplication sharedApplication].statusBarOrientation) { | ||
| 521 | case UIInterfaceOrientationPortrait: | ||
| 522 | orientation = SDL_ORIENTATION_PORTRAIT; | ||
| 523 | break; | ||
| 524 | case UIInterfaceOrientationPortraitUpsideDown: | ||
| 525 | orientation = SDL_ORIENTATION_PORTRAIT_FLIPPED; | ||
| 526 | break; | ||
| 527 | case UIInterfaceOrientationLandscapeLeft: | ||
| 528 | // Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046 | ||
| 529 | orientation = SDL_ORIENTATION_LANDSCAPE_FLIPPED; | ||
| 530 | break; | ||
| 531 | case UIInterfaceOrientationLandscapeRight: | ||
| 532 | // Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046 | ||
| 533 | orientation = SDL_ORIENTATION_LANDSCAPE; | ||
| 534 | break; | ||
| 535 | default: | ||
| 536 | break; | ||
| 537 | } | ||
| 538 | SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_ORIENTATION, orientation, 0); | ||
| 539 | } | ||
| 540 | } | ||
| 541 | #endif // !SDL_PLATFORM_TVOS | ||
| 542 | |||
| 543 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.h new file mode 100644 index 0000000..cc259f7 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.h | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #ifndef SDL_uikitopengles_ | ||
| 22 | #define SDL_uikitopengles_ | ||
| 23 | |||
| 24 | #if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2) | ||
| 25 | |||
| 26 | #include "../SDL_sysvideo.h" | ||
| 27 | |||
| 28 | extern bool UIKit_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, | ||
| 29 | SDL_GLContext context); | ||
| 30 | extern bool UIKit_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 31 | extern SDL_GLContext UIKit_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 32 | extern bool UIKit_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context); | ||
| 33 | extern SDL_FunctionPointer UIKit_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc); | ||
| 34 | extern bool UIKit_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path); | ||
| 35 | |||
| 36 | extern void UIKit_GL_RestoreCurrentContext(void); | ||
| 37 | |||
| 38 | #endif // SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2 | ||
| 39 | |||
| 40 | #endif // SDL_uikitopengles_ | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.m new file mode 100644 index 0000000..a73588b --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.m | |||
| @@ -0,0 +1,221 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #if defined(SDL_VIDEO_DRIVER_UIKIT) && (defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)) | ||
| 24 | |||
| 25 | #include "SDL_uikitopengles.h" | ||
| 26 | #import "SDL_uikitopenglview.h" | ||
| 27 | #include "SDL_uikitmodes.h" | ||
| 28 | #include "SDL_uikitwindow.h" | ||
| 29 | #include "SDL_uikitevents.h" | ||
| 30 | #include "../SDL_sysvideo.h" | ||
| 31 | #include "../../events/SDL_keyboard_c.h" | ||
| 32 | #include "../../events/SDL_mouse_c.h" | ||
| 33 | #include "../../power/uikit/SDL_syspower.h" | ||
| 34 | #include <dlfcn.h> | ||
| 35 | |||
| 36 | @interface SDLEAGLContext : EAGLContext | ||
| 37 | |||
| 38 | // The OpenGL ES context owns a view / drawable. | ||
| 39 | @property(nonatomic, strong) SDL_uikitopenglview *sdlView; | ||
| 40 | |||
| 41 | @end | ||
| 42 | |||
| 43 | @implementation SDLEAGLContext | ||
| 44 | |||
| 45 | - (void)dealloc | ||
| 46 | { | ||
| 47 | /* When the context is deallocated, its view should be removed from any | ||
| 48 | * SDL window that it's attached to. */ | ||
| 49 | [self.sdlView setSDLWindow:NULL]; | ||
| 50 | } | ||
| 51 | |||
| 52 | @end | ||
| 53 | |||
| 54 | SDL_FunctionPointer UIKit_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc) | ||
| 55 | { | ||
| 56 | /* Look through all SO's for the proc symbol. Here's why: | ||
| 57 | * -Looking for the path to the OpenGL Library seems not to work in the iOS Simulator. | ||
| 58 | * -We don't know that the path won't change in the future. */ | ||
| 59 | return dlsym(RTLD_DEFAULT, proc); | ||
| 60 | } | ||
| 61 | |||
| 62 | /* | ||
| 63 | note that SDL_GL_DestroyContext makes it current without passing the window | ||
| 64 | */ | ||
| 65 | bool UIKit_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context) | ||
| 66 | { | ||
| 67 | @autoreleasepool { | ||
| 68 | SDLEAGLContext *eaglcontext = (__bridge SDLEAGLContext *)context; | ||
| 69 | |||
| 70 | if (![EAGLContext setCurrentContext:eaglcontext]) { | ||
| 71 | return SDL_SetError("Could not make EAGL context current"); | ||
| 72 | } | ||
| 73 | |||
| 74 | if (eaglcontext) { | ||
| 75 | [eaglcontext.sdlView setSDLWindow:window]; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | return true; | ||
| 80 | } | ||
| 81 | |||
| 82 | bool UIKit_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path) | ||
| 83 | { | ||
| 84 | /* We shouldn't pass a path to this function, since we've already loaded the | ||
| 85 | * library. */ | ||
| 86 | if (path != NULL) { | ||
| 87 | return SDL_SetError("iOS GL Load Library just here for compatibility"); | ||
| 88 | } | ||
| 89 | return true; | ||
| 90 | } | ||
| 91 | |||
| 92 | bool UIKit_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 93 | { | ||
| 94 | @autoreleasepool { | ||
| 95 | SDLEAGLContext *context = (__bridge SDLEAGLContext *)SDL_GL_GetCurrentContext(); | ||
| 96 | |||
| 97 | #ifdef SDL_POWER_UIKIT | ||
| 98 | // Check once a frame to see if we should turn off the battery monitor. | ||
| 99 | SDL_UIKit_UpdateBatteryMonitoring(); | ||
| 100 | #endif | ||
| 101 | |||
| 102 | [context.sdlView swapBuffers]; | ||
| 103 | |||
| 104 | /* You need to pump events in order for the OS to make changes visible. | ||
| 105 | * We don't pump events here because we don't want iOS application events | ||
| 106 | * (low memory, terminate, etc.) to happen inside low level rendering. */ | ||
| 107 | } | ||
| 108 | return true; | ||
| 109 | } | ||
| 110 | |||
| 111 | SDL_GLContext UIKit_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 112 | { | ||
| 113 | @autoreleasepool { | ||
| 114 | SDLEAGLContext *context = nil; | ||
| 115 | SDL_uikitopenglview *view; | ||
| 116 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 117 | CGRect frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen); | ||
| 118 | EAGLSharegroup *sharegroup = nil; | ||
| 119 | CGFloat scale = 1.0; | ||
| 120 | int samples = 0; | ||
| 121 | int major = _this->gl_config.major_version; | ||
| 122 | int minor = _this->gl_config.minor_version; | ||
| 123 | |||
| 124 | /* The EAGLRenderingAPI enum values currently map 1:1 to major GLES | ||
| 125 | * versions. */ | ||
| 126 | EAGLRenderingAPI api = major; | ||
| 127 | |||
| 128 | // iOS currently doesn't support GLES >3.0. | ||
| 129 | if (major > 3 || (major == 3 && minor > 0)) { | ||
| 130 | SDL_SetError("OpenGL ES %d.%d context could not be created", major, minor); | ||
| 131 | return NULL; | ||
| 132 | } | ||
| 133 | |||
| 134 | if (_this->gl_config.multisamplebuffers > 0) { | ||
| 135 | samples = _this->gl_config.multisamplesamples; | ||
| 136 | } | ||
| 137 | |||
| 138 | if (_this->gl_config.share_with_current_context) { | ||
| 139 | EAGLContext *currContext = (__bridge EAGLContext *)SDL_GL_GetCurrentContext(); | ||
| 140 | sharegroup = currContext.sharegroup; | ||
| 141 | } | ||
| 142 | |||
| 143 | if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { | ||
| 144 | /* Set the scale to the natural scale factor of the screen - the | ||
| 145 | * backing dimensions of the OpenGL view will match the pixel | ||
| 146 | * dimensions of the screen rather than the dimensions in points. */ | ||
| 147 | scale = data.uiwindow.screen.nativeScale; | ||
| 148 | } | ||
| 149 | |||
| 150 | context = [[SDLEAGLContext alloc] initWithAPI:api sharegroup:sharegroup]; | ||
| 151 | if (!context) { | ||
| 152 | SDL_SetError("OpenGL ES %d context could not be created", _this->gl_config.major_version); | ||
| 153 | return NULL; | ||
| 154 | } | ||
| 155 | |||
| 156 | // construct our view, passing in SDL's OpenGL configuration data | ||
| 157 | view = [[SDL_uikitopenglview alloc] initWithFrame:frame | ||
| 158 | scale:scale | ||
| 159 | retainBacking:_this->gl_config.retained_backing | ||
| 160 | rBits:_this->gl_config.red_size | ||
| 161 | gBits:_this->gl_config.green_size | ||
| 162 | bBits:_this->gl_config.blue_size | ||
| 163 | aBits:_this->gl_config.alpha_size | ||
| 164 | depthBits:_this->gl_config.depth_size | ||
| 165 | stencilBits:_this->gl_config.stencil_size | ||
| 166 | sRGB:_this->gl_config.framebuffer_srgb_capable | ||
| 167 | multisamples:samples | ||
| 168 | context:context]; | ||
| 169 | |||
| 170 | if (!view) { | ||
| 171 | return NULL; | ||
| 172 | } | ||
| 173 | |||
| 174 | SDL_PropertiesID props = SDL_GetWindowProperties(window); | ||
| 175 | SDL_SetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_OPENGL_FRAMEBUFFER_NUMBER, view.drawableFramebuffer); | ||
| 176 | SDL_SetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_OPENGL_RENDERBUFFER_NUMBER, view.drawableRenderbuffer); | ||
| 177 | SDL_SetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_OPENGL_RESOLVE_FRAMEBUFFER_NUMBER, view.msaaResolveFramebuffer); | ||
| 178 | |||
| 179 | // The context owns the view / drawable. | ||
| 180 | context.sdlView = view; | ||
| 181 | |||
| 182 | if (!UIKit_GL_MakeCurrent(_this, window, (__bridge SDL_GLContext)context)) { | ||
| 183 | UIKit_GL_DestroyContext(_this, (SDL_GLContext)CFBridgingRetain(context)); | ||
| 184 | return NULL; | ||
| 185 | } | ||
| 186 | |||
| 187 | /* We return a +1'd context. The window's internal owns the view (via | ||
| 188 | * MakeCurrent.) */ | ||
| 189 | return (SDL_GLContext)CFBridgingRetain(context); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | bool UIKit_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context) | ||
| 194 | { | ||
| 195 | @autoreleasepool { | ||
| 196 | /* The context was retained in SDL_GL_CreateContext, so we release it | ||
| 197 | * here. The context's view will be detached from its window when the | ||
| 198 | * context is deallocated. */ | ||
| 199 | CFRelease(context); | ||
| 200 | } | ||
| 201 | return true; | ||
| 202 | } | ||
| 203 | |||
| 204 | void UIKit_GL_RestoreCurrentContext(void) | ||
| 205 | { | ||
| 206 | @autoreleasepool { | ||
| 207 | /* Some iOS system functionality (such as Dictation on the on-screen | ||
| 208 | keyboard) uses its own OpenGL ES context but doesn't restore the | ||
| 209 | previous one when it's done. This is a workaround to make sure the | ||
| 210 | expected SDL-created OpenGL ES context is active after the OS is | ||
| 211 | finished running its own code for the frame. If this isn't done, the | ||
| 212 | app may crash or have other nasty symptoms when Dictation is used. | ||
| 213 | */ | ||
| 214 | EAGLContext *context = (__bridge EAGLContext *)SDL_GL_GetCurrentContext(); | ||
| 215 | if (context != NULL && [EAGLContext currentContext] != context) { | ||
| 216 | [EAGLContext setCurrentContext:context]; | ||
| 217 | } | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.h new file mode 100644 index 0000000..3286a16 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.h | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | #if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2) | ||
| 23 | |||
| 24 | #import <UIKit/UIKit.h> | ||
| 25 | #import <OpenGLES/EAGL.h> | ||
| 26 | #import <OpenGLES/ES3/gl.h> | ||
| 27 | |||
| 28 | #import "SDL_uikitview.h" | ||
| 29 | #include "SDL_uikitvideo.h" | ||
| 30 | |||
| 31 | @interface SDL_uikitopenglview : SDL_uikitview | ||
| 32 | |||
| 33 | - (instancetype)initWithFrame:(CGRect)frame | ||
| 34 | scale:(CGFloat)scale | ||
| 35 | retainBacking:(BOOL)retained | ||
| 36 | rBits:(int)rBits | ||
| 37 | gBits:(int)gBits | ||
| 38 | bBits:(int)bBits | ||
| 39 | aBits:(int)aBits | ||
| 40 | depthBits:(int)depthBits | ||
| 41 | stencilBits:(int)stencilBits | ||
| 42 | sRGB:(BOOL)sRGB | ||
| 43 | multisamples:(int)multisamples | ||
| 44 | context:(EAGLContext *)glcontext; | ||
| 45 | |||
| 46 | @property(nonatomic, readonly, weak) EAGLContext *context; | ||
| 47 | |||
| 48 | // The width and height of the drawable in pixels (as opposed to points.) | ||
| 49 | @property(nonatomic, readonly) int backingWidth; | ||
| 50 | @property(nonatomic, readonly) int backingHeight; | ||
| 51 | |||
| 52 | @property(nonatomic, readonly) GLuint drawableRenderbuffer; | ||
| 53 | @property(nonatomic, readonly) GLuint drawableFramebuffer; | ||
| 54 | @property(nonatomic, readonly) GLuint msaaResolveFramebuffer; | ||
| 55 | |||
| 56 | - (void)swapBuffers; | ||
| 57 | |||
| 58 | - (void)updateFrame; | ||
| 59 | |||
| 60 | @end | ||
| 61 | |||
| 62 | #endif // SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2 | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.m new file mode 100644 index 0000000..71d167f --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.m | |||
| @@ -0,0 +1,377 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #if defined(SDL_VIDEO_DRIVER_UIKIT) && (defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)) | ||
| 24 | |||
| 25 | #include <OpenGLES/EAGLDrawable.h> | ||
| 26 | #include <OpenGLES/ES2/glext.h> | ||
| 27 | #import "SDL_uikitopenglview.h" | ||
| 28 | #include "SDL_uikitwindow.h" | ||
| 29 | |||
| 30 | @implementation SDL_uikitopenglview | ||
| 31 | { | ||
| 32 | // The renderbuffer and framebuffer used to render to this layer. | ||
| 33 | GLuint viewRenderbuffer, viewFramebuffer; | ||
| 34 | |||
| 35 | // The depth buffer that is attached to viewFramebuffer, if it exists. | ||
| 36 | GLuint depthRenderbuffer; | ||
| 37 | |||
| 38 | GLenum colorBufferFormat; | ||
| 39 | |||
| 40 | // format of depthRenderbuffer | ||
| 41 | GLenum depthBufferFormat; | ||
| 42 | |||
| 43 | // The framebuffer and renderbuffer used for rendering with MSAA. | ||
| 44 | GLuint msaaFramebuffer, msaaRenderbuffer; | ||
| 45 | |||
| 46 | // The number of MSAA samples. | ||
| 47 | int samples; | ||
| 48 | |||
| 49 | BOOL retainedBacking; | ||
| 50 | } | ||
| 51 | |||
| 52 | @synthesize context; | ||
| 53 | @synthesize backingWidth; | ||
| 54 | @synthesize backingHeight; | ||
| 55 | |||
| 56 | + (Class)layerClass | ||
| 57 | { | ||
| 58 | return [CAEAGLLayer class]; | ||
| 59 | } | ||
| 60 | |||
| 61 | - (instancetype)initWithFrame:(CGRect)frame | ||
| 62 | scale:(CGFloat)scale | ||
| 63 | retainBacking:(BOOL)retained | ||
| 64 | rBits:(int)rBits | ||
| 65 | gBits:(int)gBits | ||
| 66 | bBits:(int)bBits | ||
| 67 | aBits:(int)aBits | ||
| 68 | depthBits:(int)depthBits | ||
| 69 | stencilBits:(int)stencilBits | ||
| 70 | sRGB:(BOOL)sRGB | ||
| 71 | multisamples:(int)multisamples | ||
| 72 | context:(EAGLContext *)glcontext | ||
| 73 | { | ||
| 74 | if ((self = [super initWithFrame:frame])) { | ||
| 75 | const BOOL useStencilBuffer = (stencilBits != 0); | ||
| 76 | const BOOL useDepthBuffer = (depthBits != 0); | ||
| 77 | NSString *colorFormat = nil; | ||
| 78 | |||
| 79 | context = glcontext; | ||
| 80 | samples = multisamples; | ||
| 81 | retainedBacking = retained; | ||
| 82 | |||
| 83 | if (!context || ![EAGLContext setCurrentContext:context]) { | ||
| 84 | SDL_SetError("Could not create OpenGL ES drawable (could not make context current)"); | ||
| 85 | return nil; | ||
| 86 | } | ||
| 87 | |||
| 88 | if (samples > 0) { | ||
| 89 | GLint maxsamples = 0; | ||
| 90 | glGetIntegerv(GL_MAX_SAMPLES, &maxsamples); | ||
| 91 | |||
| 92 | // Clamp the samples to the max supported count. | ||
| 93 | samples = SDL_min(samples, maxsamples); | ||
| 94 | } | ||
| 95 | |||
| 96 | if (sRGB) { | ||
| 97 | colorFormat = kEAGLColorFormatSRGBA8; | ||
| 98 | colorBufferFormat = GL_SRGB8_ALPHA8; | ||
| 99 | } else if (rBits >= 8 || gBits >= 8 || bBits >= 8 || aBits > 0) { | ||
| 100 | // if user specifically requests rbg888 or some color format higher than 16bpp | ||
| 101 | colorFormat = kEAGLColorFormatRGBA8; | ||
| 102 | colorBufferFormat = GL_RGBA8; | ||
| 103 | } else { | ||
| 104 | // default case (potentially faster) | ||
| 105 | colorFormat = kEAGLColorFormatRGB565; | ||
| 106 | colorBufferFormat = GL_RGB565; | ||
| 107 | } | ||
| 108 | |||
| 109 | CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; | ||
| 110 | |||
| 111 | eaglLayer.opaque = YES; | ||
| 112 | eaglLayer.drawableProperties = @{ | ||
| 113 | kEAGLDrawablePropertyRetainedBacking : @(retained), | ||
| 114 | kEAGLDrawablePropertyColorFormat : colorFormat | ||
| 115 | }; | ||
| 116 | |||
| 117 | // Set the appropriate scale (for retina display support) | ||
| 118 | self.contentScaleFactor = scale; | ||
| 119 | |||
| 120 | // Create the color Renderbuffer Object | ||
| 121 | glGenRenderbuffers(1, &viewRenderbuffer); | ||
| 122 | glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer); | ||
| 123 | |||
| 124 | if (![context renderbufferStorage:GL_RENDERBUFFER fromDrawable:eaglLayer]) { | ||
| 125 | SDL_SetError("Failed to create OpenGL ES drawable"); | ||
| 126 | return nil; | ||
| 127 | } | ||
| 128 | |||
| 129 | // Create the Framebuffer Object | ||
| 130 | glGenFramebuffers(1, &viewFramebuffer); | ||
| 131 | glBindFramebuffer(GL_FRAMEBUFFER, viewFramebuffer); | ||
| 132 | |||
| 133 | // attach the color renderbuffer to the FBO | ||
| 134 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, viewRenderbuffer); | ||
| 135 | |||
| 136 | glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth); | ||
| 137 | glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight); | ||
| 138 | |||
| 139 | if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { | ||
| 140 | SDL_SetError("Failed creating OpenGL ES framebuffer"); | ||
| 141 | return nil; | ||
| 142 | } | ||
| 143 | |||
| 144 | /* When MSAA is used we'll use a separate framebuffer for rendering to, | ||
| 145 | * since we'll need to do an explicit MSAA resolve before presenting. */ | ||
| 146 | if (samples > 0) { | ||
| 147 | glGenFramebuffers(1, &msaaFramebuffer); | ||
| 148 | glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebuffer); | ||
| 149 | |||
| 150 | glGenRenderbuffers(1, &msaaRenderbuffer); | ||
| 151 | glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer); | ||
| 152 | |||
| 153 | glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, colorBufferFormat, backingWidth, backingHeight); | ||
| 154 | |||
| 155 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaRenderbuffer); | ||
| 156 | } | ||
| 157 | |||
| 158 | if (useDepthBuffer || useStencilBuffer) { | ||
| 159 | if (useStencilBuffer) { | ||
| 160 | // Apparently you need to pack stencil and depth into one buffer. | ||
| 161 | depthBufferFormat = GL_DEPTH24_STENCIL8_OES; | ||
| 162 | } else if (useDepthBuffer) { | ||
| 163 | /* iOS only uses 32-bit float (exposed as fixed point 24-bit) | ||
| 164 | * depth buffers. */ | ||
| 165 | depthBufferFormat = GL_DEPTH_COMPONENT24_OES; | ||
| 166 | } | ||
| 167 | |||
| 168 | glGenRenderbuffers(1, &depthRenderbuffer); | ||
| 169 | glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer); | ||
| 170 | |||
| 171 | if (samples > 0) { | ||
| 172 | glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, depthBufferFormat, backingWidth, backingHeight); | ||
| 173 | } else { | ||
| 174 | glRenderbufferStorage(GL_RENDERBUFFER, depthBufferFormat, backingWidth, backingHeight); | ||
| 175 | } | ||
| 176 | |||
| 177 | if (useDepthBuffer) { | ||
| 178 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer); | ||
| 179 | } | ||
| 180 | if (useStencilBuffer) { | ||
| 181 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer); | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { | ||
| 186 | SDL_SetError("Failed creating OpenGL ES framebuffer"); | ||
| 187 | return nil; | ||
| 188 | } | ||
| 189 | |||
| 190 | glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer); | ||
| 191 | |||
| 192 | [self setDebugLabels]; | ||
| 193 | } | ||
| 194 | |||
| 195 | return self; | ||
| 196 | } | ||
| 197 | |||
| 198 | - (GLuint)drawableRenderbuffer | ||
| 199 | { | ||
| 200 | return viewRenderbuffer; | ||
| 201 | } | ||
| 202 | |||
| 203 | - (GLuint)drawableFramebuffer | ||
| 204 | { | ||
| 205 | // When MSAA is used, the MSAA draw framebuffer is used for drawing. | ||
| 206 | if (msaaFramebuffer) { | ||
| 207 | return msaaFramebuffer; | ||
| 208 | } else { | ||
| 209 | return viewFramebuffer; | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | - (GLuint)msaaResolveFramebuffer | ||
| 214 | { | ||
| 215 | /* When MSAA is used, the MSAA draw framebuffer is used for drawing and the | ||
| 216 | * view framebuffer is used as a MSAA resolve framebuffer. */ | ||
| 217 | if (msaaFramebuffer) { | ||
| 218 | return viewFramebuffer; | ||
| 219 | } else { | ||
| 220 | return 0; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | - (void)updateFrame | ||
| 225 | { | ||
| 226 | GLint prevRenderbuffer = 0; | ||
| 227 | glGetIntegerv(GL_RENDERBUFFER_BINDING, &prevRenderbuffer); | ||
| 228 | |||
| 229 | glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer); | ||
| 230 | [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer]; | ||
| 231 | |||
| 232 | glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth); | ||
| 233 | glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight); | ||
| 234 | |||
| 235 | if (msaaRenderbuffer != 0) { | ||
| 236 | glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer); | ||
| 237 | glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, colorBufferFormat, backingWidth, backingHeight); | ||
| 238 | } | ||
| 239 | |||
| 240 | if (depthRenderbuffer != 0) { | ||
| 241 | glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer); | ||
| 242 | |||
| 243 | if (samples > 0) { | ||
| 244 | glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, depthBufferFormat, backingWidth, backingHeight); | ||
| 245 | } else { | ||
| 246 | glRenderbufferStorage(GL_RENDERBUFFER, depthBufferFormat, backingWidth, backingHeight); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | glBindRenderbuffer(GL_RENDERBUFFER, prevRenderbuffer); | ||
| 251 | } | ||
| 252 | |||
| 253 | - (void)setDebugLabels | ||
| 254 | { | ||
| 255 | if (viewFramebuffer != 0) { | ||
| 256 | glLabelObjectEXT(GL_FRAMEBUFFER, viewFramebuffer, 0, "context FBO"); | ||
| 257 | } | ||
| 258 | |||
| 259 | if (viewRenderbuffer != 0) { | ||
| 260 | glLabelObjectEXT(GL_RENDERBUFFER, viewRenderbuffer, 0, "context color buffer"); | ||
| 261 | } | ||
| 262 | |||
| 263 | if (depthRenderbuffer != 0) { | ||
| 264 | if (depthBufferFormat == GL_DEPTH24_STENCIL8_OES) { | ||
| 265 | glLabelObjectEXT(GL_RENDERBUFFER, depthRenderbuffer, 0, "context depth-stencil buffer"); | ||
| 266 | } else { | ||
| 267 | glLabelObjectEXT(GL_RENDERBUFFER, depthRenderbuffer, 0, "context depth buffer"); | ||
| 268 | } | ||
| 269 | } | ||
| 270 | |||
| 271 | if (msaaFramebuffer != 0) { | ||
| 272 | glLabelObjectEXT(GL_FRAMEBUFFER, msaaFramebuffer, 0, "context MSAA FBO"); | ||
| 273 | } | ||
| 274 | |||
| 275 | if (msaaRenderbuffer != 0) { | ||
| 276 | glLabelObjectEXT(GL_RENDERBUFFER, msaaRenderbuffer, 0, "context MSAA renderbuffer"); | ||
| 277 | } | ||
| 278 | } | ||
| 279 | |||
| 280 | - (void)swapBuffers | ||
| 281 | { | ||
| 282 | if (msaaFramebuffer) { | ||
| 283 | const GLenum attachments[] = { GL_COLOR_ATTACHMENT0 }; | ||
| 284 | |||
| 285 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, viewFramebuffer); | ||
| 286 | |||
| 287 | /* OpenGL ES 3+ provides explicit MSAA resolves via glBlitFramebuffer. | ||
| 288 | * In OpenGL ES 1 and 2, MSAA resolves must be done via an extension. */ | ||
| 289 | if (context.API >= kEAGLRenderingAPIOpenGLES3) { | ||
| 290 | int w = backingWidth; | ||
| 291 | int h = backingHeight; | ||
| 292 | glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); | ||
| 293 | |||
| 294 | if (!retainedBacking) { | ||
| 295 | // Discard the contents of the MSAA drawable color buffer. | ||
| 296 | glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, attachments); | ||
| 297 | } | ||
| 298 | } else { | ||
| 299 | glResolveMultisampleFramebufferAPPLE(); | ||
| 300 | |||
| 301 | if (!retainedBacking) { | ||
| 302 | glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER, 1, attachments); | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | /* We assume the "drawable framebuffer" (MSAA draw framebuffer) was | ||
| 307 | * previously bound... */ | ||
| 308 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, msaaFramebuffer); | ||
| 309 | } | ||
| 310 | |||
| 311 | /* viewRenderbuffer should always be bound here. Code that binds something | ||
| 312 | * else is responsible for rebinding viewRenderbuffer, to reduce duplicate | ||
| 313 | * state changes. */ | ||
| 314 | [context presentRenderbuffer:GL_RENDERBUFFER]; | ||
| 315 | } | ||
| 316 | |||
| 317 | - (void)layoutSubviews | ||
| 318 | { | ||
| 319 | [super layoutSubviews]; | ||
| 320 | |||
| 321 | int width = (int)(self.bounds.size.width * self.contentScaleFactor); | ||
| 322 | int height = (int)(self.bounds.size.height * self.contentScaleFactor); | ||
| 323 | |||
| 324 | // Update the color and depth buffer storage if the layer size has changed. | ||
| 325 | if (width != backingWidth || height != backingHeight) { | ||
| 326 | EAGLContext *prevContext = [EAGLContext currentContext]; | ||
| 327 | if (prevContext != context) { | ||
| 328 | [EAGLContext setCurrentContext:context]; | ||
| 329 | } | ||
| 330 | |||
| 331 | [self updateFrame]; | ||
| 332 | |||
| 333 | if (prevContext != context) { | ||
| 334 | [EAGLContext setCurrentContext:prevContext]; | ||
| 335 | } | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | - (void)destroyFramebuffer | ||
| 340 | { | ||
| 341 | if (viewFramebuffer != 0) { | ||
| 342 | glDeleteFramebuffers(1, &viewFramebuffer); | ||
| 343 | viewFramebuffer = 0; | ||
| 344 | } | ||
| 345 | |||
| 346 | if (viewRenderbuffer != 0) { | ||
| 347 | glDeleteRenderbuffers(1, &viewRenderbuffer); | ||
| 348 | viewRenderbuffer = 0; | ||
| 349 | } | ||
| 350 | |||
| 351 | if (depthRenderbuffer != 0) { | ||
| 352 | glDeleteRenderbuffers(1, &depthRenderbuffer); | ||
| 353 | depthRenderbuffer = 0; | ||
| 354 | } | ||
| 355 | |||
| 356 | if (msaaFramebuffer != 0) { | ||
| 357 | glDeleteFramebuffers(1, &msaaFramebuffer); | ||
| 358 | msaaFramebuffer = 0; | ||
| 359 | } | ||
| 360 | |||
| 361 | if (msaaRenderbuffer != 0) { | ||
| 362 | glDeleteRenderbuffers(1, &msaaRenderbuffer); | ||
| 363 | msaaRenderbuffer = 0; | ||
| 364 | } | ||
| 365 | } | ||
| 366 | |||
| 367 | - (void)dealloc | ||
| 368 | { | ||
| 369 | if (context && context == [EAGLContext currentContext]) { | ||
| 370 | [self destroyFramebuffer]; | ||
| 371 | [EAGLContext setCurrentContext:nil]; | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | @end | ||
| 376 | |||
| 377 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.h new file mode 100644 index 0000000..1e75589 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.h | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | #ifndef SDL_uikitpen_h_ | ||
| 23 | #define SDL_uikitpen_h_ | ||
| 24 | |||
| 25 | #include "SDL_uikitvideo.h" | ||
| 26 | #include "SDL_uikitwindow.h" | ||
| 27 | |||
| 28 | extern bool UIKit_InitPen(SDL_VideoDevice *_this); | ||
| 29 | extern void UIKit_HandlePenMotion(SDL_uikitview *view, UITouch *pencil); | ||
| 30 | extern void UIKit_HandlePenPress(SDL_uikitview *view, UITouch *pencil); | ||
| 31 | extern void UIKit_HandlePenRelease(SDL_uikitview *view, UITouch *pencil); | ||
| 32 | |||
| 33 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 34 | extern void UIKit_HandlePenHover(SDL_uikitview *view, UIHoverGestureRecognizer *recognizer) API_AVAILABLE(ios(13.0)); | ||
| 35 | #endif | ||
| 36 | |||
| 37 | extern void UIKit_QuitPen(SDL_VideoDevice *_this); | ||
| 38 | |||
| 39 | #endif // SDL_uikitpen_h_ | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.m new file mode 100644 index 0000000..5209681 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.m | |||
| @@ -0,0 +1,214 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_VIDEO_DRIVER_UIKIT | ||
| 24 | |||
| 25 | #include "SDL_uikitevents.h" | ||
| 26 | #include "SDL_uikitpen.h" | ||
| 27 | #include "SDL_uikitwindow.h" | ||
| 28 | |||
| 29 | #include "../../events/SDL_pen_c.h" | ||
| 30 | |||
| 31 | // Fix build errors when using an older SDK by defining these selectors | ||
| 32 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 33 | |||
| 34 | @interface UITouch (SDL) | ||
| 35 | #if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 170500) | ||
| 36 | @property (nonatomic, readonly) CGFloat rollAngle; | ||
| 37 | #endif | ||
| 38 | @end | ||
| 39 | |||
| 40 | @interface UIHoverGestureRecognizer (SDL) | ||
| 41 | #if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 160100) | ||
| 42 | @property (nonatomic, readonly) CGFloat zOffset; | ||
| 43 | #endif | ||
| 44 | #if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 160400) | ||
| 45 | - (CGFloat) azimuthAngleInView:(UIView *) view; | ||
| 46 | |||
| 47 | @property (nonatomic, readonly) CGFloat altitudeAngle; | ||
| 48 | #endif | ||
| 49 | #if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 170500) | ||
| 50 | @property (nonatomic, readonly) CGFloat rollAngle; | ||
| 51 | #endif | ||
| 52 | @end | ||
| 53 | |||
| 54 | #endif // !SDL_PLATFORM_TVOS | ||
| 55 | |||
| 56 | static SDL_PenID apple_pencil_id = 0; | ||
| 57 | |||
| 58 | bool UIKit_InitPen(SDL_VideoDevice *_this) | ||
| 59 | { | ||
| 60 | return true; | ||
| 61 | } | ||
| 62 | |||
| 63 | // we only have one Apple Pencil at a time, and it must be paired to the iOS device. | ||
| 64 | // We only know about its existence when it first sends an event, so add an single SDL pen | ||
| 65 | // device here if we haven't already. | ||
| 66 | static SDL_PenID UIKit_AddPenIfNecesary() | ||
| 67 | { | ||
| 68 | if (!apple_pencil_id) { | ||
| 69 | SDL_PenInfo info; | ||
| 70 | SDL_zero(info); | ||
| 71 | info.capabilities = SDL_PEN_CAPABILITY_PRESSURE | SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT; | ||
| 72 | info.max_tilt = 90.0f; | ||
| 73 | info.num_buttons = 0; | ||
| 74 | info.subtype = SDL_PEN_TYPE_PENCIL; | ||
| 75 | |||
| 76 | if (@available(iOS 17.5, *)) { // need rollAngle method. | ||
| 77 | info.capabilities |= SDL_PEN_CAPABILITY_ROTATION; | ||
| 78 | } | ||
| 79 | |||
| 80 | if (@available(ios 16.1, *)) { // need zOffset method. | ||
| 81 | info.capabilities |= SDL_PEN_CAPABILITY_DISTANCE; | ||
| 82 | } | ||
| 83 | |||
| 84 | // Apple Pencil and iOS can report when the pencil is being "squeezed" but it's a boolean thing, | ||
| 85 | // so we can't use it for tangential pressure. | ||
| 86 | |||
| 87 | // There's only ever one Apple Pencil at most, so we just pass a non-zero value for the handle. | ||
| 88 | apple_pencil_id = SDL_AddPenDevice(0, "Apple Pencil", &info, (void *) (size_t) 0x1); | ||
| 89 | } | ||
| 90 | |||
| 91 | return apple_pencil_id; | ||
| 92 | } | ||
| 93 | |||
| 94 | static void UIKit_HandlePenAxes(SDL_Window *window, NSTimeInterval nstimestamp, float zOffset, const CGPoint *point, float force, | ||
| 95 | float maximumPossibleForce, float azimuthAngleInView, float altitudeAngle, float rollAngle) | ||
| 96 | { | ||
| 97 | const SDL_PenID penId = UIKit_AddPenIfNecesary(); | ||
| 98 | if (penId) { | ||
| 99 | const Uint64 timestamp = UIKit_GetEventTimestamp(nstimestamp); | ||
| 100 | const float radians_to_degrees = 180.0f / SDL_PI_F; | ||
| 101 | |||
| 102 | // Normalize force to 0.0f ... 1.0f range. | ||
| 103 | const float pressure = force / maximumPossibleForce; | ||
| 104 | |||
| 105 | // azimuthAngleInView is in radians, with 0 being the pen's back end pointing due east on the screen when the | ||
| 106 | // tip is touching the screen, and negative when heading north from there, positive to the south. | ||
| 107 | // So convert to degrees, 0 being due east, etc. | ||
| 108 | const float azimuth_angle = azimuthAngleInView * radians_to_degrees; | ||
| 109 | |||
| 110 | // altitudeAngle is in radians, with 0 being the pen laying flat on (parallel to) the device | ||
| 111 | // screen and PI/2 being it pointing straight up from (perpendicular to) the device screen. | ||
| 112 | // So convert to degrees, 0 being flat and 90 being straight up. | ||
| 113 | const float altitude_angle = altitudeAngle * radians_to_degrees; | ||
| 114 | |||
| 115 | // the azimuth_angle goes from -180 to 180 (with abs(angle) moving from 180 to 0, left to right), but SDL wants | ||
| 116 | // it from -90 (back facing west) to 90 (back facing east). | ||
| 117 | const float xtilt = (180.0f - SDL_fabsf(azimuth_angle)) - 90.0f; | ||
| 118 | |||
| 119 | // the altitude_angle goes from 0 to 90 regardless of which direction the pen is lifting off the device, but SDL wants | ||
| 120 | // it from -90 (flat facing north) to 90 (flat facing south). | ||
| 121 | const float ytilt = (azimuth_angle < 0.0f) ? -(90.0f - altitude_angle) : (90.0f - altitude_angle); | ||
| 122 | |||
| 123 | // rotation is in radians, and only available on a later iOS. | ||
| 124 | const float rotation = rollAngle * radians_to_degrees; // !!! FIXME: this might need adjustment, I don't have a pencil that supports it. | ||
| 125 | |||
| 126 | SDL_SendPenMotion(timestamp, penId, window, point->x, point->y); | ||
| 127 | SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_PRESSURE, pressure); | ||
| 128 | SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_XTILT, xtilt); | ||
| 129 | SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_YTILT, ytilt); | ||
| 130 | SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_ROTATION, rotation); | ||
| 131 | SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_DISTANCE, zOffset); | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 136 | extern void UIKit_HandlePenHover(SDL_uikitview *view, UIHoverGestureRecognizer *recognizer) | ||
| 137 | { | ||
| 138 | float zOffset = 0.0f; | ||
| 139 | if (@available(iOS 16.1, *)) { | ||
| 140 | zOffset = (float) [recognizer zOffset]; | ||
| 141 | } | ||
| 142 | |||
| 143 | float azimuthAngleInView = 0.0f; | ||
| 144 | if (@available(iOS 16.4, *)) { | ||
| 145 | azimuthAngleInView = (float) [recognizer azimuthAngleInView:view]; | ||
| 146 | } | ||
| 147 | |||
| 148 | float altitudeAngle = 0.0f; | ||
| 149 | if (@available(iOS 16.4, *)) { | ||
| 150 | altitudeAngle = (float) [recognizer altitudeAngle]; | ||
| 151 | } | ||
| 152 | |||
| 153 | float rollAngle = 0.0f; | ||
| 154 | if (@available(iOS 17.5, *)) { | ||
| 155 | rollAngle = (float) [recognizer rollAngle]; | ||
| 156 | } | ||
| 157 | |||
| 158 | SDL_Window *window = [view getSDLWindow]; | ||
| 159 | const CGPoint point = [recognizer locationInView:view]; | ||
| 160 | |||
| 161 | // force is zero here; if you're here, you're not touching. | ||
| 162 | // !!! FIXME: no timestamp on these...? | ||
| 163 | UIKit_HandlePenAxes(window, 0, zOffset, &point, 0.0f, 1.0f, azimuthAngleInView, altitudeAngle, rollAngle); | ||
| 164 | } | ||
| 165 | #endif | ||
| 166 | |||
| 167 | static void UIKit_HandlePenAxesFromUITouch(SDL_uikitview *view, UITouch *pencil) | ||
| 168 | { | ||
| 169 | float rollAngle = 0.0f; | ||
| 170 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 171 | if (@available(iOS 17.5, *)) { | ||
| 172 | rollAngle = (float) [pencil rollAngle]; | ||
| 173 | } | ||
| 174 | #endif | ||
| 175 | |||
| 176 | SDL_Window *window = [view getSDLWindow]; | ||
| 177 | const CGPoint point = [pencil locationInView:view]; | ||
| 178 | |||
| 179 | // zOffset is zero here; if you're here, you're touching. | ||
| 180 | UIKit_HandlePenAxes(window, [pencil timestamp], 0.0f, &point, [pencil force], [pencil maximumPossibleForce], [pencil azimuthAngleInView:view], [pencil altitudeAngle], rollAngle); | ||
| 181 | } | ||
| 182 | |||
| 183 | void UIKit_HandlePenMotion(SDL_uikitview *view, UITouch *pencil) | ||
| 184 | { | ||
| 185 | UIKit_HandlePenAxesFromUITouch(view, pencil); | ||
| 186 | } | ||
| 187 | |||
| 188 | void UIKit_HandlePenPress(SDL_uikitview *view, UITouch *pencil) | ||
| 189 | { | ||
| 190 | const SDL_PenID penId = UIKit_AddPenIfNecesary(); | ||
| 191 | if (penId) { | ||
| 192 | UIKit_HandlePenAxesFromUITouch(view, pencil); | ||
| 193 | SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, [view getSDLWindow], false, true); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | void UIKit_HandlePenRelease(SDL_uikitview *view, UITouch *pencil) | ||
| 198 | { | ||
| 199 | const SDL_PenID penId = UIKit_AddPenIfNecesary(); | ||
| 200 | if (penId) { | ||
| 201 | SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, [view getSDLWindow], false, false); | ||
| 202 | UIKit_HandlePenAxesFromUITouch(view, pencil); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | void UIKit_QuitPen(SDL_VideoDevice *_this) | ||
| 207 | { | ||
| 208 | if (apple_pencil_id) { | ||
| 209 | SDL_RemovePenDevice(0, apple_pencil_id); | ||
| 210 | apple_pencil_id = 0; | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.h new file mode 100644 index 0000000..927e646 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.h | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #ifndef SDL_uikitvideo_h_ | ||
| 22 | #define SDL_uikitvideo_h_ | ||
| 23 | |||
| 24 | #include "../SDL_sysvideo.h" | ||
| 25 | |||
| 26 | #ifdef __OBJC__ | ||
| 27 | |||
| 28 | #include <UIKit/UIKit.h> | ||
| 29 | |||
| 30 | @interface SDL_UIKitVideoData : NSObject | ||
| 31 | |||
| 32 | @property(nonatomic, assign) id pasteboardObserver; | ||
| 33 | |||
| 34 | @end | ||
| 35 | |||
| 36 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 37 | CGRect UIKit_ComputeViewFrame(SDL_Window *window); | ||
| 38 | #else | ||
| 39 | CGRect UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen); | ||
| 40 | #endif | ||
| 41 | |||
| 42 | #endif // __OBJC__ | ||
| 43 | |||
| 44 | bool UIKit_SuspendScreenSaver(SDL_VideoDevice *_this); | ||
| 45 | |||
| 46 | void UIKit_ForceUpdateHomeIndicator(void); | ||
| 47 | |||
| 48 | bool UIKit_IsSystemVersionAtLeast(double version); | ||
| 49 | |||
| 50 | SDL_SystemTheme UIKit_GetSystemTheme(void); | ||
| 51 | |||
| 52 | #endif // SDL_uikitvideo_h_ | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.m new file mode 100644 index 0000000..5c3987d --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.m | |||
| @@ -0,0 +1,312 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_VIDEO_DRIVER_UIKIT | ||
| 24 | |||
| 25 | #import <UIKit/UIKit.h> | ||
| 26 | |||
| 27 | #include "../SDL_sysvideo.h" | ||
| 28 | #include "../SDL_pixels_c.h" | ||
| 29 | #include "../../events/SDL_events_c.h" | ||
| 30 | |||
| 31 | #include "SDL_uikitvideo.h" | ||
| 32 | #include "SDL_uikitevents.h" | ||
| 33 | #include "SDL_uikitmodes.h" | ||
| 34 | #include "SDL_uikitwindow.h" | ||
| 35 | #include "SDL_uikitopengles.h" | ||
| 36 | #include "SDL_uikitclipboard.h" | ||
| 37 | #include "SDL_uikitvulkan.h" | ||
| 38 | #include "SDL_uikitmetalview.h" | ||
| 39 | #include "SDL_uikitmessagebox.h" | ||
| 40 | |||
| 41 | #define UIKITVID_DRIVER_NAME "uikit" | ||
| 42 | |||
| 43 | @implementation SDL_UIKitVideoData | ||
| 44 | |||
| 45 | @end | ||
| 46 | |||
| 47 | // Initialization/Query functions | ||
| 48 | static bool UIKit_VideoInit(SDL_VideoDevice *_this); | ||
| 49 | static void UIKit_VideoQuit(SDL_VideoDevice *_this); | ||
| 50 | |||
| 51 | // DUMMY driver bootstrap functions | ||
| 52 | |||
| 53 | static void UIKit_DeleteDevice(SDL_VideoDevice *device) | ||
| 54 | { | ||
| 55 | @autoreleasepool { | ||
| 56 | if (device->internal){ | ||
| 57 | CFRelease(device->internal); | ||
| 58 | } | ||
| 59 | SDL_free(device); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | static SDL_VideoDevice *UIKit_CreateDevice(void) | ||
| 64 | { | ||
| 65 | @autoreleasepool { | ||
| 66 | SDL_VideoDevice *device; | ||
| 67 | SDL_UIKitVideoData *data; | ||
| 68 | |||
| 69 | // Initialize all variables that we clean on shutdown | ||
| 70 | device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); | ||
| 71 | if (!device) { | ||
| 72 | return NULL; | ||
| 73 | } | ||
| 74 | |||
| 75 | data = [SDL_UIKitVideoData new]; | ||
| 76 | |||
| 77 | device->internal = (SDL_VideoData *)CFBridgingRetain(data); | ||
| 78 | device->system_theme = UIKit_GetSystemTheme(); | ||
| 79 | |||
| 80 | // Set the function pointers | ||
| 81 | device->VideoInit = UIKit_VideoInit; | ||
| 82 | device->VideoQuit = UIKit_VideoQuit; | ||
| 83 | device->GetDisplayModes = UIKit_GetDisplayModes; | ||
| 84 | device->SetDisplayMode = UIKit_SetDisplayMode; | ||
| 85 | device->PumpEvents = UIKit_PumpEvents; | ||
| 86 | device->SuspendScreenSaver = UIKit_SuspendScreenSaver; | ||
| 87 | device->CreateSDLWindow = UIKit_CreateWindow; | ||
| 88 | device->SetWindowTitle = UIKit_SetWindowTitle; | ||
| 89 | device->ShowWindow = UIKit_ShowWindow; | ||
| 90 | device->HideWindow = UIKit_HideWindow; | ||
| 91 | device->RaiseWindow = UIKit_RaiseWindow; | ||
| 92 | device->SetWindowBordered = UIKit_SetWindowBordered; | ||
| 93 | device->SetWindowFullscreen = UIKit_SetWindowFullscreen; | ||
| 94 | device->DestroyWindow = UIKit_DestroyWindow; | ||
| 95 | device->GetDisplayUsableBounds = UIKit_GetDisplayUsableBounds; | ||
| 96 | device->GetWindowSizeInPixels = UIKit_GetWindowSizeInPixels; | ||
| 97 | |||
| 98 | #ifdef SDL_IPHONE_KEYBOARD | ||
| 99 | device->HasScreenKeyboardSupport = UIKit_HasScreenKeyboardSupport; | ||
| 100 | device->StartTextInput = UIKit_StartTextInput; | ||
| 101 | device->StopTextInput = UIKit_StopTextInput; | ||
| 102 | device->SetTextInputProperties = UIKit_SetTextInputProperties; | ||
| 103 | device->IsScreenKeyboardShown = UIKit_IsScreenKeyboardShown; | ||
| 104 | device->UpdateTextInputArea = UIKit_UpdateTextInputArea; | ||
| 105 | #endif | ||
| 106 | |||
| 107 | device->SetClipboardText = UIKit_SetClipboardText; | ||
| 108 | device->GetClipboardText = UIKit_GetClipboardText; | ||
| 109 | device->HasClipboardText = UIKit_HasClipboardText; | ||
| 110 | |||
| 111 | // OpenGL (ES) functions | ||
| 112 | #if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2) | ||
| 113 | device->GL_MakeCurrent = UIKit_GL_MakeCurrent; | ||
| 114 | device->GL_SwapWindow = UIKit_GL_SwapWindow; | ||
| 115 | device->GL_CreateContext = UIKit_GL_CreateContext; | ||
| 116 | device->GL_DestroyContext = UIKit_GL_DestroyContext; | ||
| 117 | device->GL_GetProcAddress = UIKit_GL_GetProcAddress; | ||
| 118 | device->GL_LoadLibrary = UIKit_GL_LoadLibrary; | ||
| 119 | #endif | ||
| 120 | device->free = UIKit_DeleteDevice; | ||
| 121 | |||
| 122 | #ifdef SDL_VIDEO_VULKAN | ||
| 123 | device->Vulkan_LoadLibrary = UIKit_Vulkan_LoadLibrary; | ||
| 124 | device->Vulkan_UnloadLibrary = UIKit_Vulkan_UnloadLibrary; | ||
| 125 | device->Vulkan_GetInstanceExtensions = UIKit_Vulkan_GetInstanceExtensions; | ||
| 126 | device->Vulkan_CreateSurface = UIKit_Vulkan_CreateSurface; | ||
| 127 | device->Vulkan_DestroySurface = UIKit_Vulkan_DestroySurface; | ||
| 128 | #endif | ||
| 129 | |||
| 130 | #ifdef SDL_VIDEO_METAL | ||
| 131 | device->Metal_CreateView = UIKit_Metal_CreateView; | ||
| 132 | device->Metal_DestroyView = UIKit_Metal_DestroyView; | ||
| 133 | device->Metal_GetLayer = UIKit_Metal_GetLayer; | ||
| 134 | #endif | ||
| 135 | |||
| 136 | device->device_caps = VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; | ||
| 137 | |||
| 138 | device->gl_config.accelerated = 1; | ||
| 139 | |||
| 140 | return device; | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | VideoBootStrap UIKIT_bootstrap = { | ||
| 145 | UIKITVID_DRIVER_NAME, "SDL UIKit video driver", | ||
| 146 | UIKit_CreateDevice, | ||
| 147 | UIKit_ShowMessageBox, | ||
| 148 | false | ||
| 149 | }; | ||
| 150 | |||
| 151 | static bool UIKit_VideoInit(SDL_VideoDevice *_this) | ||
| 152 | { | ||
| 153 | _this->gl_config.driver_loaded = 1; | ||
| 154 | |||
| 155 | if (!UIKit_InitModes(_this)) { | ||
| 156 | return false; | ||
| 157 | } | ||
| 158 | |||
| 159 | SDL_InitGCKeyboard(); | ||
| 160 | SDL_InitGCMouse(); | ||
| 161 | |||
| 162 | UIKit_InitClipboard(_this); | ||
| 163 | |||
| 164 | return true; | ||
| 165 | } | ||
| 166 | |||
| 167 | static void UIKit_VideoQuit(SDL_VideoDevice *_this) | ||
| 168 | { | ||
| 169 | UIKit_QuitClipboard(_this); | ||
| 170 | |||
| 171 | SDL_QuitGCKeyboard(); | ||
| 172 | SDL_QuitGCMouse(); | ||
| 173 | |||
| 174 | UIKit_QuitModes(_this); | ||
| 175 | } | ||
| 176 | |||
| 177 | bool UIKit_SuspendScreenSaver(SDL_VideoDevice *_this) | ||
| 178 | { | ||
| 179 | @autoreleasepool { | ||
| 180 | UIApplication *app = [UIApplication sharedApplication]; | ||
| 181 | |||
| 182 | // Prevent the display from dimming and going to sleep. | ||
| 183 | app.idleTimerDisabled = (_this->suspend_screensaver != false); | ||
| 184 | } | ||
| 185 | return true; | ||
| 186 | } | ||
| 187 | |||
| 188 | bool UIKit_IsSystemVersionAtLeast(double version) | ||
| 189 | { | ||
| 190 | return [[UIDevice currentDevice].systemVersion doubleValue] >= version; | ||
| 191 | } | ||
| 192 | |||
| 193 | SDL_SystemTheme UIKit_GetSystemTheme(void) | ||
| 194 | { | ||
| 195 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 196 | if (@available(iOS 12.0, tvOS 10.0, *)) { | ||
| 197 | switch ([UIScreen mainScreen].traitCollection.userInterfaceStyle) { | ||
| 198 | case UIUserInterfaceStyleDark: | ||
| 199 | return SDL_SYSTEM_THEME_DARK; | ||
| 200 | case UIUserInterfaceStyleLight: | ||
| 201 | return SDL_SYSTEM_THEME_LIGHT; | ||
| 202 | default: | ||
| 203 | break; | ||
| 204 | } | ||
| 205 | } | ||
| 206 | #endif | ||
| 207 | return SDL_SYSTEM_THEME_UNKNOWN; | ||
| 208 | } | ||
| 209 | |||
| 210 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 211 | CGRect UIKit_ComputeViewFrame(SDL_Window *window){ | ||
| 212 | return CGRectMake(window->x, window->y, window->w, window->h); | ||
| 213 | } | ||
| 214 | #else | ||
| 215 | CGRect UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen) | ||
| 216 | { | ||
| 217 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 218 | CGRect frame = screen.bounds; | ||
| 219 | |||
| 220 | /* Use the UIWindow bounds instead of the UIScreen bounds, when possible. | ||
| 221 | * The uiwindow bounds may be smaller than the screen bounds when Split View | ||
| 222 | * is used on an iPad. */ | ||
| 223 | if (data != nil && data.uiwindow != nil) { | ||
| 224 | frame = data.uiwindow.bounds; | ||
| 225 | } | ||
| 226 | |||
| 227 | #ifndef SDL_PLATFORM_TVOS | ||
| 228 | /* iOS 10 seems to have a bug where, in certain conditions, putting the | ||
| 229 | * device to sleep with the a landscape-only app open, re-orienting the | ||
| 230 | * device to portrait, and turning it back on will result in the screen | ||
| 231 | * bounds returning portrait orientation despite the app being in landscape. | ||
| 232 | * This is a workaround until a better solution can be found. | ||
| 233 | * https://bugzilla.libsdl.org/show_bug.cgi?id=3505 | ||
| 234 | * https://bugzilla.libsdl.org/show_bug.cgi?id=3465 | ||
| 235 | * https://forums.developer.apple.com/thread/65337 */ | ||
| 236 | UIInterfaceOrientation orient = [UIApplication sharedApplication].statusBarOrientation; | ||
| 237 | BOOL landscape = UIInterfaceOrientationIsLandscape(orient) || | ||
| 238 | !(UIKit_GetSupportedOrientations(window) & (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown)); | ||
| 239 | BOOL fullscreen = CGRectEqualToRect(screen.bounds, frame); | ||
| 240 | |||
| 241 | /* The orientation flip doesn't make sense when the window is smaller | ||
| 242 | * than the screen (iPad Split View, for example). */ | ||
| 243 | if (fullscreen && (landscape != (frame.size.width > frame.size.height))) { | ||
| 244 | float height = frame.size.width; | ||
| 245 | frame.size.width = frame.size.height; | ||
| 246 | frame.size.height = height; | ||
| 247 | } | ||
| 248 | #endif | ||
| 249 | |||
| 250 | return frame; | ||
| 251 | } | ||
| 252 | |||
| 253 | #endif | ||
| 254 | |||
| 255 | void UIKit_ForceUpdateHomeIndicator(void) | ||
| 256 | { | ||
| 257 | #ifndef SDL_PLATFORM_TVOS | ||
| 258 | // Force the main SDL window to re-evaluate home indicator state | ||
| 259 | SDL_Window *focus = SDL_GetKeyboardFocus(); | ||
| 260 | if (focus) { | ||
| 261 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)focus->internal; | ||
| 262 | if (data != nil) { | ||
| 263 | [data.viewcontroller performSelectorOnMainThread:@selector(setNeedsUpdateOfHomeIndicatorAutoHidden) withObject:nil waitUntilDone:NO]; | ||
| 264 | [data.viewcontroller performSelectorOnMainThread:@selector(setNeedsUpdateOfScreenEdgesDeferringSystemGestures) withObject:nil waitUntilDone:NO]; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | #endif // !SDL_PLATFORM_TVOS | ||
| 268 | } | ||
| 269 | |||
| 270 | /* | ||
| 271 | * iOS log support. | ||
| 272 | * | ||
| 273 | * This doesn't really have anything to do with the interfaces of the SDL video | ||
| 274 | * subsystem, but we need to stuff this into an Objective-C source code file. | ||
| 275 | * | ||
| 276 | * NOTE: This is copypasted from src/video/cocoa/SDL_cocoavideo.m! Thus, if | ||
| 277 | * Cocoa is supported, we use that one instead. Be sure both versions remain | ||
| 278 | * identical! | ||
| 279 | */ | ||
| 280 | |||
| 281 | #ifndef SDL_VIDEO_DRIVER_COCOA | ||
| 282 | void SDL_NSLog(const char *prefix, const char *text) | ||
| 283 | { | ||
| 284 | @autoreleasepool { | ||
| 285 | NSString *nsText = [NSString stringWithUTF8String:text]; | ||
| 286 | if (prefix && *prefix) { | ||
| 287 | NSString *nsPrefix = [NSString stringWithUTF8String:prefix]; | ||
| 288 | NSLog(@"%@%@", nsPrefix, nsText); | ||
| 289 | } else { | ||
| 290 | NSLog(@"%@", nsText); | ||
| 291 | } | ||
| 292 | } | ||
| 293 | } | ||
| 294 | #endif // SDL_VIDEO_DRIVER_COCOA | ||
| 295 | |||
| 296 | /* | ||
| 297 | * iOS Tablet, etc, detection | ||
| 298 | * | ||
| 299 | * This doesn't really have anything to do with the interfaces of the SDL video | ||
| 300 | * subsystem, but we need to stuff this into an Objective-C source code file. | ||
| 301 | */ | ||
| 302 | bool SDL_IsIPad(void) | ||
| 303 | { | ||
| 304 | return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad); | ||
| 305 | } | ||
| 306 | |||
| 307 | bool SDL_IsAppleTV(void) | ||
| 308 | { | ||
| 309 | return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomTV); | ||
| 310 | } | ||
| 311 | |||
| 312 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.h new file mode 100644 index 0000000..78c2bed --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.h | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | #import <UIKit/UIKit.h> | ||
| 23 | |||
| 24 | #include "../SDL_sysvideo.h" | ||
| 25 | |||
| 26 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 27 | @interface SDL_uikitview : UIView <UIPointerInteractionDelegate> | ||
| 28 | #else | ||
| 29 | @interface SDL_uikitview : UIView | ||
| 30 | #endif | ||
| 31 | |||
| 32 | - (instancetype)initWithFrame:(CGRect)frame; | ||
| 33 | |||
| 34 | - (void)setSDLWindow:(SDL_Window *)window; | ||
| 35 | - (SDL_Window *)getSDLWindow; | ||
| 36 | |||
| 37 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 38 | - (void)pencilHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.0)); | ||
| 39 | |||
| 40 | - (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4)); | ||
| 41 | - (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4)); | ||
| 42 | - (void)indirectPointerHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.4)); | ||
| 43 | #endif | ||
| 44 | |||
| 45 | - (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize; | ||
| 46 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; | ||
| 47 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; | ||
| 48 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; | ||
| 49 | |||
| 50 | - (void)safeAreaInsetsDidChange; | ||
| 51 | |||
| 52 | @end | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.m new file mode 100644 index 0000000..ba3b09b --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.m | |||
| @@ -0,0 +1,587 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_VIDEO_DRIVER_UIKIT | ||
| 24 | |||
| 25 | #include "SDL_uikitview.h" | ||
| 26 | |||
| 27 | #include "../../events/SDL_mouse_c.h" | ||
| 28 | #include "../../events/SDL_touch_c.h" | ||
| 29 | #include "../../events/SDL_events_c.h" | ||
| 30 | |||
| 31 | #include "SDL_uikitappdelegate.h" | ||
| 32 | #include "SDL_uikitevents.h" | ||
| 33 | #include "SDL_uikitmodes.h" | ||
| 34 | #include "SDL_uikitpen.h" | ||
| 35 | #include "SDL_uikitwindow.h" | ||
| 36 | |||
| 37 | // The maximum number of mouse buttons we support | ||
| 38 | #define MAX_MOUSE_BUTTONS 5 | ||
| 39 | |||
| 40 | // This is defined in SDL_sysjoystick.m | ||
| 41 | #ifndef SDL_JOYSTICK_DISABLED | ||
| 42 | extern int SDL_AppleTVRemoteOpenedAsJoystick; | ||
| 43 | #endif | ||
| 44 | |||
| 45 | @implementation SDL_uikitview | ||
| 46 | { | ||
| 47 | SDL_Window *sdlwindow; | ||
| 48 | |||
| 49 | SDL_TouchID directTouchId; | ||
| 50 | SDL_TouchID indirectTouchId; | ||
| 51 | |||
| 52 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 53 | UIPointerInteraction *indirectPointerInteraction API_AVAILABLE(ios(13.4)); | ||
| 54 | #endif | ||
| 55 | } | ||
| 56 | |||
| 57 | - (instancetype)initWithFrame:(CGRect)frame | ||
| 58 | { | ||
| 59 | if ((self = [super initWithFrame:frame])) { | ||
| 60 | #ifdef SDL_PLATFORM_TVOS | ||
| 61 | // Apple TV Remote touchpad swipe gestures. | ||
| 62 | UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)]; | ||
| 63 | swipeUp.direction = UISwipeGestureRecognizerDirectionUp; | ||
| 64 | [self addGestureRecognizer:swipeUp]; | ||
| 65 | |||
| 66 | UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)]; | ||
| 67 | swipeDown.direction = UISwipeGestureRecognizerDirectionDown; | ||
| 68 | [self addGestureRecognizer:swipeDown]; | ||
| 69 | |||
| 70 | UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)]; | ||
| 71 | swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft; | ||
| 72 | [self addGestureRecognizer:swipeLeft]; | ||
| 73 | |||
| 74 | UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)]; | ||
| 75 | swipeRight.direction = UISwipeGestureRecognizerDirectionRight; | ||
| 76 | [self addGestureRecognizer:swipeRight]; | ||
| 77 | #endif | ||
| 78 | |||
| 79 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | ||
| 80 | self.autoresizesSubviews = YES; | ||
| 81 | |||
| 82 | directTouchId = 1; | ||
| 83 | indirectTouchId = 2; | ||
| 84 | |||
| 85 | #ifndef SDL_PLATFORM_TVOS | ||
| 86 | self.multipleTouchEnabled = YES; | ||
| 87 | SDL_AddTouch(directTouchId, SDL_TOUCH_DEVICE_DIRECT, ""); | ||
| 88 | |||
| 89 | if (@available(iOS 13.0, *)) { | ||
| 90 | UIHoverGestureRecognizer *pencilRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(pencilHovering:)]; | ||
| 91 | pencilRecognizer.allowedTouchTypes = @[@(UITouchTypePencil)]; | ||
| 92 | [self addGestureRecognizer:pencilRecognizer]; | ||
| 93 | } | ||
| 94 | |||
| 95 | if (@available(iOS 13.4, *)) { | ||
| 96 | indirectPointerInteraction = [[UIPointerInteraction alloc] initWithDelegate:self]; | ||
| 97 | [self addInteraction:indirectPointerInteraction]; | ||
| 98 | |||
| 99 | UIHoverGestureRecognizer *indirectPointerRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(indirectPointerHovering:)]; | ||
| 100 | indirectPointerRecognizer.allowedTouchTypes = @[@(UITouchTypeIndirectPointer)]; | ||
| 101 | [self addGestureRecognizer:indirectPointerRecognizer]; | ||
| 102 | } | ||
| 103 | #endif // !defined(SDL_PLATFORM_TVOS) | ||
| 104 | } | ||
| 105 | |||
| 106 | return self; | ||
| 107 | } | ||
| 108 | |||
| 109 | - (void)setSDLWindow:(SDL_Window *)window | ||
| 110 | { | ||
| 111 | SDL_UIKitWindowData *data = nil; | ||
| 112 | |||
| 113 | if (window == sdlwindow) { | ||
| 114 | return; | ||
| 115 | } | ||
| 116 | |||
| 117 | // Remove ourself from the old window. | ||
| 118 | if (sdlwindow) { | ||
| 119 | SDL_uikitview *view = nil; | ||
| 120 | data = (__bridge SDL_UIKitWindowData *)sdlwindow->internal; | ||
| 121 | |||
| 122 | [data.views removeObject:self]; | ||
| 123 | |||
| 124 | [self removeFromSuperview]; | ||
| 125 | |||
| 126 | // Restore the next-oldest view in the old window. | ||
| 127 | view = data.views.lastObject; | ||
| 128 | |||
| 129 | data.viewcontroller.view = view; | ||
| 130 | |||
| 131 | data.uiwindow.rootViewController = nil; | ||
| 132 | data.uiwindow.rootViewController = data.viewcontroller; | ||
| 133 | |||
| 134 | [data.uiwindow layoutIfNeeded]; | ||
| 135 | } | ||
| 136 | |||
| 137 | sdlwindow = window; | ||
| 138 | |||
| 139 | // Add ourself to the new window. | ||
| 140 | if (window) { | ||
| 141 | data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 142 | |||
| 143 | // Make sure the SDL window has a strong reference to this view. | ||
| 144 | [data.views addObject:self]; | ||
| 145 | |||
| 146 | // Replace the view controller's old view with this one. | ||
| 147 | [data.viewcontroller.view removeFromSuperview]; | ||
| 148 | data.viewcontroller.view = self; | ||
| 149 | |||
| 150 | /* The root view controller handles rotation and the status bar. | ||
| 151 | * Assigning it also adds the controller's view to the window. We | ||
| 152 | * explicitly re-set it to make sure the view is properly attached to | ||
| 153 | * the window. Just adding the sub-view if the root view controller is | ||
| 154 | * already correct causes orientation issues on iOS 7 and below. */ | ||
| 155 | data.uiwindow.rootViewController = nil; | ||
| 156 | data.uiwindow.rootViewController = data.viewcontroller; | ||
| 157 | |||
| 158 | /* The view's bounds may not be correct until the next event cycle. That | ||
| 159 | * might happen after the current dimensions are queried, so we force a | ||
| 160 | * layout now to immediately update the bounds. */ | ||
| 161 | [data.uiwindow layoutIfNeeded]; | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | - (SDL_Window *)getSDLWindow | ||
| 166 | { | ||
| 167 | return sdlwindow; | ||
| 168 | } | ||
| 169 | |||
| 170 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 171 | |||
| 172 | - (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4)) | ||
| 173 | { | ||
| 174 | return [UIPointerRegion regionWithRect:self.bounds identifier:nil]; | ||
| 175 | } | ||
| 176 | |||
| 177 | - (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4)) | ||
| 178 | { | ||
| 179 | if (SDL_CursorVisible()) { | ||
| 180 | return nil; | ||
| 181 | } else { | ||
| 182 | return [UIPointerStyle hiddenPointerStyle]; | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | - (void)indirectPointerHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.4)) | ||
| 187 | { | ||
| 188 | switch (recognizer.state) { | ||
| 189 | case UIGestureRecognizerStateBegan: | ||
| 190 | case UIGestureRecognizerStateChanged: | ||
| 191 | { | ||
| 192 | CGPoint point = [recognizer locationInView:self]; | ||
| 193 | SDL_SendMouseMotion(0, sdlwindow, SDL_GLOBAL_MOUSE_ID, false, point.x, point.y); | ||
| 194 | break; | ||
| 195 | } | ||
| 196 | |||
| 197 | default: | ||
| 198 | break; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | - (void)indirectPointerMoving:(UITouch *)touch API_AVAILABLE(ios(13.4)) | ||
| 203 | { | ||
| 204 | CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO]; | ||
| 205 | SDL_SendMouseMotion(0, sdlwindow, SDL_GLOBAL_MOUSE_ID, false, locationInView.x, locationInView.y); | ||
| 206 | } | ||
| 207 | |||
| 208 | - (void)indirectPointerPressed:(UITouch *)touch fromEvent:(UIEvent *)event API_AVAILABLE(ios(13.4)) | ||
| 209 | { | ||
| 210 | if (!SDL_HasMouse()) { | ||
| 211 | int i; | ||
| 212 | |||
| 213 | for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) { | ||
| 214 | if (event.buttonMask & SDL_BUTTON_MASK(i)) { | ||
| 215 | Uint8 button; | ||
| 216 | |||
| 217 | switch (i) { | ||
| 218 | case 1: | ||
| 219 | button = SDL_BUTTON_LEFT; | ||
| 220 | break; | ||
| 221 | case 2: | ||
| 222 | button = SDL_BUTTON_RIGHT; | ||
| 223 | break; | ||
| 224 | case 3: | ||
| 225 | button = SDL_BUTTON_MIDDLE; | ||
| 226 | break; | ||
| 227 | default: | ||
| 228 | button = (Uint8)i; | ||
| 229 | break; | ||
| 230 | } | ||
| 231 | SDL_SendMouseButton(UIKit_GetEventTimestamp([touch timestamp]), sdlwindow, SDL_GLOBAL_MOUSE_ID, button, true); | ||
| 232 | } | ||
| 233 | } | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | - (void)indirectPointerReleased:(UITouch *)touch fromEvent:(UIEvent *)event API_AVAILABLE(ios(13.4)) | ||
| 238 | { | ||
| 239 | if (!SDL_HasMouse()) { | ||
| 240 | int i; | ||
| 241 | SDL_MouseButtonFlags buttons = SDL_GetMouseState(NULL, NULL); | ||
| 242 | |||
| 243 | for (i = 0; i < MAX_MOUSE_BUTTONS; ++i) { | ||
| 244 | if (buttons & SDL_BUTTON_MASK(i)) { | ||
| 245 | SDL_SendMouseButton(UIKit_GetEventTimestamp([touch timestamp]), sdlwindow, SDL_GLOBAL_MOUSE_ID, (Uint8)i, false); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | - (void)pencilHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.0)) | ||
| 252 | { | ||
| 253 | switch (recognizer.state) { | ||
| 254 | case UIGestureRecognizerStateBegan: | ||
| 255 | case UIGestureRecognizerStateChanged: | ||
| 256 | UIKit_HandlePenHover(self, recognizer); | ||
| 257 | break; | ||
| 258 | |||
| 259 | case UIGestureRecognizerStateEnded: | ||
| 260 | case UIGestureRecognizerStateCancelled: | ||
| 261 | // we track touches elsewhere, so if a hover "ends" we'll deal with that there. | ||
| 262 | break; | ||
| 263 | |||
| 264 | default: | ||
| 265 | break; | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | - (void)pencilMoving:(UITouch *)touch | ||
| 270 | { | ||
| 271 | UIKit_HandlePenMotion(self, touch); | ||
| 272 | } | ||
| 273 | |||
| 274 | - (void)pencilPressed:(UITouch *)touch | ||
| 275 | { | ||
| 276 | UIKit_HandlePenPress(self, touch); | ||
| 277 | } | ||
| 278 | |||
| 279 | - (void)pencilReleased:(UITouch *)touch | ||
| 280 | { | ||
| 281 | UIKit_HandlePenRelease(self, touch); | ||
| 282 | } | ||
| 283 | |||
| 284 | #endif // !defined(SDL_PLATFORM_TVOS) | ||
| 285 | |||
| 286 | - (SDL_TouchDeviceType)touchTypeForTouch:(UITouch *)touch | ||
| 287 | { | ||
| 288 | if (touch.type == UITouchTypeIndirect) { | ||
| 289 | return SDL_TOUCH_DEVICE_INDIRECT_RELATIVE; | ||
| 290 | } | ||
| 291 | return SDL_TOUCH_DEVICE_DIRECT; | ||
| 292 | } | ||
| 293 | |||
| 294 | - (SDL_TouchID)touchIdForType:(SDL_TouchDeviceType)type | ||
| 295 | { | ||
| 296 | switch (type) { | ||
| 297 | case SDL_TOUCH_DEVICE_DIRECT: | ||
| 298 | default: | ||
| 299 | return directTouchId; | ||
| 300 | case SDL_TOUCH_DEVICE_INDIRECT_RELATIVE: | ||
| 301 | return indirectTouchId; | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | - (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize | ||
| 306 | { | ||
| 307 | CGPoint point = [touch locationInView:self]; | ||
| 308 | |||
| 309 | if (normalize) { | ||
| 310 | CGRect bounds = self.bounds; | ||
| 311 | point.x /= bounds.size.width; | ||
| 312 | point.y /= bounds.size.height; | ||
| 313 | } | ||
| 314 | |||
| 315 | return point; | ||
| 316 | } | ||
| 317 | |||
| 318 | - (float)pressureForTouch:(UITouch *)touch | ||
| 319 | { | ||
| 320 | return (float)touch.force; | ||
| 321 | } | ||
| 322 | |||
| 323 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event | ||
| 324 | { | ||
| 325 | for (UITouch *touch in touches) { | ||
| 326 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 327 | if (@available(iOS 13.0, *)) { | ||
| 328 | if (touch.type == UITouchTypePencil) { | ||
| 329 | [self pencilPressed:touch]; | ||
| 330 | continue; | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | if (@available(iOS 13.4, *)) { | ||
| 335 | if (touch.type == UITouchTypeIndirectPointer) { | ||
| 336 | [self indirectPointerPressed:touch fromEvent:event]; | ||
| 337 | continue; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | #endif // !defined(SDL_PLATFORM_TVOS) | ||
| 341 | |||
| 342 | SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch]; | ||
| 343 | SDL_TouchID touchId = [self touchIdForType:touchType]; | ||
| 344 | float pressure = [self pressureForTouch:touch]; | ||
| 345 | |||
| 346 | if (SDL_AddTouch(touchId, touchType, "") < 0) { | ||
| 347 | continue; | ||
| 348 | } | ||
| 349 | |||
| 350 | // FIXME, need to send: int clicks = (int) touch.tapCount; ? | ||
| 351 | |||
| 352 | CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES]; | ||
| 353 | SDL_SendTouch(UIKit_GetEventTimestamp([event timestamp]), | ||
| 354 | touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow, | ||
| 355 | SDL_EVENT_FINGER_DOWN, locationInView.x, locationInView.y, pressure); | ||
| 356 | } | ||
| 357 | } | ||
| 358 | |||
| 359 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event | ||
| 360 | { | ||
| 361 | for (UITouch *touch in touches) { | ||
| 362 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 363 | if (@available(iOS 13.0, *)) { | ||
| 364 | if (touch.type == UITouchTypePencil) { | ||
| 365 | [self pencilReleased:touch]; | ||
| 366 | continue; | ||
| 367 | } | ||
| 368 | } | ||
| 369 | |||
| 370 | if (@available(iOS 13.4, *)) { | ||
| 371 | if (touch.type == UITouchTypeIndirectPointer) { | ||
| 372 | [self indirectPointerReleased:touch fromEvent:event]; | ||
| 373 | continue; | ||
| 374 | } | ||
| 375 | } | ||
| 376 | #endif // !defined(SDL_PLATFORM_TVOS) | ||
| 377 | |||
| 378 | SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch]; | ||
| 379 | SDL_TouchID touchId = [self touchIdForType:touchType]; | ||
| 380 | float pressure = [self pressureForTouch:touch]; | ||
| 381 | |||
| 382 | if (SDL_AddTouch(touchId, touchType, "") < 0) { | ||
| 383 | continue; | ||
| 384 | } | ||
| 385 | |||
| 386 | // FIXME, need to send: int clicks = (int) touch.tapCount; ? | ||
| 387 | |||
| 388 | CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES]; | ||
| 389 | SDL_SendTouch(UIKit_GetEventTimestamp([event timestamp]), | ||
| 390 | touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow, | ||
| 391 | SDL_EVENT_FINGER_UP, locationInView.x, locationInView.y, pressure); | ||
| 392 | } | ||
| 393 | } | ||
| 394 | |||
| 395 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event | ||
| 396 | { | ||
| 397 | for (UITouch *touch in touches) { | ||
| 398 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 399 | if (@available(iOS 13.0, *)) { | ||
| 400 | if (touch.type == UITouchTypePencil) { | ||
| 401 | [self pencilReleased:touch]; | ||
| 402 | continue; | ||
| 403 | } | ||
| 404 | } | ||
| 405 | |||
| 406 | if (@available(iOS 13.4, *)) { | ||
| 407 | if (touch.type == UITouchTypeIndirectPointer) { | ||
| 408 | [self indirectPointerReleased:touch fromEvent:event]; | ||
| 409 | continue; | ||
| 410 | } | ||
| 411 | } | ||
| 412 | #endif // !defined(SDL_PLATFORM_TVOS) | ||
| 413 | |||
| 414 | SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch]; | ||
| 415 | SDL_TouchID touchId = [self touchIdForType:touchType]; | ||
| 416 | float pressure = [self pressureForTouch:touch]; | ||
| 417 | |||
| 418 | if (SDL_AddTouch(touchId, touchType, "") < 0) { | ||
| 419 | continue; | ||
| 420 | } | ||
| 421 | |||
| 422 | CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES]; | ||
| 423 | SDL_SendTouch(UIKit_GetEventTimestamp([event timestamp]), | ||
| 424 | touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow, | ||
| 425 | SDL_EVENT_FINGER_CANCELED, locationInView.x, locationInView.y, pressure); | ||
| 426 | } | ||
| 427 | } | ||
| 428 | |||
| 429 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event | ||
| 430 | { | ||
| 431 | for (UITouch *touch in touches) { | ||
| 432 | #if !defined(SDL_PLATFORM_TVOS) | ||
| 433 | if (@available(iOS 13.0, *)) { | ||
| 434 | if (touch.type == UITouchTypePencil) { | ||
| 435 | [self pencilMoving:touch]; | ||
| 436 | continue; | ||
| 437 | } | ||
| 438 | } | ||
| 439 | |||
| 440 | if (@available(iOS 13.4, *)) { | ||
| 441 | if (touch.type == UITouchTypeIndirectPointer) { | ||
| 442 | [self indirectPointerMoving:touch]; | ||
| 443 | continue; | ||
| 444 | } | ||
| 445 | } | ||
| 446 | #endif // !defined(SDL_PLATFORM_TVOS) | ||
| 447 | |||
| 448 | SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch]; | ||
| 449 | SDL_TouchID touchId = [self touchIdForType:touchType]; | ||
| 450 | float pressure = [self pressureForTouch:touch]; | ||
| 451 | |||
| 452 | if (SDL_AddTouch(touchId, touchType, "") < 0) { | ||
| 453 | continue; | ||
| 454 | } | ||
| 455 | |||
| 456 | CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES]; | ||
| 457 | SDL_SendTouchMotion(UIKit_GetEventTimestamp([event timestamp]), | ||
| 458 | touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow, | ||
| 459 | locationInView.x, locationInView.y, pressure); | ||
| 460 | } | ||
| 461 | } | ||
| 462 | |||
| 463 | - (void)safeAreaInsetsDidChange | ||
| 464 | { | ||
| 465 | // Update the safe area insets | ||
| 466 | SDL_SetWindowSafeAreaInsets(sdlwindow, | ||
| 467 | (int)SDL_ceilf(self.safeAreaInsets.left), | ||
| 468 | (int)SDL_ceilf(self.safeAreaInsets.right), | ||
| 469 | (int)SDL_ceilf(self.safeAreaInsets.top), | ||
| 470 | (int)SDL_ceilf(self.safeAreaInsets.bottom)); | ||
| 471 | } | ||
| 472 | |||
| 473 | - (SDL_Scancode)scancodeFromPress:(UIPress *)press | ||
| 474 | { | ||
| 475 | if (press.key != nil) { | ||
| 476 | return (SDL_Scancode)press.key.keyCode; | ||
| 477 | } | ||
| 478 | |||
| 479 | #ifndef SDL_JOYSTICK_DISABLED | ||
| 480 | // Presses from Apple TV remote | ||
| 481 | if (!SDL_AppleTVRemoteOpenedAsJoystick) { | ||
| 482 | switch (press.type) { | ||
| 483 | case UIPressTypeUpArrow: | ||
| 484 | return SDL_SCANCODE_UP; | ||
| 485 | case UIPressTypeDownArrow: | ||
| 486 | return SDL_SCANCODE_DOWN; | ||
| 487 | case UIPressTypeLeftArrow: | ||
| 488 | return SDL_SCANCODE_LEFT; | ||
| 489 | case UIPressTypeRightArrow: | ||
| 490 | return SDL_SCANCODE_RIGHT; | ||
| 491 | case UIPressTypeSelect: | ||
| 492 | // HIG says: "primary button behavior" | ||
| 493 | return SDL_SCANCODE_RETURN; | ||
| 494 | case UIPressTypeMenu: | ||
| 495 | // HIG says: "returns to previous screen" | ||
| 496 | return SDL_SCANCODE_ESCAPE; | ||
| 497 | case UIPressTypePlayPause: | ||
| 498 | // HIG says: "secondary button behavior" | ||
| 499 | return SDL_SCANCODE_PAUSE; | ||
| 500 | default: | ||
| 501 | break; | ||
| 502 | } | ||
| 503 | } | ||
| 504 | #endif // !SDL_JOYSTICK_DISABLED | ||
| 505 | |||
| 506 | return SDL_SCANCODE_UNKNOWN; | ||
| 507 | } | ||
| 508 | |||
| 509 | - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event | ||
| 510 | { | ||
| 511 | if (!SDL_HasKeyboard()) { | ||
| 512 | for (UIPress *press in presses) { | ||
| 513 | SDL_Scancode scancode = [self scancodeFromPress:press]; | ||
| 514 | SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), SDL_GLOBAL_KEYBOARD_ID, 0, scancode, true); | ||
| 515 | } | ||
| 516 | } | ||
| 517 | if (SDL_TextInputActive(sdlwindow)) { | ||
| 518 | [super pressesBegan:presses withEvent:event]; | ||
| 519 | } | ||
| 520 | } | ||
| 521 | |||
| 522 | - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event | ||
| 523 | { | ||
| 524 | if (!SDL_HasKeyboard()) { | ||
| 525 | for (UIPress *press in presses) { | ||
| 526 | SDL_Scancode scancode = [self scancodeFromPress:press]; | ||
| 527 | SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), SDL_GLOBAL_KEYBOARD_ID, 0, scancode, false); | ||
| 528 | } | ||
| 529 | } | ||
| 530 | if (SDL_TextInputActive(sdlwindow)) { | ||
| 531 | [super pressesEnded:presses withEvent:event]; | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | - (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event | ||
| 536 | { | ||
| 537 | if (!SDL_HasKeyboard()) { | ||
| 538 | for (UIPress *press in presses) { | ||
| 539 | SDL_Scancode scancode = [self scancodeFromPress:press]; | ||
| 540 | SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), SDL_GLOBAL_KEYBOARD_ID, 0, scancode, false); | ||
| 541 | } | ||
| 542 | } | ||
| 543 | if (SDL_TextInputActive(sdlwindow)) { | ||
| 544 | [super pressesCancelled:presses withEvent:event]; | ||
| 545 | } | ||
| 546 | } | ||
| 547 | |||
| 548 | - (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event | ||
| 549 | { | ||
| 550 | // This is only called when the force of a press changes. | ||
| 551 | if (SDL_TextInputActive(sdlwindow)) { | ||
| 552 | [super pressesChanged:presses withEvent:event]; | ||
| 553 | } | ||
| 554 | } | ||
| 555 | |||
| 556 | #ifdef SDL_PLATFORM_TVOS | ||
| 557 | - (void)swipeGesture:(UISwipeGestureRecognizer *)gesture | ||
| 558 | { | ||
| 559 | // Swipe gestures don't trigger begin states. | ||
| 560 | if (gesture.state == UIGestureRecognizerStateEnded) { | ||
| 561 | #ifndef SDL_JOYSTICK_DISABLED | ||
| 562 | if (!SDL_AppleTVRemoteOpenedAsJoystick) { | ||
| 563 | /* Send arrow key presses for now, as we don't have an external API | ||
| 564 | * which better maps to swipe gestures. */ | ||
| 565 | switch (gesture.direction) { | ||
| 566 | case UISwipeGestureRecognizerDirectionUp: | ||
| 567 | SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_UP); | ||
| 568 | break; | ||
| 569 | case UISwipeGestureRecognizerDirectionDown: | ||
| 570 | SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_DOWN); | ||
| 571 | break; | ||
| 572 | case UISwipeGestureRecognizerDirectionLeft: | ||
| 573 | SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_LEFT); | ||
| 574 | break; | ||
| 575 | case UISwipeGestureRecognizerDirectionRight: | ||
| 576 | SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_RIGHT); | ||
| 577 | break; | ||
| 578 | } | ||
| 579 | } | ||
| 580 | #endif // !SDL_JOYSTICK_DISABLED | ||
| 581 | } | ||
| 582 | } | ||
| 583 | #endif // SDL_PLATFORM_TVOS | ||
| 584 | |||
| 585 | @end | ||
| 586 | |||
| 587 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.h new file mode 100644 index 0000000..d52e478 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.h | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #import <UIKit/UIKit.h> | ||
| 24 | |||
| 25 | #include "../SDL_sysvideo.h" | ||
| 26 | |||
| 27 | #ifdef SDL_PLATFORM_TVOS | ||
| 28 | #import <GameController/GameController.h> | ||
| 29 | #define SDLRootViewController GCEventViewController | ||
| 30 | #else | ||
| 31 | #define SDLRootViewController UIViewController | ||
| 32 | #endif | ||
| 33 | |||
| 34 | @interface SDLUITextField : UITextField | ||
| 35 | - (BOOL)canPerformAction:(SEL)action withSender:(id)sender; | ||
| 36 | @end | ||
| 37 | |||
| 38 | #ifdef SDL_IPHONE_KEYBOARD | ||
| 39 | @interface SDL_uikitviewcontroller : SDLRootViewController <UITextFieldDelegate> | ||
| 40 | #else | ||
| 41 | @interface SDL_uikitviewcontroller : SDLRootViewController | ||
| 42 | #endif | ||
| 43 | |||
| 44 | @property(nonatomic, assign) SDL_Window *window; | ||
| 45 | |||
| 46 | - (instancetype)initWithSDLWindow:(SDL_Window *)_window; | ||
| 47 | |||
| 48 | - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection; | ||
| 49 | |||
| 50 | - (void)setAnimationCallback:(int)interval | ||
| 51 | callback:(void (*)(void *))callback | ||
| 52 | callbackParam:(void *)callbackParam; | ||
| 53 | |||
| 54 | - (void)startAnimation; | ||
| 55 | - (void)stopAnimation; | ||
| 56 | |||
| 57 | - (void)doLoop:(CADisplayLink *)sender; | ||
| 58 | |||
| 59 | - (void)loadView; | ||
| 60 | - (void)viewDidLayoutSubviews; | ||
| 61 | |||
| 62 | #ifndef SDL_PLATFORM_TVOS | ||
| 63 | - (NSUInteger)supportedInterfaceOrientations; | ||
| 64 | - (BOOL)prefersStatusBarHidden; | ||
| 65 | - (BOOL)prefersHomeIndicatorAutoHidden; | ||
| 66 | - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures; | ||
| 67 | |||
| 68 | @property(nonatomic, assign) int homeIndicatorHidden; | ||
| 69 | #endif | ||
| 70 | |||
| 71 | #ifdef SDL_IPHONE_KEYBOARD | ||
| 72 | - (bool)startTextInput; | ||
| 73 | - (bool)stopTextInput; | ||
| 74 | - (void)initKeyboard; | ||
| 75 | - (void)deinitKeyboard; | ||
| 76 | |||
| 77 | - (void)keyboardWillShow:(NSNotification *)notification; | ||
| 78 | - (void)keyboardWillHide:(NSNotification *)notification; | ||
| 79 | |||
| 80 | - (void)updateKeyboard; | ||
| 81 | |||
| 82 | @property(nonatomic, assign, getter=isTextFieldFocused) BOOL textFieldFocused; | ||
| 83 | @property(nonatomic, assign) SDL_Rect textInputRect; | ||
| 84 | @property(nonatomic, assign) int keyboardHeight; | ||
| 85 | #endif | ||
| 86 | |||
| 87 | @end | ||
| 88 | |||
| 89 | #ifdef SDL_IPHONE_KEYBOARD | ||
| 90 | bool UIKit_HasScreenKeyboardSupport(SDL_VideoDevice *_this); | ||
| 91 | bool UIKit_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props); | ||
| 92 | bool UIKit_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 93 | void UIKit_SetTextInputProperties(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props); | ||
| 94 | bool UIKit_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 95 | bool UIKit_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 96 | #endif | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.m new file mode 100644 index 0000000..45f2d64 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.m | |||
| @@ -0,0 +1,736 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_VIDEO_DRIVER_UIKIT | ||
| 24 | |||
| 25 | #include "../SDL_sysvideo.h" | ||
| 26 | #include "../../events/SDL_events_c.h" | ||
| 27 | |||
| 28 | #include "SDL_uikitviewcontroller.h" | ||
| 29 | #include "SDL_uikitmessagebox.h" | ||
| 30 | #include "SDL_uikitevents.h" | ||
| 31 | #include "SDL_uikitvideo.h" | ||
| 32 | #include "SDL_uikitmodes.h" | ||
| 33 | #include "SDL_uikitwindow.h" | ||
| 34 | #include "SDL_uikitopengles.h" | ||
| 35 | |||
| 36 | #ifdef SDL_PLATFORM_TVOS | ||
| 37 | static void SDLCALL SDL_AppleTVControllerUIHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 38 | { | ||
| 39 | @autoreleasepool { | ||
| 40 | SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *)userdata; | ||
| 41 | viewcontroller.controllerUserInteractionEnabled = hint && (*hint != '0'); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | #endif | ||
| 45 | |||
| 46 | #ifndef SDL_PLATFORM_TVOS | ||
| 47 | static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 48 | { | ||
| 49 | @autoreleasepool { | ||
| 50 | SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *)userdata; | ||
| 51 | viewcontroller.homeIndicatorHidden = (hint && *hint) ? SDL_atoi(hint) : -1; | ||
| 52 | [viewcontroller setNeedsUpdateOfHomeIndicatorAutoHidden]; | ||
| 53 | [viewcontroller setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; | ||
| 54 | } | ||
| 55 | } | ||
| 56 | #endif | ||
| 57 | |||
| 58 | @implementation SDLUITextField : UITextField | ||
| 59 | - (BOOL)canPerformAction:(SEL)action withSender:(id)sender | ||
| 60 | { | ||
| 61 | if (action == @selector(paste:)) { | ||
| 62 | return NO; | ||
| 63 | } | ||
| 64 | |||
| 65 | return [super canPerformAction:action withSender:sender]; | ||
| 66 | } | ||
| 67 | @end | ||
| 68 | |||
| 69 | @implementation SDL_uikitviewcontroller | ||
| 70 | { | ||
| 71 | CADisplayLink *displayLink; | ||
| 72 | int animationInterval; | ||
| 73 | void (*animationCallback)(void *); | ||
| 74 | void *animationCallbackParam; | ||
| 75 | |||
| 76 | #ifdef SDL_IPHONE_KEYBOARD | ||
| 77 | SDLUITextField *textField; | ||
| 78 | BOOL hidingKeyboard; | ||
| 79 | BOOL rotatingOrientation; | ||
| 80 | NSString *committedText; | ||
| 81 | NSString *obligateForBackspace; | ||
| 82 | #endif | ||
| 83 | } | ||
| 84 | |||
| 85 | @synthesize window; | ||
| 86 | |||
| 87 | - (instancetype)initWithSDLWindow:(SDL_Window *)_window | ||
| 88 | { | ||
| 89 | if (self = [super initWithNibName:nil bundle:nil]) { | ||
| 90 | self.window = _window; | ||
| 91 | |||
| 92 | #ifdef SDL_IPHONE_KEYBOARD | ||
| 93 | [self initKeyboard]; | ||
| 94 | hidingKeyboard = NO; | ||
| 95 | rotatingOrientation = NO; | ||
| 96 | #endif | ||
| 97 | |||
| 98 | #ifdef SDL_PLATFORM_TVOS | ||
| 99 | SDL_AddHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS, | ||
| 100 | SDL_AppleTVControllerUIHintChanged, | ||
| 101 | (__bridge void *)self); | ||
| 102 | #endif | ||
| 103 | |||
| 104 | #ifndef SDL_PLATFORM_TVOS | ||
| 105 | SDL_AddHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR, | ||
| 106 | SDL_HideHomeIndicatorHintChanged, | ||
| 107 | (__bridge void *)self); | ||
| 108 | #endif | ||
| 109 | |||
| 110 | // Enable high refresh rates on iOS | ||
| 111 | // To enable this on phones, you should add the following line to Info.plist: | ||
| 112 | // <key>CADisableMinimumFrameDurationOnPhone</key> <true/> | ||
| 113 | if (@available(iOS 15.0, tvOS 15.0, *)) { | ||
| 114 | const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay()); | ||
| 115 | if (mode && mode->refresh_rate > 60.0f) { | ||
| 116 | int frame_rate = (int)mode->refresh_rate; | ||
| 117 | displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)]; | ||
| 118 | displayLink.preferredFrameRateRange = CAFrameRateRangeMake((frame_rate * 2) / 3, frame_rate, frame_rate); | ||
| 119 | [displayLink addToRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode]; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
| 123 | return self; | ||
| 124 | } | ||
| 125 | |||
| 126 | - (void)dealloc | ||
| 127 | { | ||
| 128 | #ifdef SDL_IPHONE_KEYBOARD | ||
| 129 | [self deinitKeyboard]; | ||
| 130 | #endif | ||
| 131 | |||
| 132 | #ifdef SDL_PLATFORM_TVOS | ||
| 133 | SDL_RemoveHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS, | ||
| 134 | SDL_AppleTVControllerUIHintChanged, | ||
| 135 | (__bridge void *)self); | ||
| 136 | #endif | ||
| 137 | |||
| 138 | #ifndef SDL_PLATFORM_TVOS | ||
| 139 | SDL_RemoveHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR, | ||
| 140 | SDL_HideHomeIndicatorHintChanged, | ||
| 141 | (__bridge void *)self); | ||
| 142 | #endif | ||
| 143 | } | ||
| 144 | |||
| 145 | - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection | ||
| 146 | { | ||
| 147 | SDL_SetSystemTheme(UIKit_GetSystemTheme()); | ||
| 148 | } | ||
| 149 | |||
| 150 | - (void)setAnimationCallback:(int)interval | ||
| 151 | callback:(void (*)(void *))callback | ||
| 152 | callbackParam:(void *)callbackParam | ||
| 153 | { | ||
| 154 | [self stopAnimation]; | ||
| 155 | |||
| 156 | if (interval <= 0) { | ||
| 157 | interval = 1; | ||
| 158 | } | ||
| 159 | animationInterval = interval; | ||
| 160 | animationCallback = callback; | ||
| 161 | animationCallbackParam = callbackParam; | ||
| 162 | |||
| 163 | if (animationCallback) { | ||
| 164 | [self startAnimation]; | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | - (void)startAnimation | ||
| 169 | { | ||
| 170 | displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)]; | ||
| 171 | |||
| 172 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 173 | displayLink.preferredFramesPerSecond = 90 / animationInterval; //TODO: Get frame max frame rate on visionOS | ||
| 174 | #else | ||
| 175 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 176 | |||
| 177 | displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval; | ||
| 178 | #endif | ||
| 179 | |||
| 180 | [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; | ||
| 181 | } | ||
| 182 | |||
| 183 | - (void)stopAnimation | ||
| 184 | { | ||
| 185 | [displayLink invalidate]; | ||
| 186 | displayLink = nil; | ||
| 187 | } | ||
| 188 | |||
| 189 | - (void)doLoop:(CADisplayLink *)sender | ||
| 190 | { | ||
| 191 | // Don't run the game loop while a messagebox is up | ||
| 192 | if (animationCallback && !UIKit_ShowingMessageBox()) { | ||
| 193 | // See the comment in the function definition. | ||
| 194 | #if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2) | ||
| 195 | UIKit_GL_RestoreCurrentContext(); | ||
| 196 | #endif | ||
| 197 | |||
| 198 | animationCallback(animationCallbackParam); | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | - (void)loadView | ||
| 203 | { | ||
| 204 | // Do nothing. | ||
| 205 | } | ||
| 206 | |||
| 207 | - (void)viewDidLayoutSubviews | ||
| 208 | { | ||
| 209 | const CGSize size = self.view.bounds.size; | ||
| 210 | int w = (int)size.width; | ||
| 211 | int h = (int)size.height; | ||
| 212 | |||
| 213 | SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, w, h); | ||
| 214 | } | ||
| 215 | |||
| 216 | #ifndef SDL_PLATFORM_TVOS | ||
| 217 | - (NSUInteger)supportedInterfaceOrientations | ||
| 218 | { | ||
| 219 | return UIKit_GetSupportedOrientations(window); | ||
| 220 | } | ||
| 221 | |||
| 222 | - (BOOL)prefersStatusBarHidden | ||
| 223 | { | ||
| 224 | BOOL hidden = (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) != 0; | ||
| 225 | return hidden; | ||
| 226 | } | ||
| 227 | |||
| 228 | - (BOOL)prefersHomeIndicatorAutoHidden | ||
| 229 | { | ||
| 230 | BOOL hidden = NO; | ||
| 231 | if (self.homeIndicatorHidden == 1) { | ||
| 232 | hidden = YES; | ||
| 233 | } | ||
| 234 | return hidden; | ||
| 235 | } | ||
| 236 | |||
| 237 | - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures | ||
| 238 | { | ||
| 239 | if (self.homeIndicatorHidden >= 0) { | ||
| 240 | if (self.homeIndicatorHidden == 2) { | ||
| 241 | return UIRectEdgeAll; | ||
| 242 | } else { | ||
| 243 | return UIRectEdgeNone; | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | // By default, fullscreen and borderless windows get all screen gestures | ||
| 248 | if ((window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) != 0) { | ||
| 249 | return UIRectEdgeAll; | ||
| 250 | } else { | ||
| 251 | return UIRectEdgeNone; | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | - (BOOL)prefersPointerLocked | ||
| 256 | { | ||
| 257 | return SDL_GCMouseRelativeMode() ? YES : NO; | ||
| 258 | } | ||
| 259 | |||
| 260 | #endif // !SDL_PLATFORM_TVOS | ||
| 261 | |||
| 262 | /* | ||
| 263 | ---- Keyboard related functionality below this line ---- | ||
| 264 | */ | ||
| 265 | #ifdef SDL_IPHONE_KEYBOARD | ||
| 266 | |||
| 267 | @synthesize textInputRect; | ||
| 268 | @synthesize keyboardHeight; | ||
| 269 | @synthesize textFieldFocused; | ||
| 270 | |||
| 271 | // Set ourselves up as a UITextFieldDelegate | ||
| 272 | - (void)initKeyboard | ||
| 273 | { | ||
| 274 | obligateForBackspace = @" "; // 64 space | ||
| 275 | textField = [[SDLUITextField alloc] initWithFrame:CGRectZero]; | ||
| 276 | textField.delegate = self; | ||
| 277 | // placeholder so there is something to delete! | ||
| 278 | textField.text = obligateForBackspace; | ||
| 279 | committedText = textField.text; | ||
| 280 | |||
| 281 | textField.hidden = YES; | ||
| 282 | textFieldFocused = NO; | ||
| 283 | |||
| 284 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; | ||
| 285 | #ifndef SDL_PLATFORM_TVOS | ||
| 286 | [center addObserver:self | ||
| 287 | selector:@selector(keyboardWillShow:) | ||
| 288 | name:UIKeyboardWillShowNotification | ||
| 289 | object:nil]; | ||
| 290 | [center addObserver:self | ||
| 291 | selector:@selector(keyboardWillHide:) | ||
| 292 | name:UIKeyboardWillHideNotification | ||
| 293 | object:nil]; | ||
| 294 | [center addObserver:self | ||
| 295 | selector:@selector(keyboardDidHide:) | ||
| 296 | name:UIKeyboardDidHideNotification | ||
| 297 | object:nil]; | ||
| 298 | #endif | ||
| 299 | [center addObserver:self | ||
| 300 | selector:@selector(textFieldTextDidChange:) | ||
| 301 | name:UITextFieldTextDidChangeNotification | ||
| 302 | object:nil]; | ||
| 303 | } | ||
| 304 | |||
| 305 | - (NSArray *)keyCommands | ||
| 306 | { | ||
| 307 | NSMutableArray *commands = [[NSMutableArray alloc] init]; | ||
| 308 | [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]]; | ||
| 309 | [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]]; | ||
| 310 | [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]]; | ||
| 311 | [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]]; | ||
| 312 | [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:kNilOptions action:@selector(handleCommand:)]]; | ||
| 313 | return [NSArray arrayWithArray:commands]; | ||
| 314 | } | ||
| 315 | |||
| 316 | - (void)handleCommand:(UIKeyCommand *)keyCommand | ||
| 317 | { | ||
| 318 | SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN; | ||
| 319 | NSString *input = keyCommand.input; | ||
| 320 | |||
| 321 | if (input == UIKeyInputUpArrow) { | ||
| 322 | scancode = SDL_SCANCODE_UP; | ||
| 323 | } else if (input == UIKeyInputDownArrow) { | ||
| 324 | scancode = SDL_SCANCODE_DOWN; | ||
| 325 | } else if (input == UIKeyInputLeftArrow) { | ||
| 326 | scancode = SDL_SCANCODE_LEFT; | ||
| 327 | } else if (input == UIKeyInputRightArrow) { | ||
| 328 | scancode = SDL_SCANCODE_RIGHT; | ||
| 329 | } else if (input == UIKeyInputEscape) { | ||
| 330 | scancode = SDL_SCANCODE_ESCAPE; | ||
| 331 | } | ||
| 332 | |||
| 333 | if (scancode != SDL_SCANCODE_UNKNOWN) { | ||
| 334 | SDL_SendKeyboardKeyAutoRelease(0, scancode); | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 338 | - (void)setView:(UIView *)view | ||
| 339 | { | ||
| 340 | [super setView:view]; | ||
| 341 | |||
| 342 | [view addSubview:textField]; | ||
| 343 | |||
| 344 | if (textFieldFocused) { | ||
| 345 | /* startTextInput has been called before the text field was added to the view, | ||
| 346 | * call it again for the text field to actually become first responder. */ | ||
| 347 | [self startTextInput]; | ||
| 348 | } | ||
| 349 | } | ||
| 350 | |||
| 351 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator | ||
| 352 | { | ||
| 353 | [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; | ||
| 354 | rotatingOrientation = YES; | ||
| 355 | [coordinator | ||
| 356 | animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) { | ||
| 357 | } | ||
| 358 | completion:^(id<UIViewControllerTransitionCoordinatorContext> context) { | ||
| 359 | self->rotatingOrientation = NO; | ||
| 360 | }]; | ||
| 361 | } | ||
| 362 | |||
| 363 | - (void)deinitKeyboard | ||
| 364 | { | ||
| 365 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; | ||
| 366 | #ifndef SDL_PLATFORM_TVOS | ||
| 367 | [center removeObserver:self | ||
| 368 | name:UIKeyboardWillShowNotification | ||
| 369 | object:nil]; | ||
| 370 | [center removeObserver:self | ||
| 371 | name:UIKeyboardWillHideNotification | ||
| 372 | object:nil]; | ||
| 373 | [center removeObserver:self | ||
| 374 | name:UIKeyboardDidHideNotification | ||
| 375 | object:nil]; | ||
| 376 | #endif | ||
| 377 | [center removeObserver:self | ||
| 378 | name:UITextFieldTextDidChangeNotification | ||
| 379 | object:nil]; | ||
| 380 | } | ||
| 381 | |||
| 382 | - (void)setTextFieldProperties:(SDL_PropertiesID) props | ||
| 383 | { | ||
| 384 | textField.secureTextEntry = NO; | ||
| 385 | |||
| 386 | switch (SDL_GetTextInputType(props)) { | ||
| 387 | default: | ||
| 388 | case SDL_TEXTINPUT_TYPE_TEXT: | ||
| 389 | textField.keyboardType = UIKeyboardTypeDefault; | ||
| 390 | textField.textContentType = nil; | ||
| 391 | break; | ||
| 392 | case SDL_TEXTINPUT_TYPE_TEXT_NAME: | ||
| 393 | textField.keyboardType = UIKeyboardTypeDefault; | ||
| 394 | textField.textContentType = UITextContentTypeName; | ||
| 395 | break; | ||
| 396 | case SDL_TEXTINPUT_TYPE_TEXT_EMAIL: | ||
| 397 | textField.keyboardType = UIKeyboardTypeEmailAddress; | ||
| 398 | textField.textContentType = UITextContentTypeEmailAddress; | ||
| 399 | break; | ||
| 400 | case SDL_TEXTINPUT_TYPE_TEXT_USERNAME: | ||
| 401 | textField.keyboardType = UIKeyboardTypeDefault; | ||
| 402 | textField.textContentType = UITextContentTypeUsername; | ||
| 403 | break; | ||
| 404 | case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN: | ||
| 405 | textField.keyboardType = UIKeyboardTypeDefault; | ||
| 406 | textField.textContentType = UITextContentTypePassword; | ||
| 407 | textField.secureTextEntry = YES; | ||
| 408 | break; | ||
| 409 | case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE: | ||
| 410 | textField.keyboardType = UIKeyboardTypeDefault; | ||
| 411 | textField.textContentType = UITextContentTypePassword; | ||
| 412 | break; | ||
| 413 | case SDL_TEXTINPUT_TYPE_NUMBER: | ||
| 414 | textField.keyboardType = UIKeyboardTypeDecimalPad; | ||
| 415 | textField.textContentType = nil; | ||
| 416 | break; | ||
| 417 | case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN: | ||
| 418 | textField.keyboardType = UIKeyboardTypeNumberPad; | ||
| 419 | if (@available(iOS 12.0, tvOS 12.0, *)) { | ||
| 420 | textField.textContentType = UITextContentTypeOneTimeCode; | ||
| 421 | } else { | ||
| 422 | textField.textContentType = nil; | ||
| 423 | } | ||
| 424 | textField.secureTextEntry = YES; | ||
| 425 | break; | ||
| 426 | case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE: | ||
| 427 | textField.keyboardType = UIKeyboardTypeNumberPad; | ||
| 428 | if (@available(iOS 12.0, tvOS 12.0, *)) { | ||
| 429 | textField.textContentType = UITextContentTypeOneTimeCode; | ||
| 430 | } else { | ||
| 431 | textField.textContentType = nil; | ||
| 432 | } | ||
| 433 | break; | ||
| 434 | } | ||
| 435 | |||
| 436 | switch (SDL_GetTextInputCapitalization(props)) { | ||
| 437 | default: | ||
| 438 | case SDL_CAPITALIZE_NONE: | ||
| 439 | textField.autocapitalizationType = UITextAutocapitalizationTypeNone; | ||
| 440 | break; | ||
| 441 | case SDL_CAPITALIZE_LETTERS: | ||
| 442 | textField.autocapitalizationType = UITextAutocapitalizationTypeAllCharacters; | ||
| 443 | break; | ||
| 444 | case SDL_CAPITALIZE_WORDS: | ||
| 445 | textField.autocapitalizationType = UITextAutocapitalizationTypeWords; | ||
| 446 | break; | ||
| 447 | case SDL_CAPITALIZE_SENTENCES: | ||
| 448 | textField.autocapitalizationType = UITextAutocapitalizationTypeSentences; | ||
| 449 | break; | ||
| 450 | } | ||
| 451 | |||
| 452 | if (SDL_GetTextInputAutocorrect(props)) { | ||
| 453 | textField.autocorrectionType = UITextAutocorrectionTypeYes; | ||
| 454 | textField.spellCheckingType = UITextSpellCheckingTypeYes; | ||
| 455 | } else { | ||
| 456 | textField.autocorrectionType = UITextAutocorrectionTypeNo; | ||
| 457 | textField.spellCheckingType = UITextSpellCheckingTypeNo; | ||
| 458 | } | ||
| 459 | |||
| 460 | if (SDL_GetTextInputMultiline(props)) { | ||
| 461 | textField.enablesReturnKeyAutomatically = YES; | ||
| 462 | } else { | ||
| 463 | textField.enablesReturnKeyAutomatically = NO; | ||
| 464 | } | ||
| 465 | |||
| 466 | if (!textField.window) { | ||
| 467 | /* textField has not been added to the view yet, | ||
| 468 | we don't have to do anything. */ | ||
| 469 | return; | ||
| 470 | } | ||
| 471 | |||
| 472 | // the text field needs to be re-added to the view in order to update correctly. | ||
| 473 | UIView *superview = textField.superview; | ||
| 474 | [textField removeFromSuperview]; | ||
| 475 | [superview addSubview:textField]; | ||
| 476 | |||
| 477 | if (SDL_TextInputActive(window)) { | ||
| 478 | [textField becomeFirstResponder]; | ||
| 479 | } | ||
| 480 | } | ||
| 481 | |||
| 482 | /* requests the SDL text field to become focused and accept text input. | ||
| 483 | * also shows the onscreen virtual keyboard if no hardware keyboard is attached. */ | ||
| 484 | - (bool)startTextInput | ||
| 485 | { | ||
| 486 | textFieldFocused = YES; | ||
| 487 | if (!textField.window) { | ||
| 488 | /* textField has not been added to the view yet, | ||
| 489 | * we will try again when that happens. */ | ||
| 490 | return true; | ||
| 491 | } | ||
| 492 | |||
| 493 | return [textField becomeFirstResponder]; | ||
| 494 | } | ||
| 495 | |||
| 496 | /* requests the SDL text field to lose focus and stop accepting text input. | ||
| 497 | * also hides the onscreen virtual keyboard if no hardware keyboard is attached. */ | ||
| 498 | - (bool)stopTextInput | ||
| 499 | { | ||
| 500 | textFieldFocused = NO; | ||
| 501 | if (!textField.window) { | ||
| 502 | /* textField has not been added to the view yet, | ||
| 503 | * we will try again when that happens. */ | ||
| 504 | return true; | ||
| 505 | } | ||
| 506 | |||
| 507 | [self resetTextState]; | ||
| 508 | return [textField resignFirstResponder]; | ||
| 509 | } | ||
| 510 | |||
| 511 | - (void)keyboardWillShow:(NSNotification *)notification | ||
| 512 | { | ||
| 513 | #ifndef SDL_PLATFORM_TVOS | ||
| 514 | CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue]; | ||
| 515 | |||
| 516 | /* The keyboard rect is in the coordinate space of the screen/window, but we | ||
| 517 | * want its height in the coordinate space of the view. */ | ||
| 518 | kbrect = [self.view convertRect:kbrect fromView:nil]; | ||
| 519 | |||
| 520 | [self setKeyboardHeight:(int)kbrect.size.height]; | ||
| 521 | #endif | ||
| 522 | |||
| 523 | /* A keyboard hide transition has been interrupted with a show (keyboardWillHide has been called but keyboardDidHide didn't). | ||
| 524 | * since text input was stopped by the hide, we have to start it again. */ | ||
| 525 | if (hidingKeyboard) { | ||
| 526 | SDL_StartTextInput(window); | ||
| 527 | hidingKeyboard = NO; | ||
| 528 | } | ||
| 529 | } | ||
| 530 | |||
| 531 | - (void)keyboardWillHide:(NSNotification *)notification | ||
| 532 | { | ||
| 533 | hidingKeyboard = YES; | ||
| 534 | [self setKeyboardHeight:0]; | ||
| 535 | |||
| 536 | /* When the user dismisses the software keyboard by the "hide" button in the bottom right corner, | ||
| 537 | * we want to reflect that on SDL_TextInputActive by calling SDL_StopTextInput...on certain conditions */ | ||
| 538 | if (SDL_TextInputActive(window) | ||
| 539 | /* keyboardWillHide gets called when a hardware keyboard is attached, | ||
| 540 | * keep text input state active if hiding while there is a hardware keyboard. | ||
| 541 | * if the hardware keyboard gets detached, the software keyboard will appear anyway. */ | ||
| 542 | && !SDL_HasKeyboard() | ||
| 543 | /* When the device changes orientation, a sequence of hide and show transitions are triggered. | ||
| 544 | * keep text input state active in this case. */ | ||
| 545 | && !rotatingOrientation) { | ||
| 546 | SDL_StopTextInput(window); | ||
| 547 | } | ||
| 548 | } | ||
| 549 | |||
| 550 | - (void)keyboardDidHide:(NSNotification *)notification | ||
| 551 | { | ||
| 552 | hidingKeyboard = NO; | ||
| 553 | } | ||
| 554 | |||
| 555 | - (void)textFieldTextDidChange:(NSNotification *)notification | ||
| 556 | { | ||
| 557 | if (textField.markedTextRange == nil) { | ||
| 558 | NSUInteger compareLength = SDL_min(textField.text.length, committedText.length); | ||
| 559 | NSUInteger matchLength; | ||
| 560 | |||
| 561 | // Backspace over characters that are no longer in the string | ||
| 562 | for (matchLength = 0; matchLength < compareLength; ++matchLength) { | ||
| 563 | if ([committedText characterAtIndex:matchLength] != [textField.text characterAtIndex:matchLength]) { | ||
| 564 | break; | ||
| 565 | } | ||
| 566 | } | ||
| 567 | if (matchLength < committedText.length) { | ||
| 568 | size_t deleteLength = SDL_utf8strlen([[committedText substringFromIndex:matchLength] UTF8String]); | ||
| 569 | while (deleteLength > 0) { | ||
| 570 | // Send distinct down and up events for each backspace action | ||
| 571 | SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_BACKSPACE, true); | ||
| 572 | SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_BACKSPACE, false); | ||
| 573 | --deleteLength; | ||
| 574 | } | ||
| 575 | } | ||
| 576 | |||
| 577 | if (matchLength < textField.text.length) { | ||
| 578 | NSString *pendingText = [textField.text substringFromIndex:matchLength]; | ||
| 579 | if (!SDL_HardwareKeyboardKeyPressed()) { | ||
| 580 | /* Go through all the characters in the string we've been sent and | ||
| 581 | * convert them to key presses */ | ||
| 582 | NSUInteger i; | ||
| 583 | for (i = 0; i < pendingText.length; i++) { | ||
| 584 | SDL_SendKeyboardUnicodeKey(0, [pendingText characterAtIndex:i]); | ||
| 585 | } | ||
| 586 | } | ||
| 587 | SDL_SendKeyboardText([pendingText UTF8String]); | ||
| 588 | } | ||
| 589 | committedText = textField.text; | ||
| 590 | } | ||
| 591 | } | ||
| 592 | |||
| 593 | - (void)updateKeyboard | ||
| 594 | { | ||
| 595 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *) window->internal; | ||
| 596 | |||
| 597 | CGAffineTransform t = self.view.transform; | ||
| 598 | CGPoint offset = CGPointMake(0.0, 0.0); | ||
| 599 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 600 | CGRect frame = UIKit_ComputeViewFrame(window); | ||
| 601 | #else | ||
| 602 | CGRect frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen); | ||
| 603 | #endif | ||
| 604 | |||
| 605 | if (self.keyboardHeight && self.textInputRect.h) { | ||
| 606 | int rectbottom = (int)(self.textInputRect.y + self.textInputRect.h); | ||
| 607 | int keybottom = (int)(self.view.bounds.size.height - self.keyboardHeight); | ||
| 608 | if (keybottom < rectbottom) { | ||
| 609 | offset.y = keybottom - rectbottom; | ||
| 610 | } | ||
| 611 | } | ||
| 612 | |||
| 613 | /* Apply this view's transform (except any translation) to the offset, in | ||
| 614 | * order to orient it correctly relative to the frame's coordinate space. */ | ||
| 615 | t.tx = 0.0; | ||
| 616 | t.ty = 0.0; | ||
| 617 | offset = CGPointApplyAffineTransform(offset, t); | ||
| 618 | |||
| 619 | // Apply the updated offset to the view's frame. | ||
| 620 | frame.origin.x += offset.x; | ||
| 621 | frame.origin.y += offset.y; | ||
| 622 | |||
| 623 | self.view.frame = frame; | ||
| 624 | } | ||
| 625 | |||
| 626 | - (void)setKeyboardHeight:(int)height | ||
| 627 | { | ||
| 628 | keyboardHeight = height; | ||
| 629 | [self updateKeyboard]; | ||
| 630 | } | ||
| 631 | |||
| 632 | // UITextFieldDelegate method. Invoked when user types something. | ||
| 633 | - (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string | ||
| 634 | { | ||
| 635 | if (textField.markedTextRange == nil) { | ||
| 636 | if (textField.text.length < 16) { | ||
| 637 | [self resetTextState]; | ||
| 638 | } | ||
| 639 | } | ||
| 640 | return YES; | ||
| 641 | } | ||
| 642 | |||
| 643 | // Terminates the editing session | ||
| 644 | - (BOOL)textFieldShouldReturn:(UITextField *)_textField | ||
| 645 | { | ||
| 646 | SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_RETURN); | ||
| 647 | if (textFieldFocused && | ||
| 648 | SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, false)) { | ||
| 649 | SDL_StopTextInput(window); | ||
| 650 | } | ||
| 651 | return YES; | ||
| 652 | } | ||
| 653 | |||
| 654 | - (void)resetTextState | ||
| 655 | { | ||
| 656 | textField.text = obligateForBackspace; | ||
| 657 | committedText = textField.text; | ||
| 658 | } | ||
| 659 | |||
| 660 | #endif | ||
| 661 | |||
| 662 | @end | ||
| 663 | |||
| 664 | // iPhone keyboard addition functions | ||
| 665 | #ifdef SDL_IPHONE_KEYBOARD | ||
| 666 | |||
| 667 | static SDL_uikitviewcontroller *GetWindowViewController(SDL_Window *window) | ||
| 668 | { | ||
| 669 | if (!window || !window->internal) { | ||
| 670 | SDL_SetError("Invalid window"); | ||
| 671 | return nil; | ||
| 672 | } | ||
| 673 | |||
| 674 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 675 | |||
| 676 | return data.viewcontroller; | ||
| 677 | } | ||
| 678 | |||
| 679 | bool UIKit_HasScreenKeyboardSupport(SDL_VideoDevice *_this) | ||
| 680 | { | ||
| 681 | return true; | ||
| 682 | } | ||
| 683 | |||
| 684 | bool UIKit_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) | ||
| 685 | { | ||
| 686 | @autoreleasepool { | ||
| 687 | SDL_uikitviewcontroller *vc = GetWindowViewController(window); | ||
| 688 | return [vc startTextInput]; | ||
| 689 | } | ||
| 690 | } | ||
| 691 | |||
| 692 | bool UIKit_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 693 | { | ||
| 694 | @autoreleasepool { | ||
| 695 | SDL_uikitviewcontroller *vc = GetWindowViewController(window); | ||
| 696 | return [vc stopTextInput]; | ||
| 697 | } | ||
| 698 | } | ||
| 699 | |||
| 700 | void UIKit_SetTextInputProperties(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) | ||
| 701 | { | ||
| 702 | @autoreleasepool { | ||
| 703 | SDL_uikitviewcontroller *vc = GetWindowViewController(window); | ||
| 704 | [vc setTextFieldProperties:props]; | ||
| 705 | } | ||
| 706 | } | ||
| 707 | |||
| 708 | bool UIKit_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 709 | { | ||
| 710 | @autoreleasepool { | ||
| 711 | SDL_uikitviewcontroller *vc = GetWindowViewController(window); | ||
| 712 | if (vc != nil) { | ||
| 713 | return vc.textFieldFocused; | ||
| 714 | } | ||
| 715 | return false; | ||
| 716 | } | ||
| 717 | } | ||
| 718 | |||
| 719 | bool UIKit_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 720 | { | ||
| 721 | @autoreleasepool { | ||
| 722 | SDL_uikitviewcontroller *vc = GetWindowViewController(window); | ||
| 723 | if (vc != nil) { | ||
| 724 | vc.textInputRect = window->text_input_rect; | ||
| 725 | |||
| 726 | if (vc.textFieldFocused) { | ||
| 727 | [vc updateKeyboard]; | ||
| 728 | } | ||
| 729 | } | ||
| 730 | } | ||
| 731 | return true; | ||
| 732 | } | ||
| 733 | |||
| 734 | #endif // SDL_IPHONE_KEYBOARD | ||
| 735 | |||
| 736 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.h new file mode 100644 index 0000000..6957670 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.h | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | /* | ||
| 23 | * @author Mark Callow, www.edgewise-consulting.com. Based on Jacob Lifshay's | ||
| 24 | * SDL_x11vulkan.h. | ||
| 25 | */ | ||
| 26 | |||
| 27 | #include "SDL_internal.h" | ||
| 28 | |||
| 29 | #ifndef SDL_uikitvulkan_h_ | ||
| 30 | #define SDL_uikitvulkan_h_ | ||
| 31 | |||
| 32 | #include "../SDL_vulkan_internal.h" | ||
| 33 | #include "../SDL_sysvideo.h" | ||
| 34 | |||
| 35 | #if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_UIKIT) | ||
| 36 | |||
| 37 | extern bool UIKit_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); | ||
| 38 | extern void UIKit_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); | ||
| 39 | extern char const* const* UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); | ||
| 40 | extern bool UIKit_Vulkan_CreateSurface(SDL_VideoDevice *_this, | ||
| 41 | SDL_Window *window, | ||
| 42 | VkInstance instance, | ||
| 43 | const struct VkAllocationCallbacks *allocator, | ||
| 44 | VkSurfaceKHR *surface); | ||
| 45 | extern void UIKit_Vulkan_DestroySurface(SDL_VideoDevice *_this, | ||
| 46 | VkInstance instance, | ||
| 47 | VkSurfaceKHR surface, | ||
| 48 | const struct VkAllocationCallbacks *allocator); | ||
| 49 | |||
| 50 | #endif | ||
| 51 | |||
| 52 | #endif // SDL_uikitvulkan_h_ | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.m new file mode 100644 index 0000000..332593b --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.m | |||
| @@ -0,0 +1,265 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | /* | ||
| 23 | * @author Mark Callow, www.edgewise-consulting.com. Based on Jacob Lifshay's | ||
| 24 | * SDL_x11vulkan.c. | ||
| 25 | */ | ||
| 26 | |||
| 27 | #include "SDL_internal.h" | ||
| 28 | |||
| 29 | #if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_UIKIT) | ||
| 30 | |||
| 31 | #include "SDL_uikitvideo.h" | ||
| 32 | #include "SDL_uikitwindow.h" | ||
| 33 | |||
| 34 | #include "SDL_uikitvulkan.h" | ||
| 35 | #include "SDL_uikitmetalview.h" | ||
| 36 | |||
| 37 | #include <dlfcn.h> | ||
| 38 | |||
| 39 | const char *defaultPaths[] = { | ||
| 40 | "libvulkan.dylib", | ||
| 41 | }; | ||
| 42 | |||
| 43 | /* Since libSDL is static, could use RTLD_SELF. Using RTLD_DEFAULT is future | ||
| 44 | * proofing. */ | ||
| 45 | #define DEFAULT_HANDLE RTLD_DEFAULT | ||
| 46 | |||
| 47 | bool UIKit_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) | ||
| 48 | { | ||
| 49 | VkExtensionProperties *extensions = NULL; | ||
| 50 | Uint32 extensionCount = 0; | ||
| 51 | bool hasSurfaceExtension = false; | ||
| 52 | bool hasMetalSurfaceExtension = false; | ||
| 53 | bool hasIOSSurfaceExtension = false; | ||
| 54 | PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL; | ||
| 55 | |||
| 56 | if (_this->vulkan_config.loader_handle) { | ||
| 57 | return SDL_SetError("Vulkan Portability library is already loaded."); | ||
| 58 | } | ||
| 59 | |||
| 60 | // Load the Vulkan loader library | ||
| 61 | if (!path) { | ||
| 62 | path = SDL_GetHint(SDL_HINT_VULKAN_LIBRARY); | ||
| 63 | } | ||
| 64 | |||
| 65 | if (!path) { | ||
| 66 | // Handle the case where Vulkan Portability is linked statically. | ||
| 67 | vkGetInstanceProcAddr = | ||
| 68 | (PFN_vkGetInstanceProcAddr)dlsym(DEFAULT_HANDLE, | ||
| 69 | "vkGetInstanceProcAddr"); | ||
| 70 | } | ||
| 71 | |||
| 72 | if (vkGetInstanceProcAddr) { | ||
| 73 | _this->vulkan_config.loader_handle = DEFAULT_HANDLE; | ||
| 74 | } else { | ||
| 75 | const char **paths; | ||
| 76 | const char *foundPath = NULL; | ||
| 77 | int numPaths; | ||
| 78 | int i; | ||
| 79 | |||
| 80 | if (path) { | ||
| 81 | paths = &path; | ||
| 82 | numPaths = 1; | ||
| 83 | } else { | ||
| 84 | // Look for the .dylib packaged with the application instead. | ||
| 85 | paths = defaultPaths; | ||
| 86 | numPaths = SDL_arraysize(defaultPaths); | ||
| 87 | } | ||
| 88 | |||
| 89 | for (i = 0; i < numPaths && _this->vulkan_config.loader_handle == NULL; i++) { | ||
| 90 | foundPath = paths[i]; | ||
| 91 | _this->vulkan_config.loader_handle = SDL_LoadObject(foundPath); | ||
| 92 | } | ||
| 93 | |||
| 94 | if (_this->vulkan_config.loader_handle == NULL) { | ||
| 95 | return SDL_SetError("Failed to load Vulkan Portability library"); | ||
| 96 | } | ||
| 97 | |||
| 98 | SDL_strlcpy(_this->vulkan_config.loader_path, path, | ||
| 99 | SDL_arraysize(_this->vulkan_config.loader_path)); | ||
| 100 | vkGetInstanceProcAddr = | ||
| 101 | (PFN_vkGetInstanceProcAddr)SDL_LoadFunction( | ||
| 102 | _this->vulkan_config.loader_handle, | ||
| 103 | "vkGetInstanceProcAddr"); | ||
| 104 | } | ||
| 105 | |||
| 106 | if (!vkGetInstanceProcAddr) { | ||
| 107 | SDL_SetError("Failed to find %s in either executable or %s: %s", | ||
| 108 | "vkGetInstanceProcAddr", | ||
| 109 | "linked Vulkan Portability library", | ||
| 110 | (const char *)dlerror()); | ||
| 111 | goto fail; | ||
| 112 | } | ||
| 113 | |||
| 114 | _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr; | ||
| 115 | _this->vulkan_config.vkEnumerateInstanceExtensionProperties = | ||
| 116 | (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)( | ||
| 117 | VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); | ||
| 118 | |||
| 119 | if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) { | ||
| 120 | SDL_SetError("No vkEnumerateInstanceExtensionProperties found."); | ||
| 121 | goto fail; | ||
| 122 | } | ||
| 123 | |||
| 124 | extensions = SDL_Vulkan_CreateInstanceExtensionsList( | ||
| 125 | (PFN_vkEnumerateInstanceExtensionProperties) | ||
| 126 | _this->vulkan_config.vkEnumerateInstanceExtensionProperties, | ||
| 127 | &extensionCount); | ||
| 128 | |||
| 129 | if (!extensions) { | ||
| 130 | goto fail; | ||
| 131 | } | ||
| 132 | |||
| 133 | for (Uint32 i = 0; i < extensionCount; i++) { | ||
| 134 | if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) { | ||
| 135 | hasSurfaceExtension = true; | ||
| 136 | } else if (SDL_strcmp(VK_EXT_METAL_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) { | ||
| 137 | hasMetalSurfaceExtension = true; | ||
| 138 | } else if (SDL_strcmp(VK_MVK_IOS_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) { | ||
| 139 | hasIOSSurfaceExtension = true; | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | SDL_free(extensions); | ||
| 144 | |||
| 145 | if (!hasSurfaceExtension) { | ||
| 146 | SDL_SetError("Installed Vulkan Portability doesn't implement the " VK_KHR_SURFACE_EXTENSION_NAME " extension"); | ||
| 147 | goto fail; | ||
| 148 | } else if (!hasMetalSurfaceExtension && !hasIOSSurfaceExtension) { | ||
| 149 | SDL_SetError("Installed Vulkan Portability doesn't implement the " VK_EXT_METAL_SURFACE_EXTENSION_NAME " or " VK_MVK_IOS_SURFACE_EXTENSION_NAME " extensions"); | ||
| 150 | goto fail; | ||
| 151 | } | ||
| 152 | |||
| 153 | return true; | ||
| 154 | |||
| 155 | fail: | ||
| 156 | _this->vulkan_config.loader_handle = NULL; | ||
| 157 | return false; | ||
| 158 | } | ||
| 159 | |||
| 160 | void UIKit_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) | ||
| 161 | { | ||
| 162 | if (_this->vulkan_config.loader_handle) { | ||
| 163 | if (_this->vulkan_config.loader_handle != DEFAULT_HANDLE) { | ||
| 164 | SDL_UnloadObject(_this->vulkan_config.loader_handle); | ||
| 165 | } | ||
| 166 | _this->vulkan_config.loader_handle = NULL; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | char const* const* UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, | ||
| 171 | Uint32 *count) | ||
| 172 | { | ||
| 173 | static const char *const extensionsForUIKit[] = { | ||
| 174 | VK_KHR_SURFACE_EXTENSION_NAME, VK_EXT_METAL_SURFACE_EXTENSION_NAME | ||
| 175 | }; | ||
| 176 | if(count) { | ||
| 177 | *count = SDL_arraysize(extensionsForUIKit); | ||
| 178 | } | ||
| 179 | return extensionsForUIKit; | ||
| 180 | } | ||
| 181 | |||
| 182 | bool UIKit_Vulkan_CreateSurface(SDL_VideoDevice *_this, | ||
| 183 | SDL_Window *window, | ||
| 184 | VkInstance instance, | ||
| 185 | const struct VkAllocationCallbacks *allocator, | ||
| 186 | VkSurfaceKHR *surface) | ||
| 187 | { | ||
| 188 | PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = | ||
| 189 | (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr; | ||
| 190 | PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT = | ||
| 191 | (PFN_vkCreateMetalSurfaceEXT)vkGetInstanceProcAddr( | ||
| 192 | (VkInstance)instance, | ||
| 193 | "vkCreateMetalSurfaceEXT"); | ||
| 194 | PFN_vkCreateIOSSurfaceMVK vkCreateIOSSurfaceMVK = | ||
| 195 | (PFN_vkCreateIOSSurfaceMVK)vkGetInstanceProcAddr( | ||
| 196 | (VkInstance)instance, | ||
| 197 | "vkCreateIOSSurfaceMVK"); | ||
| 198 | VkResult result; | ||
| 199 | SDL_MetalView metalview; | ||
| 200 | |||
| 201 | if (!_this->vulkan_config.loader_handle) { | ||
| 202 | return SDL_SetError("Vulkan is not loaded"); | ||
| 203 | } | ||
| 204 | |||
| 205 | if (!vkCreateMetalSurfaceEXT && !vkCreateIOSSurfaceMVK) { | ||
| 206 | return SDL_SetError(VK_EXT_METAL_SURFACE_EXTENSION_NAME " or " VK_MVK_IOS_SURFACE_EXTENSION_NAME | ||
| 207 | " extensions are not enabled in the Vulkan instance."); | ||
| 208 | } | ||
| 209 | |||
| 210 | metalview = UIKit_Metal_CreateView(_this, window); | ||
| 211 | if (metalview == NULL) { | ||
| 212 | return false; | ||
| 213 | } | ||
| 214 | |||
| 215 | if (vkCreateMetalSurfaceEXT) { | ||
| 216 | VkMetalSurfaceCreateInfoEXT createInfo = {}; | ||
| 217 | createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; | ||
| 218 | createInfo.pNext = NULL; | ||
| 219 | createInfo.flags = 0; | ||
| 220 | createInfo.pLayer = (__bridge const CAMetalLayer *) | ||
| 221 | UIKit_Metal_GetLayer(_this, metalview); | ||
| 222 | result = vkCreateMetalSurfaceEXT(instance, &createInfo, allocator, surface); | ||
| 223 | if (result != VK_SUCCESS) { | ||
| 224 | UIKit_Metal_DestroyView(_this, metalview); | ||
| 225 | return SDL_SetError("vkCreateMetalSurfaceEXT failed: %s", SDL_Vulkan_GetResultString(result)); | ||
| 226 | } | ||
| 227 | } else { | ||
| 228 | VkIOSSurfaceCreateInfoMVK createInfo = {}; | ||
| 229 | createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK; | ||
| 230 | createInfo.pNext = NULL; | ||
| 231 | createInfo.flags = 0; | ||
| 232 | createInfo.pView = (const void *)metalview; | ||
| 233 | result = vkCreateIOSSurfaceMVK(instance, &createInfo, | ||
| 234 | allocator, surface); | ||
| 235 | if (result != VK_SUCCESS) { | ||
| 236 | UIKit_Metal_DestroyView(_this, metalview); | ||
| 237 | return SDL_SetError("vkCreateIOSSurfaceMVK failed: %s", SDL_Vulkan_GetResultString(result)); | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | /* Unfortunately there's no SDL_Vulkan_DestroySurface function we can call | ||
| 242 | * Metal_DestroyView from. Right now the metal view's ref count is +2 (one | ||
| 243 | * from returning a new view object in CreateView, and one because it's | ||
| 244 | * a subview of the window.) If we release the view here to make it +1, it | ||
| 245 | * will be destroyed when the window is destroyed. | ||
| 246 | * | ||
| 247 | * TODO: Now that we have SDL_Vulkan_DestroySurface someone with enough | ||
| 248 | * knowledge of Metal can proceed. */ | ||
| 249 | CFBridgingRelease(metalview); | ||
| 250 | |||
| 251 | return true; | ||
| 252 | } | ||
| 253 | |||
| 254 | void UIKit_Vulkan_DestroySurface(SDL_VideoDevice *_this, | ||
| 255 | VkInstance instance, | ||
| 256 | VkSurfaceKHR surface, | ||
| 257 | const struct VkAllocationCallbacks *allocator) | ||
| 258 | { | ||
| 259 | if (_this->vulkan_config.loader_handle) { | ||
| 260 | SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator); | ||
| 261 | // TODO: Add CFBridgingRelease(metalview) here perhaps? | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | #endif | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.h new file mode 100644 index 0000000..da1fb63 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.h | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #ifndef SDL_uikitwindow_h_ | ||
| 22 | #define SDL_uikitwindow_h_ | ||
| 23 | |||
| 24 | #include "../SDL_sysvideo.h" | ||
| 25 | #import "SDL_uikitvideo.h" | ||
| 26 | #import "SDL_uikitview.h" | ||
| 27 | #import "SDL_uikitviewcontroller.h" | ||
| 28 | |||
| 29 | extern bool UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); | ||
| 30 | extern void UIKit_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 31 | extern void UIKit_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 32 | extern void UIKit_HideWindow(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 33 | extern void UIKit_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 34 | extern void UIKit_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered); | ||
| 35 | extern SDL_FullscreenResult UIKit_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen); | ||
| 36 | extern void UIKit_UpdatePointerLock(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 37 | extern void UIKit_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); | ||
| 38 | extern void UIKit_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h); | ||
| 39 | |||
| 40 | extern NSUInteger UIKit_GetSupportedOrientations(SDL_Window *window); | ||
| 41 | |||
| 42 | #define SDL_METALVIEW_TAG 255 | ||
| 43 | |||
| 44 | @class UIWindow; | ||
| 45 | |||
| 46 | @interface SDL_UIKitWindowData : NSObject | ||
| 47 | |||
| 48 | @property(nonatomic, strong) UIWindow *uiwindow; | ||
| 49 | @property(nonatomic, strong) SDL_uikitviewcontroller *viewcontroller; | ||
| 50 | |||
| 51 | // Array of SDL_uikitviews owned by this window. | ||
| 52 | @property(nonatomic, copy) NSMutableArray *views; | ||
| 53 | |||
| 54 | @end | ||
| 55 | |||
| 56 | #endif // SDL_uikitwindow_h_ | ||
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.m new file mode 100644 index 0000000..2b258b1 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.m | |||
| @@ -0,0 +1,471 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_VIDEO_DRIVER_UIKIT | ||
| 24 | |||
| 25 | #include "../SDL_sysvideo.h" | ||
| 26 | #include "../SDL_pixels_c.h" | ||
| 27 | #include "../../events/SDL_events_c.h" | ||
| 28 | |||
| 29 | #include "SDL_uikitvideo.h" | ||
| 30 | #include "SDL_uikitevents.h" | ||
| 31 | #include "SDL_uikitmodes.h" | ||
| 32 | #include "SDL_uikitwindow.h" | ||
| 33 | #include "SDL_uikitappdelegate.h" | ||
| 34 | #include "SDL_uikitview.h" | ||
| 35 | #include "SDL_uikitopenglview.h" | ||
| 36 | |||
| 37 | #include <Foundation/Foundation.h> | ||
| 38 | |||
| 39 | @implementation SDL_UIKitWindowData | ||
| 40 | |||
| 41 | @synthesize uiwindow; | ||
| 42 | @synthesize viewcontroller; | ||
| 43 | @synthesize views; | ||
| 44 | |||
| 45 | - (instancetype)init | ||
| 46 | { | ||
| 47 | if ((self = [super init])) { | ||
| 48 | views = [NSMutableArray new]; | ||
| 49 | } | ||
| 50 | |||
| 51 | return self; | ||
| 52 | } | ||
| 53 | |||
| 54 | @end | ||
| 55 | |||
| 56 | @interface SDL_uikitwindow : UIWindow | ||
| 57 | |||
| 58 | - (void)layoutSubviews; | ||
| 59 | |||
| 60 | @end | ||
| 61 | |||
| 62 | @implementation SDL_uikitwindow | ||
| 63 | |||
| 64 | - (void)layoutSubviews | ||
| 65 | { | ||
| 66 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 67 | // Workaround to fix window orientation issues in iOS 8. | ||
| 68 | /* As of July 1 2019, I haven't been able to reproduce any orientation | ||
| 69 | * issues with this disabled on iOS 12. The issue this is meant to fix might | ||
| 70 | * only happen on iOS 8, or it might have been fixed another way with other | ||
| 71 | * code... This code prevents split view (iOS 9+) from working on iPads, so | ||
| 72 | * we want to avoid using it if possible. */ | ||
| 73 | if (!UIKit_IsSystemVersionAtLeast(9.0)) { | ||
| 74 | self.frame = self.screen.bounds; | ||
| 75 | } | ||
| 76 | #endif | ||
| 77 | [super layoutSubviews]; | ||
| 78 | } | ||
| 79 | |||
| 80 | @end | ||
| 81 | |||
| 82 | static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow *uiwindow, bool created) | ||
| 83 | { | ||
| 84 | SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window); | ||
| 85 | SDL_UIKitDisplayData *displaydata = (__bridge SDL_UIKitDisplayData *)display->internal; | ||
| 86 | SDL_uikitview *view; | ||
| 87 | |||
| 88 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 89 | CGRect frame = UIKit_ComputeViewFrame(window); | ||
| 90 | #else | ||
| 91 | CGRect frame = UIKit_ComputeViewFrame(window, displaydata.uiscreen); | ||
| 92 | #endif | ||
| 93 | |||
| 94 | int width = (int)frame.size.width; | ||
| 95 | int height = (int)frame.size.height; | ||
| 96 | |||
| 97 | SDL_UIKitWindowData *data = [[SDL_UIKitWindowData alloc] init]; | ||
| 98 | if (!data) { | ||
| 99 | return SDL_OutOfMemory(); | ||
| 100 | } | ||
| 101 | |||
| 102 | window->internal = (SDL_WindowData *)CFBridgingRetain(data); | ||
| 103 | |||
| 104 | data.uiwindow = uiwindow; | ||
| 105 | |||
| 106 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 107 | if (displaydata.uiscreen != [UIScreen mainScreen]) { | ||
| 108 | window->flags &= ~SDL_WINDOW_RESIZABLE; // window is NEVER resizable | ||
| 109 | window->flags &= ~SDL_WINDOW_INPUT_FOCUS; // never has input focus | ||
| 110 | window->flags |= SDL_WINDOW_BORDERLESS; // never has a status bar. | ||
| 111 | } | ||
| 112 | #endif | ||
| 113 | |||
| 114 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 115 | if (displaydata.uiscreen == [UIScreen mainScreen]) { | ||
| 116 | NSUInteger orients = UIKit_GetSupportedOrientations(window); | ||
| 117 | BOOL supportsLandscape = (orients & UIInterfaceOrientationMaskLandscape) != 0; | ||
| 118 | BOOL supportsPortrait = (orients & (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown)) != 0; | ||
| 119 | |||
| 120 | // Make sure the width/height are oriented correctly | ||
| 121 | if ((width > height && !supportsLandscape) || (height > width && !supportsPortrait)) { | ||
| 122 | int temp = width; | ||
| 123 | width = height; | ||
| 124 | height = temp; | ||
| 125 | } | ||
| 126 | } | ||
| 127 | #endif // !SDL_PLATFORM_TVOS | ||
| 128 | |||
| 129 | #if 0 // Don't set the x/y position, it's already placed on a display | ||
| 130 | window->x = 0; | ||
| 131 | window->y = 0; | ||
| 132 | #endif | ||
| 133 | window->w = width; | ||
| 134 | window->h = height; | ||
| 135 | |||
| 136 | /* The View Controller will handle rotating the view when the device | ||
| 137 | * orientation changes. This will trigger resize events, if appropriate. */ | ||
| 138 | data.viewcontroller = [[SDL_uikitviewcontroller alloc] initWithSDLWindow:window]; | ||
| 139 | |||
| 140 | /* The window will initially contain a generic view so resizes, touch events, | ||
| 141 | * etc. can be handled without an active OpenGL view/context. */ | ||
| 142 | view = [[SDL_uikitview alloc] initWithFrame:frame]; | ||
| 143 | |||
| 144 | /* Sets this view as the controller's view, and adds the view to the window | ||
| 145 | * hierarchy. */ | ||
| 146 | [view setSDLWindow:window]; | ||
| 147 | |||
| 148 | SDL_PropertiesID props = SDL_GetWindowProperties(window); | ||
| 149 | SDL_SetPointerProperty(props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, (__bridge void *)data.uiwindow); | ||
| 150 | SDL_SetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER, SDL_METALVIEW_TAG); | ||
| 151 | |||
| 152 | return true; | ||
| 153 | } | ||
| 154 | |||
| 155 | bool UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) | ||
| 156 | { | ||
| 157 | @autoreleasepool { | ||
| 158 | SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window); | ||
| 159 | SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal; | ||
| 160 | SDL_Window *other; | ||
| 161 | |||
| 162 | // We currently only handle a single window per display on iOS | ||
| 163 | for (other = _this->windows; other; other = other->next) { | ||
| 164 | if (other != window && SDL_GetVideoDisplayForWindow(other) == display) { | ||
| 165 | return SDL_SetError("Only one window allowed per display."); | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | /* If monitor has a resolution of 0x0 (hasn't been explicitly set by the | ||
| 170 | * user, so it's in standby), try to force the display to a resolution | ||
| 171 | * that most closely matches the desired window size. */ | ||
| 172 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 173 | const CGSize origsize = data.uiscreen.currentMode.size; | ||
| 174 | if ((origsize.width == 0.0f) && (origsize.height == 0.0f)) { | ||
| 175 | SDL_DisplayMode bestmode; | ||
| 176 | bool include_high_density_modes = false; | ||
| 177 | if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { | ||
| 178 | include_high_density_modes = true; | ||
| 179 | } | ||
| 180 | if (SDL_GetClosestFullscreenDisplayMode(display->id, window->w, window->h, 0.0f, include_high_density_modes, &bestmode)) { | ||
| 181 | SDL_UIKitDisplayModeData *modedata = (__bridge SDL_UIKitDisplayModeData *)bestmode.internal; | ||
| 182 | [data.uiscreen setCurrentMode:modedata.uiscreenmode]; | ||
| 183 | |||
| 184 | /* desktop_mode doesn't change here (the higher level will | ||
| 185 | * use it to set all the screens back to their defaults | ||
| 186 | * upon window destruction, SDL_Quit(), etc. */ | ||
| 187 | SDL_SetCurrentDisplayMode(display, &bestmode); | ||
| 188 | } | ||
| 189 | } | ||
| 190 | |||
| 191 | if (data.uiscreen == [UIScreen mainScreen]) { | ||
| 192 | if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) { | ||
| 193 | [UIApplication sharedApplication].statusBarHidden = YES; | ||
| 194 | } else { | ||
| 195 | [UIApplication sharedApplication].statusBarHidden = NO; | ||
| 196 | } | ||
| 197 | } | ||
| 198 | #endif // !SDL_PLATFORM_TVOS | ||
| 199 | |||
| 200 | // ignore the size user requested, and make a fullscreen window | ||
| 201 | // !!! FIXME: can we have a smaller view? | ||
| 202 | #ifdef SDL_PLATFORM_VISIONOS | ||
| 203 | UIWindow *uiwindow = [[SDL_uikitwindow alloc] initWithFrame:CGRectMake(window->x, window->y, window->w, window->h)]; | ||
| 204 | #else | ||
| 205 | UIWindow *uiwindow = [[SDL_uikitwindow alloc] initWithFrame:data.uiscreen.bounds]; | ||
| 206 | #endif | ||
| 207 | |||
| 208 | // put the window on an external display if appropriate. | ||
| 209 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 210 | if (data.uiscreen != [UIScreen mainScreen]) { | ||
| 211 | [uiwindow setScreen:data.uiscreen]; | ||
| 212 | } | ||
| 213 | #endif | ||
| 214 | |||
| 215 | if (!SetupWindowData(_this, window, uiwindow, true)) { | ||
| 216 | return false; | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | return true; | ||
| 221 | } | ||
| 222 | |||
| 223 | void UIKit_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 224 | { | ||
| 225 | @autoreleasepool { | ||
| 226 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 227 | data.viewcontroller.title = @(window->title); | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | void UIKit_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 232 | { | ||
| 233 | @autoreleasepool { | ||
| 234 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 235 | [data.uiwindow makeKeyAndVisible]; | ||
| 236 | |||
| 237 | // Make this window the current mouse focus for touch input | ||
| 238 | SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window); | ||
| 239 | SDL_UIKitDisplayData *displaydata = (__bridge SDL_UIKitDisplayData *)display->internal; | ||
| 240 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 241 | if (displaydata.uiscreen == [UIScreen mainScreen]) | ||
| 242 | #endif | ||
| 243 | { | ||
| 244 | SDL_SetMouseFocus(window); | ||
| 245 | SDL_SetKeyboardFocus(window); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | void UIKit_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 251 | { | ||
| 252 | @autoreleasepool { | ||
| 253 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 254 | data.uiwindow.hidden = YES; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | void UIKit_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 259 | { | ||
| 260 | #if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2) | ||
| 261 | /* We don't currently offer a concept of "raising" the SDL window, since | ||
| 262 | * we only allow one per display, in the iOS fashion. | ||
| 263 | * However, we use this entry point to rebind the context to the view | ||
| 264 | * during OnWindowRestored processing. */ | ||
| 265 | _this->GL_MakeCurrent(_this, _this->current_glwin, _this->current_glctx); | ||
| 266 | #endif | ||
| 267 | } | ||
| 268 | |||
| 269 | static void UIKit_UpdateWindowBorder(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 270 | { | ||
| 271 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 272 | SDL_uikitviewcontroller *viewcontroller = data.viewcontroller; | ||
| 273 | |||
| 274 | #if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) | ||
| 275 | if (data.uiwindow.screen == [UIScreen mainScreen]) { | ||
| 276 | if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) { | ||
| 277 | [UIApplication sharedApplication].statusBarHidden = YES; | ||
| 278 | } else { | ||
| 279 | [UIApplication sharedApplication].statusBarHidden = NO; | ||
| 280 | } | ||
| 281 | |||
| 282 | [viewcontroller setNeedsStatusBarAppearanceUpdate]; | ||
| 283 | } | ||
| 284 | |||
| 285 | // Update the view's frame to account for the status bar change. | ||
| 286 | viewcontroller.view.frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen); | ||
| 287 | #endif // !SDL_PLATFORM_TVOS | ||
| 288 | |||
| 289 | #ifdef SDL_IPHONE_KEYBOARD | ||
| 290 | // Make sure the view is offset correctly when the keyboard is visible. | ||
| 291 | [viewcontroller updateKeyboard]; | ||
| 292 | #endif | ||
| 293 | |||
| 294 | [viewcontroller.view setNeedsLayout]; | ||
| 295 | [viewcontroller.view layoutIfNeeded]; | ||
| 296 | } | ||
| 297 | |||
| 298 | void UIKit_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered) | ||
| 299 | { | ||
| 300 | @autoreleasepool { | ||
| 301 | if (bordered) { | ||
| 302 | window->flags &= ~SDL_WINDOW_BORDERLESS; | ||
| 303 | } else { | ||
| 304 | window->flags |= SDL_WINDOW_BORDERLESS; | ||
| 305 | } | ||
| 306 | UIKit_UpdateWindowBorder(_this, window); | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | SDL_FullscreenResult UIKit_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen) | ||
| 311 | { | ||
| 312 | @autoreleasepool { | ||
| 313 | SDL_SendWindowEvent(window, fullscreen ? SDL_EVENT_WINDOW_ENTER_FULLSCREEN : SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); | ||
| 314 | UIKit_UpdateWindowBorder(_this, window); | ||
| 315 | } | ||
| 316 | return SDL_FULLSCREEN_SUCCEEDED; | ||
| 317 | } | ||
| 318 | |||
| 319 | void UIKit_UpdatePointerLock(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 320 | { | ||
| 321 | #ifndef SDL_PLATFORM_TVOS | ||
| 322 | @autoreleasepool { | ||
| 323 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 324 | SDL_uikitviewcontroller *viewcontroller = data.viewcontroller; | ||
| 325 | if (@available(iOS 14.0, *)) { | ||
| 326 | [viewcontroller setNeedsUpdateOfPrefersPointerLocked]; | ||
| 327 | } | ||
| 328 | } | ||
| 329 | #endif // !SDL_PLATFORM_TVOS | ||
| 330 | } | ||
| 331 | |||
| 332 | void UIKit_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 333 | { | ||
| 334 | @autoreleasepool { | ||
| 335 | if (window->internal != NULL) { | ||
| 336 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 337 | NSArray *views = nil; | ||
| 338 | |||
| 339 | [data.viewcontroller stopAnimation]; | ||
| 340 | |||
| 341 | /* Detach all views from this window. We use a copy of the array | ||
| 342 | * because setSDLWindow will remove the object from the original | ||
| 343 | * array, which would be undesirable if we were iterating over it. */ | ||
| 344 | views = [data.views copy]; | ||
| 345 | for (SDL_uikitview *view in views) { | ||
| 346 | [view setSDLWindow:NULL]; | ||
| 347 | } | ||
| 348 | |||
| 349 | /* iOS may still hold a reference to the window after we release it. | ||
| 350 | * We want to make sure the SDL view controller isn't accessed in | ||
| 351 | * that case, because it would contain an invalid pointer to the old | ||
| 352 | * SDL window. */ | ||
| 353 | data.uiwindow.rootViewController = nil; | ||
| 354 | data.uiwindow.hidden = YES; | ||
| 355 | |||
| 356 | CFRelease(window->internal); | ||
| 357 | window->internal = NULL; | ||
| 358 | } | ||
| 359 | } | ||
| 360 | } | ||
| 361 | |||
| 362 | void UIKit_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h) | ||
| 363 | { | ||
| 364 | @autoreleasepool { | ||
| 365 | SDL_UIKitWindowData *windata = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 366 | UIView *view = windata.viewcontroller.view; | ||
| 367 | CGSize size = view.bounds.size; | ||
| 368 | CGFloat scale = 1.0; | ||
| 369 | |||
| 370 | |||
| 371 | if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { | ||
| 372 | #ifndef SDL_PLATFORM_VISIONOS | ||
| 373 | scale = windata.uiwindow.screen.nativeScale; | ||
| 374 | #else | ||
| 375 | scale = 2.0; | ||
| 376 | #endif | ||
| 377 | } | ||
| 378 | |||
| 379 | |||
| 380 | /* Integer truncation of fractional values matches SDL_uikitmetalview and | ||
| 381 | * SDL_uikitopenglview. */ | ||
| 382 | *w = (int)(size.width * scale); | ||
| 383 | *h = (int)(size.height * scale); | ||
| 384 | } | ||
| 385 | } | ||
| 386 | |||
| 387 | #ifndef SDL_PLATFORM_TVOS | ||
| 388 | NSUInteger | ||
| 389 | UIKit_GetSupportedOrientations(SDL_Window *window) | ||
| 390 | { | ||
| 391 | const char *hint = SDL_GetHint(SDL_HINT_ORIENTATIONS); | ||
| 392 | NSUInteger validOrientations = UIInterfaceOrientationMaskAll; | ||
| 393 | NSUInteger orientationMask = 0; | ||
| 394 | |||
| 395 | @autoreleasepool { | ||
| 396 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 397 | UIApplication *app = [UIApplication sharedApplication]; | ||
| 398 | |||
| 399 | /* Get all possible valid orientations. If the app delegate doesn't tell | ||
| 400 | * us, we get the orientations from Info.plist via UIApplication. */ | ||
| 401 | if ([app.delegate respondsToSelector:@selector(application:supportedInterfaceOrientationsForWindow:)]) { | ||
| 402 | validOrientations = [app.delegate application:app supportedInterfaceOrientationsForWindow:data.uiwindow]; | ||
| 403 | } else { | ||
| 404 | validOrientations = [app supportedInterfaceOrientationsForWindow:data.uiwindow]; | ||
| 405 | } | ||
| 406 | |||
| 407 | if (hint != NULL) { | ||
| 408 | NSArray *orientations = [@(hint) componentsSeparatedByString:@" "]; | ||
| 409 | |||
| 410 | if ([orientations containsObject:@"LandscapeLeft"]) { | ||
| 411 | orientationMask |= UIInterfaceOrientationMaskLandscapeLeft; | ||
| 412 | } | ||
| 413 | if ([orientations containsObject:@"LandscapeRight"]) { | ||
| 414 | orientationMask |= UIInterfaceOrientationMaskLandscapeRight; | ||
| 415 | } | ||
| 416 | if ([orientations containsObject:@"Portrait"]) { | ||
| 417 | orientationMask |= UIInterfaceOrientationMaskPortrait; | ||
| 418 | } | ||
| 419 | if ([orientations containsObject:@"PortraitUpsideDown"]) { | ||
| 420 | orientationMask |= UIInterfaceOrientationMaskPortraitUpsideDown; | ||
| 421 | } | ||
| 422 | } | ||
| 423 | |||
| 424 | if (orientationMask == 0 && (window->flags & SDL_WINDOW_RESIZABLE)) { | ||
| 425 | // any orientation is okay. | ||
| 426 | orientationMask = UIInterfaceOrientationMaskAll; | ||
| 427 | } | ||
| 428 | |||
| 429 | if (orientationMask == 0) { | ||
| 430 | if (window->floating.w >= window->floating.h) { | ||
| 431 | orientationMask |= UIInterfaceOrientationMaskLandscape; | ||
| 432 | } | ||
| 433 | if (window->floating.h >= window->floating.w) { | ||
| 434 | orientationMask |= (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown); | ||
| 435 | } | ||
| 436 | } | ||
| 437 | |||
| 438 | // Don't allow upside-down orientation on phones, so answering calls is in the natural orientation | ||
| 439 | if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) { | ||
| 440 | orientationMask &= ~UIInterfaceOrientationMaskPortraitUpsideDown; | ||
| 441 | } | ||
| 442 | |||
| 443 | /* If none of the specified orientations are actually supported by the | ||
| 444 | * app, we'll revert to what the app supports. An exception would be | ||
| 445 | * thrown by the system otherwise. */ | ||
| 446 | if ((validOrientations & orientationMask) == 0) { | ||
| 447 | orientationMask = validOrientations; | ||
| 448 | } | ||
| 449 | } | ||
| 450 | |||
| 451 | return orientationMask; | ||
| 452 | } | ||
| 453 | #endif // !SDL_PLATFORM_TVOS | ||
| 454 | |||
| 455 | bool SDL_SetiOSAnimationCallback(SDL_Window *window, int interval, SDL_iOSAnimationCallback callback, void *callbackParam) | ||
| 456 | { | ||
| 457 | if (!window || !window->internal) { | ||
| 458 | return SDL_SetError("Invalid window"); | ||
| 459 | } | ||
| 460 | |||
| 461 | @autoreleasepool { | ||
| 462 | SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal; | ||
| 463 | [data.viewcontroller setAnimationCallback:interval | ||
| 464 | callback:callback | ||
| 465 | callbackParam:callbackParam]; | ||
| 466 | } | ||
| 467 | |||
| 468 | return true; | ||
| 469 | } | ||
| 470 | |||
| 471 | #endif // SDL_VIDEO_DRIVER_UIKIT | ||
