Add Apple Pencil support on iOS
The Apple Pencil now generates QTabletEvents, with tilt, rotation and pressure. Predicted touches are not supported, because we don't yet have a suitable QEvent or flag for that. [ChangeLog][iOS] The Apple Pencil now generates QTabletEvents, with the complete feature set (tilt, rotation, pressure). Task-number: QTBUG-59042 Change-Id: Id58e22ac4cf8dfa80519d516c388309966f773f9 Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
parent
3b5b8f1d4a
commit
b87a2b3508
@ -123,6 +123,7 @@ QIOSIntegration::QIOSIntegration()
|
|||||||
}
|
}
|
||||||
m_touchDevice->setCapabilities(touchCapabilities);
|
m_touchDevice->setCapabilities(touchCapabilities);
|
||||||
QWindowSystemInterface::registerTouchDevice(m_touchDevice);
|
QWindowSystemInterface::registerTouchDevice(m_touchDevice);
|
||||||
|
QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
|
||||||
QMacInternalPasteboardMime::initializeMimeTypes();
|
QMacInternalPasteboardMime::initializeMimeTypes();
|
||||||
|
|
||||||
for (int i = 0; i < m_optionalPlugins->metaData().size(); ++i)
|
for (int i = 0; i < m_optionalPlugins->metaData().size(); ++i)
|
||||||
|
@ -58,6 +58,7 @@ QT_END_NAMESPACE
|
|||||||
QT_PREPEND_NAMESPACE(QIOSWindow) *m_qioswindow;
|
QT_PREPEND_NAMESPACE(QIOSWindow) *m_qioswindow;
|
||||||
@private
|
@private
|
||||||
QHash<UITouch *, QWindowSystemInterface::TouchPoint> m_activeTouches;
|
QHash<UITouch *, QWindowSystemInterface::TouchPoint> m_activeTouches;
|
||||||
|
UITouch *m_activePencilTouch;
|
||||||
int m_nextTouchId;
|
int m_nextTouchId;
|
||||||
|
|
||||||
@private
|
@private
|
||||||
|
@ -53,6 +53,8 @@
|
|||||||
#include <QtGui/private/qwindow_p.h>
|
#include <QtGui/private/qwindow_p.h>
|
||||||
#include <qpa/qwindowsysteminterface_p.h>
|
#include <qpa/qwindowsysteminterface_p.h>
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet")
|
||||||
|
|
||||||
@implementation QUIView
|
@implementation QUIView
|
||||||
|
|
||||||
+ (Class)layerClass
|
+ (Class)layerClass
|
||||||
@ -311,11 +313,42 @@
|
|||||||
return [super pointInside:point withEvent:event];
|
return [super pointInside:point withEvent:event];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateTouchList:(NSSet *)touches withState:(Qt::TouchPointState)state
|
- (void)handleTouches:(NSSet *)touches withEvent:(UIEvent *)event withState:(Qt::TouchPointState)state withTimestamp:(ulong)timeStamp
|
||||||
{
|
{
|
||||||
|
QIOSIntegration *iosIntegration = QIOSIntegration::instance();
|
||||||
bool supportsPressure = QIOSIntegration::instance()->touchDevice()->capabilities() & QTouchDevice::Pressure;
|
bool supportsPressure = QIOSIntegration::instance()->touchDevice()->capabilities() & QTouchDevice::Pressure;
|
||||||
|
|
||||||
foreach (UITouch *uiTouch, m_activeTouches.keys()) {
|
if (m_activePencilTouch && [touches containsObject:m_activePencilTouch]) {
|
||||||
|
NSArray<UITouch *> *cTouches = [event coalescedTouchesForTouch:m_activePencilTouch];
|
||||||
|
int i = 0;
|
||||||
|
for (UITouch *cTouch in cTouches) {
|
||||||
|
QPointF localViewPosition = QPointF::fromCGPoint([cTouch preciseLocationInView:self]);
|
||||||
|
QPoint localViewPositionI = localViewPosition.toPoint();
|
||||||
|
QPointF globalScreenPosition = m_qioswindow->mapToGlobal(localViewPositionI) +
|
||||||
|
(localViewPosition - localViewPositionI);
|
||||||
|
qreal pressure = cTouch.force / cTouch.maximumPossibleForce;
|
||||||
|
// azimuth unit vector: +x to the right, +y going downwards
|
||||||
|
CGVector azimuth = [cTouch azimuthUnitVectorInView: self];
|
||||||
|
// azimuthAngle given in radians, zero when the stylus points towards +x axis; converted to degrees with 0 pointing straight up
|
||||||
|
qreal azimuthAngle = [cTouch azimuthAngleInView: self] * 180 / M_PI + 90;
|
||||||
|
// 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 - cTouch.altitudeAngle * 180 / M_PI;
|
||||||
|
qCDebug(lcQpaTablet) << i << ":" << timeStamp << localViewPosition << pressure << state << "azimuth" << azimuth.dx << azimuth.dy
|
||||||
|
<< "angle" << azimuthAngle << "altitude" << cTouch.altitudeAngle
|
||||||
|
<< "xTilt" << qBound(-60.0, altitudeAngle * azimuth.dx, 60.0) << "yTilt" << qBound(-60.0, altitudeAngle * azimuth.dy, 60.0);
|
||||||
|
QWindowSystemInterface::handleTabletEvent(m_qioswindow->window(), timeStamp, localViewPosition, globalScreenPosition,
|
||||||
|
// device, pointerType, buttons
|
||||||
|
QTabletEvent::RotationStylus, QTabletEvent::Pen, state == Qt::TouchPointReleased ? Qt::NoButton : Qt::LeftButton,
|
||||||
|
// pressure, xTilt, yTilt
|
||||||
|
pressure, qBound(-60.0, altitudeAngle * azimuth.dx, 60.0), qBound(-60.0, altitudeAngle * azimuth.dy, 60.0),
|
||||||
|
// tangentialPressure, rotation, z, uid, modifiers
|
||||||
|
0, azimuthAngle, 0, 0, Qt::NoModifier);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UITouch *uiTouch : m_activeTouches.keys()) {
|
||||||
QWindowSystemInterface::TouchPoint &touchPoint = m_activeTouches[uiTouch];
|
QWindowSystemInterface::TouchPoint &touchPoint = m_activeTouches[uiTouch];
|
||||||
if (![touches containsObject:uiTouch]) {
|
if (![touches containsObject:uiTouch]) {
|
||||||
touchPoint.state = Qt::TouchPointStationary;
|
touchPoint.state = Qt::TouchPointStationary;
|
||||||
@ -344,17 +377,13 @@
|
|||||||
touchPoint.pressure = uiTouch.force / uiTouch.maximumPossibleForce;
|
touchPoint.pressure = uiTouch.force / uiTouch.maximumPossibleForce;
|
||||||
} else {
|
} else {
|
||||||
// We don't claim that our touch device supports QTouchDevice::Pressure,
|
// We don't claim that our touch device supports QTouchDevice::Pressure,
|
||||||
// but fill in a meaningfull value in case clients use it anyways.
|
// but fill in a meaningful value in case clients use it anyway.
|
||||||
touchPoint.pressure = (state == Qt::TouchPointReleased) ? 0.0 : 1.0;
|
touchPoint.pressure = (state == Qt::TouchPointReleased) ? 0.0 : 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (!m_activeTouches.isEmpty())
|
||||||
|
QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(m_qioswindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
|
||||||
- (void)sendTouchEventWithTimestamp:(ulong)timeStamp
|
|
||||||
{
|
|
||||||
QIOSIntegration *iosIntegration = QIOSIntegration::instance();
|
|
||||||
QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(m_qioswindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
||||||
@ -364,8 +393,16 @@
|
|||||||
// points to QWindowSystemInterface::TouchPoints, and assigns each TouchPoint
|
// points to QWindowSystemInterface::TouchPoints, and assigns each TouchPoint
|
||||||
// an id for use by Qt.
|
// an id for use by Qt.
|
||||||
for (UITouch *touch in touches) {
|
for (UITouch *touch in touches) {
|
||||||
Q_ASSERT(!m_activeTouches.contains(touch));
|
if (touch.type == UITouchTypeStylus) {
|
||||||
m_activeTouches[touch].id = m_nextTouchId++;
|
if (Q_UNLIKELY(m_activePencilTouch)) {
|
||||||
|
qWarning("ignoring additional Pencil while first is still active");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_activePencilTouch = touch;
|
||||||
|
} else {
|
||||||
|
Q_ASSERT(!m_activeTouches.contains(touch));
|
||||||
|
m_activeTouches[touch].id = m_nextTouchId++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_qioswindow->shouldAutoActivateWindow() && m_activeTouches.size() == 1) {
|
if (m_qioswindow->shouldAutoActivateWindow() && m_activeTouches.size() == 1) {
|
||||||
@ -376,31 +413,33 @@
|
|||||||
topLevel->requestActivateWindow();
|
topLevel->requestActivateWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
[self updateTouchList:touches withState:Qt::TouchPointPressed];
|
[self handleTouches:touches withEvent:event withState:Qt::TouchPointPressed withTimestamp:ulong(event.timestamp * 1000)];
|
||||||
[self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
||||||
{
|
{
|
||||||
[self updateTouchList:touches withState:Qt::TouchPointMoved];
|
[self handleTouches:touches withEvent:event withState:Qt::TouchPointMoved withTimestamp:ulong(event.timestamp * 1000)];
|
||||||
[self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
||||||
{
|
{
|
||||||
[self updateTouchList:touches withState:Qt::TouchPointReleased];
|
[self handleTouches:touches withEvent:event withState:Qt::TouchPointReleased withTimestamp:ulong(event.timestamp * 1000)];
|
||||||
[self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)];
|
|
||||||
|
|
||||||
// Remove ended touch points from the active set:
|
// Remove ended touch points from the active set:
|
||||||
for (UITouch *touch in touches)
|
for (UITouch *touch in touches) {
|
||||||
m_activeTouches.remove(touch);
|
if (touch.type == UITouchTypeStylus) {
|
||||||
if (m_activeTouches.isEmpty())
|
m_activePencilTouch = nil;
|
||||||
|
} else {
|
||||||
|
m_activeTouches.remove(touch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_activeTouches.isEmpty() && !m_activePencilTouch)
|
||||||
m_nextTouchId = 0;
|
m_nextTouchId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
|
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
|
||||||
{
|
{
|
||||||
if (m_activeTouches.isEmpty())
|
if (m_activeTouches.isEmpty() && !m_activePencilTouch)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// When four-finger swiping, we get a touchesCancelled callback
|
// When four-finger swiping, we get a touchesCancelled callback
|
||||||
@ -424,11 +463,12 @@
|
|||||||
// sub-set of the active touch events are intentionally cancelled.
|
// sub-set of the active touch events are intentionally cancelled.
|
||||||
|
|
||||||
NSInteger count = static_cast<NSInteger>([touches count]);
|
NSInteger count = static_cast<NSInteger>([touches count]);
|
||||||
if (count != 0 && count != m_activeTouches.count())
|
if (count != 0 && count != m_activeTouches.count() && !m_activePencilTouch)
|
||||||
qWarning("Subset of active touches cancelled by UIKit");
|
qWarning("Subset of active touches cancelled by UIKit");
|
||||||
|
|
||||||
m_activeTouches.clear();
|
m_activeTouches.clear();
|
||||||
m_nextTouchId = 0;
|
m_nextTouchId = 0;
|
||||||
|
m_activePencilTouch = nil;
|
||||||
|
|
||||||
NSTimeInterval timestamp = event ? event.timestamp : [[NSProcessInfo processInfo] systemUptime];
|
NSTimeInterval timestamp = event ? event.timestamp : [[NSProcessInfo processInfo] systemUptime];
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user