From 774e38d073c8fd0abf91570e3f680b5a4eb5b297 Mon Sep 17 00:00:00 2001
From: Salman Alshamrani <[EMAIL REDACTED]>
Date: Tue, 17 Dec 2024 08:59:49 -0500
Subject: [PATCH] uikit: Initial Apple Pencil support.
Reference Issue #9911.
Reference Issue #10516.
---
src/video/uikit/SDL_uikitpen.h | 37 +++++
src/video/uikit/SDL_uikitpen.m | 93 ++++++++++++
src/video/uikit/SDL_uikitview.h | 7 +
src/video/uikit/SDL_uikitview.m | 254 ++++++++++++++++++++++----------
4 files changed, 317 insertions(+), 74 deletions(-)
create mode 100644 src/video/uikit/SDL_uikitpen.h
create mode 100644 src/video/uikit/SDL_uikitpen.m
diff --git a/src/video/uikit/SDL_uikitpen.h b/src/video/uikit/SDL_uikitpen.h
new file mode 100644
index 0000000000000..c63df2c2857b6
--- /dev/null
+++ b/src/video/uikit/SDL_uikitpen.h
@@ -0,0 +1,37 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef SDL_uikitpen_h_
+#define SDL_uikitpen_h_
+
+#include "SDL_uikitvideo.h"
+#include "SDL_uikitwindow.h"
+
+extern bool UIKit_InitPen(SDL_VideoDevice *_this);
+extern void UIKit_HandlePenEnter();
+extern void UIKit_HandlePenLeave();
+extern void UIKit_HandlePenHover(SDL_uikitview *view, CGPoint point);
+extern void UIKit_HandlePenMotion(SDL_uikitview *view, UITouch *pencil);
+extern void UIKit_HandlePenPress(SDL_uikitview *view, UITouch *pencil);
+extern void UIKit_HandlePenRelease(SDL_uikitview *view, UITouch *pencil);
+extern void UIKit_QuitPen(SDL_VideoDevice *_this);
+
+#endif // SDL_uikitpen_h_
diff --git a/src/video/uikit/SDL_uikitpen.m b/src/video/uikit/SDL_uikitpen.m
new file mode 100644
index 0000000000000..bfa62ebc00679
--- /dev/null
+++ b/src/video/uikit/SDL_uikitpen.m
@@ -0,0 +1,93 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#ifdef SDL_VIDEO_DRIVER_UIKIT
+
+#include "SDL_uikitevents.h"
+#include "SDL_uikitpen.h"
+#include "SDL_uikitwindow.h"
+
+#include "../../events/SDL_pen_c.h"
+
+SDL_PenID penId;
+
+typedef struct UIKit_PenHandle
+{
+ SDL_PenID pen;
+} UIKit_PenHandle;
+
+bool UIKit_InitPen(SDL_VideoDevice *_this)
+{
+ return true;
+}
+
+void UIKit_HandlePenEnter()
+{
+ SDL_PenInfo penInfo;
+ SDL_zero(penInfo);
+ penInfo.capabilities = SDL_PEN_CAPABILITY_PRESSURE | SDL_PEN_CAPABILITY_ROTATION | SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT | SDL_PEN_CAPABILITY_TANGENTIAL_PRESSURE;
+ penInfo.max_tilt = 90.0f;
+ penInfo.num_buttons = 0;
+ penInfo.subtype = SDL_PEN_TYPE_PENCIL;
+
+ // probably make this better
+ penId = SDL_AddPenDevice(0, [@"Apple Pencil" UTF8String], &penInfo, calloc(1, sizeof(UIKit_PenHandle)));
+}
+
+void UIKit_HandlePenHover(SDL_uikitview *view, CGPoint point)
+{
+ SDL_SendPenMotion(0, penId, [view getSDLWindow], point.x, point.y);
+}
+
+void UIKit_HandlePenMotion(SDL_uikitview *view, UITouch *pencil)
+{
+ CGPoint point = [pencil locationInView:view];
+ SDL_SendPenMotion(UIKit_GetEventTimestamp([pencil timestamp]), penId, [view getSDLWindow], point.x, point.y);
+ SDL_SendPenAxis(UIKit_GetEventTimestamp([pencil timestamp]), penId, [view getSDLWindow], SDL_PEN_AXIS_PRESSURE, [pencil force] / [pencil maximumPossibleForce]);
+ NSLog(@"ALTITUDE: %f", [pencil altitudeAngle]);
+ NSLog(@"AZIMUTH VECTOR: %@", [NSValue valueWithCGVector: [pencil azimuthUnitVectorInView:view]]);
+ NSLog(@"AZIMUTH ANGLE: %f", [pencil azimuthAngleInView:view]);
+ // hold it
+ // SDL_SendPenAxis(0, penId, [view getSDLWindow], SDL_PEN_AXIS_XTILT, [pencil altitudeAngle] / M_PI);
+}
+
+void UIKit_HandlePenPress(SDL_uikitview *view, UITouch *pencil)
+{
+ SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, [view getSDLWindow], false, true);
+}
+
+void UIKit_HandlePenRelease(SDL_uikitview *view, UITouch *pencil)
+{
+ SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, [view getSDLWindow], false, false);
+}
+
+void UIKit_HandlePenLeave()
+{
+ SDL_RemovePenDevice(0, penId);
+ penId = 0;
+}
+
+void UIKit_QuitPen(SDL_VideoDevice *_this)
+{
+}
+
+#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/src/video/uikit/SDL_uikitview.h b/src/video/uikit/SDL_uikitview.h
index 6169ccfa022b4..5d2121428712c 100644
--- a/src/video/uikit/SDL_uikitview.h
+++ b/src/video/uikit/SDL_uikitview.h
@@ -34,9 +34,16 @@
- (void)setSDLWindow:(SDL_Window *)window;
- (SDL_Window *)getSDLWindow;
+#if defined(__IPHONE_13_0)
+- (void)pencilHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.0));
+#endif
+
#if !defined(SDL_PLATFORM_TVOS) && defined(__IPHONE_13_4)
- (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4));
- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4));
+- (void)indirectPointerHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.4));
+- (void)updateIndirectPointerFromTouch:(UITouch *)touch;
+- (void)updateIndirectPointerButtonState:(UITouch *)touch fromEvent:(UIEvent *)event;
#endif
- (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize;
diff --git a/src/video/uikit/SDL_uikitview.m b/src/video/uikit/SDL_uikitview.m
index 215f84712d835..99ebc774d63f0 100644
--- a/src/video/uikit/SDL_uikitview.m
+++ b/src/video/uikit/SDL_uikitview.m
@@ -31,6 +31,7 @@
#include "SDL_uikitappdelegate.h"
#include "SDL_uikitevents.h"
#include "SDL_uikitmodes.h"
+#include "SDL_uikitpen.h"
#include "SDL_uikitwindow.h"
// The maximum number of mouse buttons we support
@@ -47,6 +48,10 @@ @implementation SDL_uikitview
SDL_TouchID directTouchId;
SDL_TouchID indirectTouchId;
+
+#if defined(__IPHONE_13_4)
+ UIPointerInteraction *indirectPointerInteraction API_AVAILABLE(ios(13.4));
+#endif
}
- (instancetype)initWithFrame:(CGRect)frame
@@ -81,10 +86,23 @@ - (instancetype)initWithFrame:(CGRect)frame
self.multipleTouchEnabled = YES;
SDL_AddTouch(directTouchId, SDL_TOUCH_DEVICE_DIRECT, "");
#endif
+
+#if defined(__IPHONE_13_0)
+ if (@available(iOS 13.0, *)) {
+ UIHoverGestureRecognizer *pencilRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(pencilHovering:)];
+ pencilRecognizer.allowedTouchTypes = @[@(UITouchTypePencil)];
+ [self addGestureRecognizer:pencilRecognizer];
+ }
+#endif
#if !defined(SDL_PLATFORM_TVOS) && defined(__IPHONE_13_4)
if (@available(iOS 13.4, *)) {
- [self addInteraction:[[UIPointerInteraction alloc] initWithDelegate:self]];
+ indirectPointerInteraction = [[UIPointerInteraction alloc] initWithDelegate:self];
+ [self addInteraction:indirectPointerInteraction];
+
+ UIHoverGestureRecognizer *indirectPointerRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(indirectPointerHovering:)];
+ indirectPointerRecognizer.allowedTouchTypes = @[@(UITouchTypeIndirectPointer)];
+ [self addGestureRecognizer:indirectPointerRecognizer];
}
#endif
}
@@ -156,15 +174,6 @@ - (SDL_Window *)getSDLWindow
#if !defined(SDL_PLATFORM_TVOS) && defined(__IPHONE_13_4)
- (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4))
{
- if (request != nil && !SDL_GCMouseRelativeMode()) {
- CGPoint origin = self.bounds.origin;
- CGPoint point = request.location;
-
- point.x -= origin.x;
- point.y -= origin.y;
-
- SDL_SendMouseMotion(0, sdlwindow, SDL_GLOBAL_MOUSE_ID, false, point.x, point.y);
- }
return [UIPointerRegion regionWithRect:self.bounds identifier:nil];
}
@@ -176,8 +185,115 @@ - (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction style
return [UIPointerStyle hiddenPointerStyle];
}
}
+
+- (void)indirectPointerHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.4))
+{
+ switch (recognizer.state) {
+ case UIGestureRecognizerStateBegan:
+ case UIGestureRecognizerStateChanged:
+ {
+ CGPoint point = [recognizer locationInView:self];
+ SDL_SendMouseMotion(0, sdlwindow, SDL_GLOBAL_MOUSE_ID, false, point.x, point.y);
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+- (void)indirectPointerMoving:(UITouch *)touch API_AVAILABLE(ios(13.4))
+{
+ CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO];
+ SDL_SendMouseMotion(0, sdlwindow, SDL_GLOBAL_MOUSE_ID, false, locationInView.x, locationInView.y);
+}
+
+- (void)indirectPointerPressed:(UITouch *)touch fromEvent:(UIEvent *)event API_AVAILABLE(ios(13.4))
+{
+ if (!SDL_HasMouse()) {
+ int i;
+
+ for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) {
+ if (event.buttonMask & SDL_BUTTON_MASK(i)) {
+ Uint8 button;
+
+ switch (i) {
+ case 1:
+ button = SDL_BUTTON_LEFT;
+ break;
+ case 2:
+ button = SDL_BUTTON_RIGHT;
+ break;
+ case 3:
+ button = SDL_BUTTON_MIDDLE;
+ break;
+ default:
+ button = (Uint8)i;
+ break;
+ }
+ SDL_SendMouseButton(UIKit_GetEventTimestamp([touch timestamp]), sdlwindow, SDL_GLOBAL_MOUSE_ID, button, true);
+ }
+ }
+ }
+}
+
+- (void)indirectPointerReleased:(UITouch *)touch fromEvent:(UIEvent *)event API_AVAILABLE(ios(13.4))
+{
+ if (!SDL_HasMouse()) {
+ int i;
+ SDL_MouseButtonFlags buttons = SDL_GetMouseState(NULL, NULL);
+
+ for (i = 0; i < MAX_MOUSE_BUTTONS; ++i) {
+ if (buttons & SDL_BUTTON_MASK(i)) {
+ SDL_SendMouseButton(UIKit_GetEventTimestamp([touch timestamp]), sdlwindow, SDL_GLOBAL_MOUSE_ID, (Uint8)i, false);
+ }
+ }
+ }
+}
+
#endif // !defined(SDL_PLATFORM_TVOS) && __IPHONE_13_4
+#if defined(__IPHONE_13_0)
+
+- (void)pencilHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.0))
+{
+ switch (recognizer.state) {
+ case UIGestureRecognizerStateBegan:
+ UIKit_HandlePenEnter();
+ UIKit_HandlePenHover(self, [recognizer locationInView:self]);
+ break;
+
+ case UIGestureRecognizerStateChanged:
+ UIKit_HandlePenHover(self, [recognizer locationInView:self]);
+ break;
+
+ case UIGestureRecognizerStateEnded:
+ case UIGestureRecognizerStateCancelled:
+ UIKit_HandlePenLeave();
+ break;
+
+ default:
+ break;
+ }
+}
+
+- (void)pencilMoving:(UITouch *)touch
+{
+ UIKit_HandlePenMotion(self, touch);
+}
+
+- (void)pencilPressed:(UITouch *)touch
+{
+ UIKit_HandlePenPress(self, touch);
+}
+
+- (void)pencilReleased:(UITouch *)touch
+{
+ UIKit_HandlePenRelease(self, touch);
+}
+
+#endif // defined(__IPHONE_13_0)
+
- (SDL_TouchDeviceType)touchTypeForTouch:(UITouch *)touch
{
#ifdef __IPHONE_9_0
@@ -231,34 +347,19 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
for (UITouch *touch in touches) {
BOOL handled = NO;
+#if defined(__IPHONE_13_0)
+ if (@available(iOS 13.0, *)) {
+ if (touch.type == UITouchTypePencil) {
+ [self pencilPressed:touch];
+ continue;
+ }
+ }
+#endif
+
#if !defined(SDL_PLATFORM_TVOS) && defined(__IPHONE_13_4)
if (@available(iOS 13.4, *)) {
if (touch.type == UITouchTypeIndirectPointer) {
- if (!SDL_HasMouse()) {
- int i;
-
- for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) {
- if (event.buttonMask & SDL_BUTTON_MASK(i)) {
- Uint8 button;
-
- switch (i) {
- case 1:
- button = SDL_BUTTON_LEFT;
- break;
- case 2:
- button = SDL_BUTTON_RIGHT;
- break;
- case 3:
- button = SDL_BUTTON_MIDDLE;
- break;
- default:
- button = (Uint8)i;
- break;
- }
- SDL_SendMouseButton(UIKit_GetEventTimestamp([event timestamp]), sdlwindow, SDL_GLOBAL_MOUSE_ID, button, true);
- }
- }
- }
+ [self indirectPointerPressed:touch fromEvent:event];
handled = YES;
}
}
@@ -286,40 +387,38 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
BOOL handled = NO;
-
+
+#if defined(__IPHONE_13_0)
+ if (@available(iOS 13.0, *)) {
+ if (touch.type == UITouchTypePencil) {
+ [self pencilReleased:touch];
+ continue;
+ }
+ }
+#endif
+
#if !defined(SDL_PLATFORM_TVOS) && defined(__IPHONE_13_4)
if (@available(iOS 13.4, *)) {
if (touch.type == UITouchTypeIndirectPointer) {
- if (!SDL_HasMouse()) {
- int i;
- SDL_MouseButtonFlags buttons = SDL_GetMouseState(NULL, NULL);
-
- for (i = 0; i < MAX_MOUSE_BUTTONS; ++i) {
- if (buttons & SDL_BUTTON_MASK(i)) {
- SDL_SendMouseButton(UIKit_GetEventTimestamp([event timestamp]), sdlwindow, SDL_GLOBAL_MOUSE_ID, (Uint8)i, false);
- }
- }
- }
- handled = YES;
+ [self indirectPointerReleased:touch fromEvent:event];
+ continue;
}
}
#endif
- if (!handled) {
- SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
- SDL_TouchID touchId = [self touchIdForType:touchType];
- float pressure = [self pressureForTouch:touch];
+ SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
+ SDL_TouchID touchId = [self touchIdForType:touchType];
+ float pressure = [self pressureForTouch:touch];
- if (SDL_AddTouch(touchId, touchType, "") < 0) {
- continue;
- }
+ if (SDL_AddTouch(touchId, touchType, "") < 0) {
+ continue;
+ }
- // FIXME, need to send: int clicks = (int) touch.tapCount; ?
+ // FIXME, need to send: int clicks = (int) touch.tapCount; ?
- CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
- SDL_SendTouch(UIKit_GetEventTimestamp([event timestamp]),
- touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow,
- false, locationInView.x, locationInView.y, pressure);
- }
+ CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
+ SDL_SendTouch(UIKit_GetEventTimestamp([event timestamp]),
+ touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow,
+ false, locationInView.x, locationInView.y, pressure);
}
}
@@ -332,29 +431,36 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
BOOL handled = NO;
+
+#if defined(__IPHONE_13_0)
+ if (@available(iOS 13.0, *)) {
+ if (touch.type == UITouchTypePencil) {
+ [self pencilMoving:touch];
+ continue;
+ }
+ }
+#endif
#if !defined(SDL_PLATFORM_TVOS) && defined(__IPHONE_13_4)
if (@available(iOS 13.4, *)) {
if (touch.type == UITouchTypeIndirectPointer) {
- // Already handled in pointerInteraction callback
- handled = YES;
+ [self indirectPointerMoving:touch];
+ continue;
}
}
#endif
- if (!handled) {
- SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
- SDL_TouchID touchId = [self touchIdForType:touchType];
- float pressure = [self pressureForTouch:touch];
+ SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
+ SDL_TouchID touchId = [self touchIdForType:touchType];
+ float pressure = [self pressureForTouch:touch];
- if (SDL_AddTouch(touchId, touchType, "") < 0) {
- continue;
- }
-
- CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
- SDL_SendTouchMotion(UIKit_GetEventTimestamp([event timestamp]),
- touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow,
- locationInView.x, locationInView.y, pressure);
+ if (SDL_AddTouch(touchId, touchType, "") < 0) {
+ continue;
}
+
+ CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
+ SDL_SendTouchMotion(UIKit_GetEventTimestamp([event timestamp]),
+ touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow,
+ locationInView.x, locationInView.y, pressure);
}
}