summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.m
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.m')
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.m587
1 files changed, 587 insertions, 0 deletions
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
42extern 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