222 lines
8.5 KiB
Objective-C
222 lines
8.5 KiB
Objective-C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2026 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"
|
|
|
|
// Fix build errors when using an older SDK by defining these selectors
|
|
#if !defined(SDL_PLATFORM_TVOS)
|
|
|
|
@interface UITouch (SDL)
|
|
#if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 170500)
|
|
@property (nonatomic, readonly) CGFloat rollAngle;
|
|
#endif
|
|
@end
|
|
|
|
@interface UIHoverGestureRecognizer (SDL)
|
|
#if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 160100)
|
|
@property (nonatomic, readonly) CGFloat zOffset;
|
|
#endif
|
|
#if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 160400)
|
|
- (CGFloat) azimuthAngleInView:(UIView *) view;
|
|
|
|
@property (nonatomic, readonly) CGFloat altitudeAngle;
|
|
#endif
|
|
#if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 170500)
|
|
@property (nonatomic, readonly) CGFloat rollAngle;
|
|
#endif
|
|
@end
|
|
|
|
#endif // !SDL_PLATFORM_TVOS
|
|
|
|
static SDL_PenID apple_pencil_id = 0;
|
|
|
|
bool UIKit_InitPen(SDL_VideoDevice *_this)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// we only have one Apple Pencil at a time, and it must be paired to the iOS device.
|
|
// We only know about its existence when it first sends an event, so add an single SDL pen
|
|
// device here if we haven't already.
|
|
static SDL_PenID UIKit_AddPenIfNecesary(SDL_Window *window)
|
|
{
|
|
if (!apple_pencil_id) {
|
|
SDL_PenInfo info;
|
|
SDL_zero(info);
|
|
info.capabilities = SDL_PEN_CAPABILITY_PRESSURE | SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT;
|
|
info.max_tilt = 90.0f;
|
|
info.num_buttons = 0;
|
|
info.subtype = SDL_PEN_TYPE_PENCIL;
|
|
info.device_type = SDL_PEN_DEVICE_TYPE_DIRECT; // Apple Pencil on iOS is always a direct device; it works on the tablet's screen.
|
|
|
|
if (@available(iOS 17.5, *)) { // need rollAngle method.
|
|
info.capabilities |= SDL_PEN_CAPABILITY_ROTATION;
|
|
}
|
|
|
|
if (@available(ios 16.1, *)) { // need zOffset method.
|
|
info.capabilities |= SDL_PEN_CAPABILITY_DISTANCE;
|
|
}
|
|
|
|
// Apple Pencil and iOS can report when the pencil is being "squeezed" but it's a boolean thing,
|
|
// so we can't use it for tangential pressure.
|
|
|
|
// There's only ever one Apple Pencil at most, so we just pass a non-zero value for the handle.
|
|
apple_pencil_id = SDL_AddPenDevice(0, "Apple Pencil", window, &info, (void *) (size_t) 0x1, true);
|
|
}
|
|
|
|
return apple_pencil_id;
|
|
}
|
|
|
|
static void UIKit_HandlePenAxes(SDL_Window *window, NSTimeInterval nstimestamp, float zOffset, const CGPoint *point, float force,
|
|
float maximumPossibleForce, float azimuthAngleInView, float altitudeAngle, float rollAngle)
|
|
{
|
|
const SDL_PenID penId = UIKit_AddPenIfNecesary(window);
|
|
if (penId) {
|
|
const Uint64 timestamp = UIKit_GetEventTimestamp(nstimestamp);
|
|
const float radians_to_degrees = 180.0f / SDL_PI_F;
|
|
|
|
// Normalize force to 0.0f ... 1.0f range.
|
|
const float pressure = force / maximumPossibleForce;
|
|
|
|
// azimuthAngleInView is in radians, with 0 being the pen's back end pointing due east on the screen when the
|
|
// tip is touching the screen, and negative when heading north from there, positive to the south.
|
|
// So convert to degrees, 0 being due east, etc.
|
|
const float azimuth_angle = azimuthAngleInView * radians_to_degrees;
|
|
|
|
// altitudeAngle is in radians, with 0 being the pen laying flat on (parallel to) the device
|
|
// screen and PI/2 being it pointing straight up from (perpendicular to) the device screen.
|
|
// So convert to degrees, 0 being flat and 90 being straight up.
|
|
const float altitude_angle = altitudeAngle * radians_to_degrees;
|
|
|
|
// the azimuth_angle goes from -180 to 180 (with abs(angle) moving from 180 to 0, left to right), but SDL wants
|
|
// it from -90 (back facing west) to 90 (back facing east).
|
|
const float xtilt = (180.0f - SDL_fabsf(azimuth_angle)) - 90.0f;
|
|
|
|
// the altitude_angle goes from 0 to 90 regardless of which direction the pen is lifting off the device, but SDL wants
|
|
// it from -90 (flat facing north) to 90 (flat facing south).
|
|
const float ytilt = (azimuth_angle < 0.0f) ? -(90.0f - altitude_angle) : (90.0f - altitude_angle);
|
|
|
|
// rotation is in radians, and only available on a later iOS.
|
|
const float rotation = rollAngle * radians_to_degrees; // !!! FIXME: this might need adjustment, I don't have a pencil that supports it.
|
|
|
|
if (force == 0.0f && (SDL_GetPenStatus(penId, NULL, 0) & SDL_PEN_INPUT_DOWN)) {
|
|
// The first hover as the pen is being released has a stale position, so ignore it
|
|
return;
|
|
}
|
|
|
|
SDL_SendPenMotion(timestamp, penId, window, point->x, point->y);
|
|
SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_PRESSURE, pressure);
|
|
SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_XTILT, xtilt);
|
|
SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_YTILT, ytilt);
|
|
SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_ROTATION, rotation);
|
|
SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_DISTANCE, zOffset);
|
|
}
|
|
}
|
|
|
|
#if !defined(SDL_PLATFORM_TVOS)
|
|
extern void UIKit_HandlePenHover(SDL_uikitview *view, UIHoverGestureRecognizer *recognizer)
|
|
{
|
|
float zOffset = 0.0f;
|
|
if (@available(iOS 16.1, *)) {
|
|
zOffset = (float) [recognizer zOffset];
|
|
}
|
|
|
|
float azimuthAngleInView = 0.0f;
|
|
if (@available(iOS 16.4, *)) {
|
|
azimuthAngleInView = (float) [recognizer azimuthAngleInView:view];
|
|
}
|
|
|
|
float altitudeAngle = 0.0f;
|
|
if (@available(iOS 16.4, *)) {
|
|
altitudeAngle = (float) [recognizer altitudeAngle];
|
|
}
|
|
|
|
float rollAngle = 0.0f;
|
|
if (@available(iOS 17.5, *)) {
|
|
rollAngle = (float) [recognizer rollAngle];
|
|
}
|
|
|
|
SDL_Window *window = [view getSDLWindow];
|
|
const CGPoint point = [recognizer locationInView:view];
|
|
|
|
// force is zero here; if you're here, you're not touching.
|
|
// !!! FIXME: no timestamp on these...?
|
|
UIKit_HandlePenAxes(window, 0, zOffset, &point, 0.0f, 1.0f, azimuthAngleInView, altitudeAngle, rollAngle);
|
|
}
|
|
#endif
|
|
|
|
static void UIKit_HandlePenAxesFromUITouch(SDL_uikitview *view, UITouch *pencil)
|
|
{
|
|
float rollAngle = 0.0f;
|
|
#if !defined(SDL_PLATFORM_TVOS)
|
|
if (@available(iOS 17.5, *)) {
|
|
rollAngle = (float) [pencil rollAngle];
|
|
}
|
|
#endif
|
|
|
|
SDL_Window *window = [view getSDLWindow];
|
|
const CGPoint point = [pencil locationInView:view];
|
|
|
|
// zOffset is zero here; if you're here, you're touching.
|
|
UIKit_HandlePenAxes(window, [pencil timestamp], 0.0f, &point, [pencil force], [pencil maximumPossibleForce], [pencil azimuthAngleInView:view], [pencil altitudeAngle], rollAngle);
|
|
}
|
|
|
|
void UIKit_HandlePenMotion(SDL_uikitview *view, UITouch *pencil)
|
|
{
|
|
UIKit_HandlePenAxesFromUITouch(view, pencil);
|
|
}
|
|
|
|
void UIKit_HandlePenPress(SDL_uikitview *view, UITouch *pencil)
|
|
{
|
|
SDL_Window *window = [view getSDLWindow];
|
|
const SDL_PenID penId = UIKit_AddPenIfNecesary(window);
|
|
if (penId) {
|
|
UIKit_HandlePenAxesFromUITouch(view, pencil);
|
|
SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, window, false, true);
|
|
}
|
|
}
|
|
|
|
void UIKit_HandlePenRelease(SDL_uikitview *view, UITouch *pencil)
|
|
{
|
|
SDL_Window *window = [view getSDLWindow];
|
|
const SDL_PenID penId = UIKit_AddPenIfNecesary(window);
|
|
if (penId) {
|
|
SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, window, false, false);
|
|
UIKit_HandlePenAxesFromUITouch(view, pencil);
|
|
}
|
|
}
|
|
|
|
void UIKit_QuitPen(SDL_VideoDevice *_this)
|
|
{
|
|
if (apple_pencil_id) {
|
|
SDL_RemovePenDevice(0, NULL, apple_pencil_id);
|
|
apple_pencil_id = 0;
|
|
}
|
|
}
|
|
|
|
#endif // SDL_VIDEO_DRIVER_UIKIT
|