2 Simple DirectMedia Layer
3 Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
21 #include "../../SDL_internal.h"
23 #if SDL_VIDEO_DRIVER_UIKIT
25 #include "SDL_uikitview.h"
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"
32 #import "SDL_uikitappdelegate.h"
33 #import "SDL_uikitmodes.h"
34 #import "SDL_uikitwindow.h"
36 /* This is defined in SDL_sysjoystick.m */
37 extern int SDL_AppleTVRemoteOpenedAsJoystick;
39 @implementation SDL_uikitview {
40 SDL_Window *sdlwindow;
43 UITouch * __weak firstFingerDown;
46 - (instancetype)initWithFrame:(CGRect)frame
48 if ((self = [super initWithFrame:frame])) {
50 /* Apple TV Remote touchpad swipe gestures. */
51 UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
52 swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
53 [self addGestureRecognizer:swipeUp];
55 UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
56 swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
57 [self addGestureRecognizer:swipeDown];
59 UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
60 swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
61 [self addGestureRecognizer:swipeLeft];
63 UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
64 swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
65 [self addGestureRecognizer:swipeRight];
68 self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
69 self.autoresizesSubviews = YES;
72 self.multipleTouchEnabled = YES;
76 SDL_AddTouch(touchId, "");
82 - (void)setSDLWindow:(SDL_Window *)window
84 SDL_WindowData *data = nil;
86 if (window == sdlwindow) {
90 /* Remove ourself from the old window. */
92 SDL_uikitview *view = nil;
93 data = (__bridge SDL_WindowData *) sdlwindow->driverdata;
95 [data.views removeObject:self];
97 [self removeFromSuperview];
99 /* Restore the next-oldest view in the old window. */
100 view = data.views.lastObject;
102 data.viewcontroller.view = view;
104 data.uiwindow.rootViewController = nil;
105 data.uiwindow.rootViewController = data.viewcontroller;
107 [data.uiwindow layoutIfNeeded];
110 /* Add ourself to the new window. */
112 data = (__bridge SDL_WindowData *) window->driverdata;
114 /* Make sure the SDL window has a strong reference to this view. */
115 [data.views addObject:self];
117 /* Replace the view controller's old view with this one. */
118 [data.viewcontroller.view removeFromSuperview];
119 data.viewcontroller.view = self;
121 /* The root view controller handles rotation and the status bar.
122 * Assigning it also adds the controller's view to the window. We
123 * explicitly re-set it to make sure the view is properly attached to
124 * the window. Just adding the sub-view if the root view controller is
125 * already correct causes orientation issues on iOS 7 and below. */
126 data.uiwindow.rootViewController = nil;
127 data.uiwindow.rootViewController = data.viewcontroller;
129 /* The view's bounds may not be correct until the next event cycle. That
130 * might happen after the current dimensions are queried, so we force a
131 * layout now to immediately update the bounds. */
132 [data.uiwindow layoutIfNeeded];
138 - (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize
140 CGPoint point = [touch locationInView:self];
143 CGRect bounds = self.bounds;
144 point.x /= bounds.size.width;
145 point.y /= bounds.size.height;
151 - (float)pressureForTouch:(UITouch *)touch
154 if ([touch respondsToSelector:@selector(force)]) {
155 return (float) touch.force;
162 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
164 for (UITouch *touch in touches) {
165 float pressure = [self pressureForTouch:touch];
167 if (!firstFingerDown) {
168 CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO];
169 int clicks = (int) touch.tapCount;
171 /* send mouse moved event */
172 SDL_SendMouseMotion(sdlwindow, SDL_TOUCH_MOUSEID, 0, locationInView.x, locationInView.y);
174 /* send mouse down event */
175 SDL_SendMouseButtonClicks(sdlwindow, SDL_TOUCH_MOUSEID, SDL_PRESSED, SDL_BUTTON_LEFT, clicks);
177 firstFingerDown = touch;
180 CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
181 SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch),
182 SDL_TRUE, locationInView.x, locationInView.y, pressure);
186 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
188 for (UITouch *touch in touches) {
189 float pressure = [self pressureForTouch:touch];
191 if (touch == firstFingerDown) {
193 int clicks = (int) touch.tapCount;
194 SDL_SendMouseButtonClicks(sdlwindow, SDL_TOUCH_MOUSEID, SDL_RELEASED, SDL_BUTTON_LEFT, clicks);
195 firstFingerDown = nil;
198 CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
199 SDL_SendTouch(touchId, (SDL_FingerID)((size_t)touch),
200 SDL_FALSE, locationInView.x, locationInView.y, pressure);
204 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
206 [self touchesEnded:touches withEvent:event];
209 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
211 for (UITouch *touch in touches) {
212 float pressure = [self pressureForTouch:touch];
214 if (touch == firstFingerDown) {
215 CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO];
217 /* send moved event */
218 SDL_SendMouseMotion(sdlwindow, SDL_TOUCH_MOUSEID, 0, locationInView.x, locationInView.y);
221 CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
222 SDL_SendTouchMotion(touchId, (SDL_FingerID)((size_t)touch),
223 locationInView.x, locationInView.y, pressure);
227 #if TARGET_OS_TV || defined(__IPHONE_9_1)
228 - (SDL_Scancode)scancodeFromPressType:(UIPressType)presstype
231 case UIPressTypeUpArrow:
232 return SDL_SCANCODE_UP;
233 case UIPressTypeDownArrow:
234 return SDL_SCANCODE_DOWN;
235 case UIPressTypeLeftArrow:
236 return SDL_SCANCODE_LEFT;
237 case UIPressTypeRightArrow:
238 return SDL_SCANCODE_RIGHT;
239 case UIPressTypeSelect:
240 /* HIG says: "primary button behavior" */
241 return SDL_SCANCODE_RETURN;
242 case UIPressTypeMenu:
243 /* HIG says: "returns to previous screen" */
244 return SDL_SCANCODE_ESCAPE;
245 case UIPressTypePlayPause:
246 /* HIG says: "secondary button behavior" */
247 return SDL_SCANCODE_PAUSE;
249 return SDL_SCANCODE_UNKNOWN;
253 - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
255 if (!SDL_AppleTVRemoteOpenedAsJoystick) {
256 for (UIPress *press in presses) {
257 SDL_Scancode scancode = [self scancodeFromPressType:press.type];
258 SDL_SendKeyboardKey(SDL_PRESSED, scancode);
261 [super pressesBegan:presses withEvent:event];
264 - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
266 if (!SDL_AppleTVRemoteOpenedAsJoystick) {
267 for (UIPress *press in presses) {
268 SDL_Scancode scancode = [self scancodeFromPressType:press.type];
269 SDL_SendKeyboardKey(SDL_RELEASED, scancode);
272 [super pressesEnded:presses withEvent:event];
275 - (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
277 if (!SDL_AppleTVRemoteOpenedAsJoystick) {
278 for (UIPress *press in presses) {
279 SDL_Scancode scancode = [self scancodeFromPressType:press.type];
280 SDL_SendKeyboardKey(SDL_RELEASED, scancode);
283 [super pressesCancelled:presses withEvent:event];
286 - (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
288 /* This is only called when the force of a press changes. */
289 [super pressesChanged:presses withEvent:event];
291 #endif /* TARGET_OS_TV || defined(__IPHONE_9_1) */
294 -(void)swipeGesture:(UISwipeGestureRecognizer *)gesture
296 /* Swipe gestures don't trigger begin states. */
297 if (gesture.state == UIGestureRecognizerStateEnded) {
298 if (!SDL_AppleTVRemoteOpenedAsJoystick) {
299 /* Send arrow key presses for now, as we don't have an external API
300 * which better maps to swipe gestures. */
301 switch (gesture.direction) {
302 case UISwipeGestureRecognizerDirectionUp:
303 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_UP);
304 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_UP);
306 case UISwipeGestureRecognizerDirectionDown:
307 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_DOWN);
308 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_DOWN);
310 case UISwipeGestureRecognizerDirectionLeft:
311 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LEFT);
312 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LEFT);
314 case UISwipeGestureRecognizerDirectionRight:
315 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_RIGHT);
316 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RIGHT);
322 #endif /* TARGET_OS_TV */
326 #endif /* SDL_VIDEO_DRIVER_UIKIT */
328 /* vi: set ts=4 sw=4 expandtab: */