1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2020 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 SDL_VIDEO_DRIVER_UIKIT
24
25#include "SDL_uikitview.h"
26
27#include "SDL_hints.h"
28#include "../../events/SDL_mouse_c.h"
29#include "../../events/SDL_touch_c.h"
30#include "../../events/SDL_events_c.h"
31
32#import "SDL_uikitappdelegate.h"
33#import "SDL_uikitmodes.h"
34#import "SDL_uikitwindow.h"
35
36/* The maximum number of mouse buttons we support */
37#define MAX_MOUSE_BUTTONS    5
38
39/* This is defined in SDL_sysjoystick.m */
40#if !SDL_JOYSTICK_DISABLED
41extern int SDL_AppleTVRemoteOpenedAsJoystick;
42#endif
43
44@implementation SDL_uikitview {
45    SDL_Window *sdlwindow;
46
47    SDL_TouchID directTouchId;
48    SDL_TouchID indirectTouchId;
49}
50
51- (instancetype)initWithFrame:(CGRect)frame
52{
53    if ((self = [super initWithFrame:frame])) {
54#if TARGET_OS_TV
55        /* Apple TV Remote touchpad swipe gestures. */
56        UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
57        swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
58        [self addGestureRecognizer:swipeUp];
59
60        UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
61        swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
62        [self addGestureRecognizer:swipeDown];
63
64        UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
65        swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
66        [self addGestureRecognizer:swipeLeft];
67
68        UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
69        swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
70        [self addGestureRecognizer:swipeRight];
71#else
72        if (@available(iOS 13.4, *)) {
73            UIPanGestureRecognizer *mouseWheelRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(mouseWheelGesture:)];
74            mouseWheelRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
75            mouseWheelRecognizer.allowedTouchTypes = @[ @(UITouchTypeIndirectPointer) ];
76            mouseWheelRecognizer.cancelsTouchesInView = NO;
77            mouseWheelRecognizer.delaysTouchesBegan = NO;
78            mouseWheelRecognizer.delaysTouchesEnded = NO;
79            [self addGestureRecognizer:mouseWheelRecognizer];
80        }
81#endif
82
83        self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
84        self.autoresizesSubviews = YES;
85
86        directTouchId = 1;
87        indirectTouchId = 2;
88
89#if !TARGET_OS_TV
90        self.multipleTouchEnabled = YES;
91        SDL_AddTouch(directTouchId, SDL_TOUCH_DEVICE_DIRECT, "");
92#endif
93
94#if !TARGET_OS_TV && defined(__IPHONE_13_4)
95        if (@available(iOS 13.4, *)) {
96            [self addInteraction:[[UIPointerInteraction alloc] initWithDelegate:self]];
97        }
98#endif
99    }
100
101    return self;
102}
103
104- (void)setSDLWindow:(SDL_Window *)window
105{
106    SDL_WindowData *data = nil;
107
108    if (window == sdlwindow) {
109        return;
110    }
111
112    /* Remove ourself from the old window. */
113    if (sdlwindow) {
114        SDL_uikitview *view = nil;
115        data = (__bridge SDL_WindowData *) sdlwindow->driverdata;
116
117        [data.views removeObject:self];
118
119        [self removeFromSuperview];
120
121        /* Restore the next-oldest view in the old window. */
122        view = data.views.lastObject;
123
124        data.viewcontroller.view = view;
125
126        data.uiwindow.rootViewController = nil;
127        data.uiwindow.rootViewController = data.viewcontroller;
128
129        [data.uiwindow layoutIfNeeded];
130    }
131
132    /* Add ourself to the new window. */
133    if (window) {
134        data = (__bridge SDL_WindowData *) window->driverdata;
135
136        /* Make sure the SDL window has a strong reference to this view. */
137        [data.views addObject:self];
138
139        /* Replace the view controller's old view with this one. */
140        [data.viewcontroller.view removeFromSuperview];
141        data.viewcontroller.view = self;
142
143        /* The root view controller handles rotation and the status bar.
144         * Assigning it also adds the controller's view to the window. We
145         * explicitly re-set it to make sure the view is properly attached to
146         * the window. Just adding the sub-view if the root view controller is
147         * already correct causes orientation issues on iOS 7 and below. */
148        data.uiwindow.rootViewController = nil;
149        data.uiwindow.rootViewController = data.viewcontroller;
150
151        /* The view's bounds may not be correct until the next event cycle. That
152         * might happen after the current dimensions are queried, so we force a
153         * layout now to immediately update the bounds. */
154        [data.uiwindow layoutIfNeeded];
155    }
156
157    sdlwindow = window;
158}
159
160#if !TARGET_OS_TV && defined(__IPHONE_13_4)
161- (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4)){
162    if (request != nil) {
163        CGPoint origin = self.bounds.origin;
164        CGPoint point = request.location;
165
166        point.x -= origin.x;
167        point.y -= origin.y;
168
169        SDL_SendMouseMotion(sdlwindow, 0, 0, (int)point.x, (int)point.y);
170    }
171    return [UIPointerRegion regionWithRect:self.bounds identifier:nil];
172}
173
174- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region  API_AVAILABLE(ios(13.4)){
175    if (SDL_ShowCursor(-1)) {
176        return nil;
177    } else {
178        return [UIPointerStyle hiddenPointerStyle];
179    }
180}
181#endif /* !TARGET_OS_TV && __IPHONE_13_4 */
182
183- (SDL_TouchDeviceType)touchTypeForTouch:(UITouch *)touch
184{
185#ifdef __IPHONE_9_0
186    if ([touch respondsToSelector:@selector((type))]) {
187        if (touch.type == UITouchTypeIndirect) {
188            return SDL_TOUCH_DEVICE_INDIRECT_RELATIVE;
189        }
190    }
191#endif
192
193    return SDL_TOUCH_DEVICE_DIRECT;
194}
195
196- (SDL_TouchID)touchIdForType:(SDL_TouchDeviceType)type
197{
198    switch (type) {
199        case SDL_TOUCH_DEVICE_DIRECT:
200        default:
201            return directTouchId;
202        case SDL_TOUCH_DEVICE_INDIRECT_RELATIVE:
203            return indirectTouchId;
204    }
205}
206
207- (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize
208{
209    CGPoint point = [touch locationInView:self];
210
211    if (normalize) {
212        CGRect bounds = self.bounds;
213        point.x /= bounds.size.width;
214        point.y /= bounds.size.height;
215    }
216
217    return point;
218}
219
220- (float)pressureForTouch:(UITouch *)touch
221{
222#ifdef __IPHONE_9_0
223    if ([touch respondsToSelector:@selector(force)]) {
224        return (float) touch.force;
225    }
226#endif
227
228    return 1.0f;
229}
230
231- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
232{
233    for (UITouch *touch in touches) {
234        BOOL handled = NO;
235
236#if !TARGET_OS_TV && defined(__IPHONE_13_4)
237        if (@available(iOS 13.4, *)) {
238            if (touch.type == UITouchTypeIndirectPointer) {
239                int i;
240
241                for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) {
242                    if (event.buttonMask & SDL_BUTTON(i)) {
243                        Uint8 button;
244
245                        switch (i) {
246                        case 1:
247                            button = SDL_BUTTON_LEFT;
248                            break;
249                        case 2:
250                            button = SDL_BUTTON_RIGHT;
251                            break;
252                        case 3:
253                            button = SDL_BUTTON_MIDDLE;
254                            break;
255                        default:
256                            button = (Uint8)i;
257                            break;
258                        }
259                        SDL_SendMouseButton(sdlwindow, 0, SDL_PRESSED, button);
260                    }
261                }
262                handled = YES;
263            }
264        }
265#endif
266        if (!handled) {
267            SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
268            SDL_TouchID touchId = [self touchIdForType:touchType];
269            float pressure = [self pressureForTouch:touch];
270
271            if (SDL_AddTouch(touchId, touchType, "") < 0) {
272                continue;
273            }
274
275            /* FIXME, need to send: int clicks = (int) touch.tapCount; ? */
276
277            CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
278            SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch), sdlwindow,
279                          SDL_TRUE, locationInView.x, locationInView.y, pressure);
280        }
281    }
282}
283
284- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
285{
286    for (UITouch *touch in touches) {
287        BOOL handled = NO;
288
289#if !TARGET_OS_TV && defined(__IPHONE_13_4)
290        if (@available(iOS 13.4, *)) {
291            if (touch.type == UITouchTypeIndirectPointer) {
292                int i;
293
294                for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) {
295                    if (!(event.buttonMask & SDL_BUTTON(i))) {
296                        Uint8 button;
297
298                        switch (i) {
299                        case 1:
300                            button = SDL_BUTTON_LEFT;
301                            break;
302                        case 2:
303                            button = SDL_BUTTON_RIGHT;
304                            break;
305                        case 3:
306                            button = SDL_BUTTON_MIDDLE;
307                            break;
308                        default:
309                            button = (Uint8)i;
310                            break;
311                        }
312                        SDL_SendMouseButton(sdlwindow, 0, SDL_RELEASED, button);
313                    }
314                }
315                handled = YES;
316            }
317        }
318#endif
319        if (!handled) {
320            SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
321            SDL_TouchID touchId = [self touchIdForType:touchType];
322            float pressure = [self pressureForTouch:touch];
323
324            if (SDL_AddTouch(touchId, touchType, "") < 0) {
325                continue;
326            }
327
328            /* FIXME, need to send: int clicks = (int) touch.tapCount; ? */
329
330            CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
331            SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch), sdlwindow,
332                          SDL_FALSE, locationInView.x, locationInView.y, pressure);
333        }
334    }
335}
336
337- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
338{
339    [self touchesEnded:touches withEvent:event];
340}
341
342- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
343{
344    for (UITouch *touch in touches) {
345        BOOL handled = NO;
346
347#if !TARGET_OS_TV && defined(__IPHONE_13_4)
348        if (@available(iOS 13.4, *)) {
349            if (touch.type == UITouchTypeIndirectPointer) {
350                /* Already handled in pointerInteraction callback */
351                handled = YES;
352            }
353        }
354#endif
355        if (!handled) {
356            SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
357            SDL_TouchID touchId = [self touchIdForType:touchType];
358            float pressure = [self pressureForTouch:touch];
359
360            if (SDL_AddTouch(touchId, touchType, "") < 0) {
361                continue;
362            }
363
364            CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
365            SDL_SendTouchMotion(touchId, (SDL_FingerID)((size_t)touch), sdlwindow,
366                                locationInView.x, locationInView.y, pressure);
367        }
368    }
369}
370
371#if TARGET_OS_TV || defined(__IPHONE_9_1)
372- (SDL_Scancode)scancodeFromPress:(UIPress*)press
373{
374#ifdef __IPHONE_13_4
375    if ([press respondsToSelector:@selector((key))]) {
376        if (press.key != nil) {
377            return (SDL_Scancode)press.key.keyCode;
378        }
379    }
380#endif
381
382#if !SDL_JOYSTICK_DISABLED
383    /* Presses from Apple TV remote */
384    if (!SDL_AppleTVRemoteOpenedAsJoystick) {
385        switch (press.type) {
386        case UIPressTypeUpArrow:
387            return SDL_SCANCODE_UP;
388        case UIPressTypeDownArrow:
389            return SDL_SCANCODE_DOWN;
390        case UIPressTypeLeftArrow:
391            return SDL_SCANCODE_LEFT;
392        case UIPressTypeRightArrow:
393            return SDL_SCANCODE_RIGHT;
394        case UIPressTypeSelect:
395            /* HIG says: "primary button behavior" */
396            return SDL_SCANCODE_RETURN;
397        case UIPressTypeMenu:
398            /* HIG says: "returns to previous screen" */
399            return SDL_SCANCODE_ESCAPE;
400        case UIPressTypePlayPause:
401            /* HIG says: "secondary button behavior" */
402            return SDL_SCANCODE_PAUSE;
403        default:
404            break;
405        }
406    }
407#endif /* !SDL_JOYSTICK_DISABLED */
408
409    return SDL_SCANCODE_UNKNOWN;
410}
411
412- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
413{
414    for (UIPress *press in presses) {
415        SDL_Scancode scancode = [self scancodeFromPress:press];
416        SDL_SendKeyboardKey(SDL_PRESSED, scancode);
417    }
418    [super pressesBegan:presses withEvent:event];
419}
420
421- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
422{
423    for (UIPress *press in presses) {
424        SDL_Scancode scancode = [self scancodeFromPress:press];
425        SDL_SendKeyboardKey(SDL_RELEASED, scancode);
426    }
427    [super pressesEnded:presses withEvent:event];
428}
429
430- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
431{
432    for (UIPress *press in presses) {
433        SDL_Scancode scancode = [self scancodeFromPress:press];
434        SDL_SendKeyboardKey(SDL_RELEASED, scancode);
435    }
436    [super pressesCancelled:presses withEvent:event];
437}
438
439- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
440{
441    /* This is only called when the force of a press changes. */
442    [super pressesChanged:presses withEvent:event];
443}
444
445#endif /* TARGET_OS_TV || defined(__IPHONE_9_1) */
446
447-(void)mouseWheelGesture:(UIPanGestureRecognizer *)gesture
448{
449    if (gesture.state == UIGestureRecognizerStateBegan ||
450        gesture.state == UIGestureRecognizerStateChanged ||
451        gesture.state == UIGestureRecognizerStateEnded) {
452        CGPoint velocity = [gesture velocityInView:self];
453
454        if (velocity.x > 0.0f) {
455            velocity.x = -1.0;
456        } else if (velocity.x < 0.0f) {
457            velocity.x = 1.0f;
458        }
459        if (velocity.y > 0.0f) {
460            velocity.y = -1.0;
461        } else if (velocity.y < 0.0f) {
462            velocity.y = 1.0f;
463        }
464        if (velocity.x != 0.0f || velocity.y != 0.0f) {
465            SDL_SendMouseWheel(sdlwindow, 0, velocity.x, velocity.y, SDL_MOUSEWHEEL_NORMAL);
466        }
467    }
468}
469
470#if TARGET_OS_TV
471-(void)swipeGesture:(UISwipeGestureRecognizer *)gesture
472{
473    /* Swipe gestures don't trigger begin states. */
474    if (gesture.state == UIGestureRecognizerStateEnded) {
475#if !SDL_JOYSTICK_DISABLED
476        if (!SDL_AppleTVRemoteOpenedAsJoystick) {
477            /* Send arrow key presses for now, as we don't have an external API
478             * which better maps to swipe gestures. */
479            switch (gesture.direction) {
480            case UISwipeGestureRecognizerDirectionUp:
481                SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_UP);
482                break;
483            case UISwipeGestureRecognizerDirectionDown:
484                SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_DOWN);
485                break;
486            case UISwipeGestureRecognizerDirectionLeft:
487                SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_LEFT);
488                break;
489            case UISwipeGestureRecognizerDirectionRight:
490                SDL_SendKeyboardKeyAutoRelease(SDL_SCANCODE_RIGHT);
491                break;
492            }
493        }
494#endif /* !SDL_JOYSTICK_DISABLED */
495    }
496}
497#endif /* TARGET_OS_TV */
498
499@end
500
501#endif /* SDL_VIDEO_DRIVER_UIKIT */
502
503/* vi: set ts=4 sw=4 expandtab: */
504