iOS: Add support for hover feature for Apple Pencil 2nd gen or later

Add UIHoverGestureRecognizer to support Hover feature.

Task-number: QTBUG-128473
Change-Id: I73bbd1b8cca1ffcb8fcc27f6c26cb4ab8830c690
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
HIDAKA Takahiro 2025-01-11 01:50:09 +09:00 committed by Tor Arne Vestbø
parent cf674f3845
commit 2429c73cc9

View File

@ -59,6 +59,9 @@ inline ulong getTimeStamp(UIEvent *event)
UIPanGestureRecognizer *m_scrollGestureRecognizer; UIPanGestureRecognizer *m_scrollGestureRecognizer;
CGPoint m_lastScrollCursorPos; CGPoint m_lastScrollCursorPos;
CGPoint m_lastScrollDelta; CGPoint m_lastScrollDelta;
#if QT_CONFIG(tabletevent)
UIHoverGestureRecognizer *m_hoverGestureRecognizer;
#endif
} }
+ (Class)layerClass + (Class)layerClass
@ -99,6 +102,13 @@ inline ulong getTimeStamp(UIEvent *event)
m_lastScrollCursorPos = CGPointZero; m_lastScrollCursorPos = CGPointZero;
[self addGestureRecognizer:m_scrollGestureRecognizer]; [self addGestureRecognizer:m_scrollGestureRecognizer];
#if QT_CONFIG(tabletevent)
m_hoverGestureRecognizer = [[UIHoverGestureRecognizer alloc]
initWithTarget:self
action:@selector(handleHover:)];
[self addGestureRecognizer:m_hoverGestureRecognizer];
#endif
// Set up layer // Set up layer
if ([self.layer isKindOfClass:CAMetalLayer.class]) { if ([self.layer isKindOfClass:CAMetalLayer.class]) {
QWindow *window = self.platformWindow->window(); QWindow *window = self.platformWindow->window();
@ -362,6 +372,39 @@ inline ulong getTimeStamp(UIEvent *event)
return [super pointInside:point withEvent:event]; return [super pointInside:point withEvent:event];
} }
#if QT_CONFIG(tabletevent)
- (void)handlePencilEventForLocationInView:(CGPoint)locationInView withState:(QEventPoint::State)state withTimestamp:(ulong)timeStamp
withForce:(CGFloat)force withMaximumPossibleForce:(CGFloat)maximumPossibleForce withZOffset:(CGFloat)zOffset
withAzimuthUnitVector:(CGVector)azimuth withAltitudeAngleRadian:(CGFloat)altitudeAngleRadian
{
QIOSIntegration *iosIntegration = QIOSIntegration::instance();
QPointF localViewPosition = QPointF::fromCGPoint(locationInView);
QPoint localViewPositionI = localViewPosition.toPoint();
QPointF globalScreenPosition = self.platformWindow->mapToGlobal(localViewPositionI) +
(localViewPosition - localViewPositionI);
qreal pressure = 0;
if (force != 0 && maximumPossibleForce != 0)
pressure = force / maximumPossibleForce;
// azimuth unit vector: +x to the right, +y going downwards
// altitudeAngleRadian given in radians, pi / 2 is with the stylus perpendicular to the iPad, smaller values mean more tilted, but never negative.
// Convert to degrees with zero being perpendicular.
qreal altitudeAngle = 90 - qRadiansToDegrees(altitudeAngleRadian);
qreal xTilt = qBound(-60.0, altitudeAngle * azimuth.dx, 60.0);
qreal yTilt = qBound(-60.0, altitudeAngle * azimuth.dy, 60.0);
qCDebug(lcQpaTablet) << ":" << timeStamp << localViewPosition << pressure << state << "azimuth" << azimuth.dx << azimuth.dy
<< "altitude" << altitudeAngleRadian << "xTilt" << xTilt << "yTilt" << yTilt;
QWindowSystemInterface::handleTabletEvent(self.platformWindow->window(), timeStamp,
// device, local, global
iosIntegration->pencilDevice(), localViewPosition, globalScreenPosition,
// buttons
state == QEventPoint::State::Released ? Qt::NoButton : Qt::LeftButton,
// pressure, xTilt, yTilt, tangentialPressure, rotation, z, modifiers
pressure, xTilt, yTilt, 0, 0, zOffset, Qt::NoModifier);
}
#endif
- (void)handleTouches:(NSSet *)touches withEvent:(UIEvent *)event withState:(QEventPoint::State)state withTimestamp:(ulong)timeStamp - (void)handleTouches:(NSSet *)touches withEvent:(UIEvent *)event withState:(QEventPoint::State)state withTimestamp:(ulong)timeStamp
{ {
QIOSIntegration *iosIntegration = QIOSIntegration::instance(); QIOSIntegration *iosIntegration = QIOSIntegration::instance();
@ -370,30 +413,11 @@ inline ulong getTimeStamp(UIEvent *event)
#if QT_CONFIG(tabletevent) #if QT_CONFIG(tabletevent)
if (m_activePencilTouch && [touches containsObject:m_activePencilTouch]) { if (m_activePencilTouch && [touches containsObject:m_activePencilTouch]) {
NSArray<UITouch *> *cTouches = [event coalescedTouchesForTouch:m_activePencilTouch]; NSArray<UITouch *> *cTouches = [event coalescedTouchesForTouch:m_activePencilTouch];
int i = 0;
for (UITouch *cTouch in cTouches) { for (UITouch *cTouch in cTouches) {
QPointF localViewPosition = QPointF::fromCGPoint([cTouch preciseLocationInView:self]); [self handlePencilEventForLocationInView:[cTouch preciseLocationInView:self] withState:state withTimestamp:timeStamp
QPoint localViewPositionI = localViewPosition.toPoint(); withForce:cTouch.force withMaximumPossibleForce:cTouch.maximumPossibleForce withZOffset:0
QPointF globalScreenPosition = self.platformWindow->mapToGlobal(localViewPositionI) + withAzimuthUnitVector:[cTouch azimuthUnitVectorInView:self]
(localViewPosition - localViewPositionI); withAltitudeAngleRadian:cTouch.altitudeAngle];
qreal pressure = cTouch.force / cTouch.maximumPossibleForce;
// azimuth unit vector: +x to the right, +y going downwards
CGVector azimuth = [cTouch azimuthUnitVectorInView:self];
// altitudeAngle given in radians, pi / 2 is with the stylus perpendicular to the iPad, smaller values mean more tilted, but never negative.
// Convert to degrees with zero being perpendicular.
qreal altitudeAngle = 90 - qRadiansToDegrees(cTouch.altitudeAngle);
qreal xTilt = qBound(-60.0, altitudeAngle * azimuth.dx, 60.0);
qreal yTilt = qBound(-60.0, altitudeAngle * azimuth.dy, 60.0);
qCDebug(lcQpaTablet) << i << ":" << timeStamp << localViewPosition << pressure << state << "azimuth" << azimuth.dx << azimuth.dy
<< "altitude" << cTouch.altitudeAngle << "xTilt" << xTilt << "yTilt" << yTilt;
QWindowSystemInterface::handleTabletEvent(self.platformWindow->window(), timeStamp,
// device, local, global
iosIntegration->pencilDevice(), localViewPosition, globalScreenPosition,
// buttons
state == QEventPoint::State::Released ? Qt::NoButton : Qt::LeftButton,
// pressure, xTilt, yTilt, tangentialPressure, rotation, z, modifiers
pressure, xTilt, yTilt, 0, 0, 0, Qt::NoModifier);
++i;
} }
} }
#endif #endif
@ -759,6 +783,28 @@ inline ulong getTimeStamp(UIEvent *event)
} }
#endif // QT_CONFIG(wheelevent) #endif // QT_CONFIG(wheelevent)
#if QT_CONFIG(tabletevent)
- (void)handleHover:(UIHoverGestureRecognizer *)recognizer
{
ulong timeStamp = [[NSProcessInfo processInfo] systemUptime] * 1000;
CGFloat zOffset = 0;
if (@available(ios 16.1, *))
zOffset = [recognizer zOffset];
CGVector azimuth;
CGFloat altitudeAngleRadian = 0;
if (@available(ios 16.4, *)) {
azimuth = [recognizer azimuthUnitVectorInView:self];
altitudeAngleRadian = recognizer.altitudeAngle;
}
[self handlePencilEventForLocationInView:[recognizer locationInView:self] withState:QEventPoint::State::Released
withTimestamp:timeStamp withForce:0 withMaximumPossibleForce:0 withZOffset:zOffset
withAzimuthUnitVector:azimuth withAltitudeAngleRadian:altitudeAngleRadian];
}
#endif
@end @end
@implementation UIView (QtHelpers) @implementation UIView (QtHelpers)