diff options
Diffstat (limited to 'src/plugins/platforms/ios/quiview.mm')
-rw-r--r-- | src/plugins/platforms/ios/quiview.mm | 182 |
1 files changed, 143 insertions, 39 deletions
diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm index 32f27587c6..8cc5bd3734 100644 --- a/src/plugins/platforms/ios/quiview.mm +++ b/src/plugins/platforms/ios/quiview.mm @@ -22,12 +22,42 @@ #include <qpa/qwindowsysteminterface_p.h> Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") +Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events") + +namespace { +inline ulong getTimeStamp(UIEvent *event) +{ +#if TARGET_OS_SIMULATOR == 1 + // We currently build Qt for simulator using X86_64, even on ARM based macs. + // This results in the simulator running on ARM, while the app is running + // inside it using Rosetta. And with this combination, the event.timestamp, which is + // documented to be in seconds, looks to be something else, and is not progressing + // in sync with a normal clock. + // Sending out mouse events with a timestamp that doesn't follow normal clock time + // will cause problems for mouse-, and pointer handlers that uses them to e.g calculate + // the time between a press and release, and to decide if the user is performing a tap + // or a drag. + // For that reason, we choose to ignore UIEvent.timestamp under the mentioned condition, and + // instead rely on NSProcessInfo. Note that if we force the whole simulator to use Rosetta + // (and not only the Qt app), the timestamps will progress normally. +#if defined(Q_PROCESSOR_ARM) + #warning The timestamp work-around for x86_64 can (probably) be removed when building for ARM +#endif + return ulong(NSProcessInfo.processInfo.systemUptime * 1000); +#endif + + return ulong(event.timestamp * 1000); +} +} @implementation QUIView { QHash<NSUInteger, QWindowSystemInterface::TouchPoint> m_activeTouches; UITouch *m_activePencilTouch; int m_nextTouchId; NSMutableArray<UIAccessibilityElement *> *m_accessibleElements; + UIPanGestureRecognizer *m_scrollGestureRecognizer; + CGPoint m_lastScrollCursorPos; + CGPoint m_lastScrollDelta; } + (void)load @@ -60,6 +90,23 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") if (self = [self initWithFrame:window->geometry().toCGRect()]) { self.platformWindow = window; m_accessibleElements = [[NSMutableArray<UIAccessibilityElement *> alloc] init]; + m_scrollGestureRecognizer = [[UIPanGestureRecognizer alloc] + initWithTarget:self + action:@selector(handleScroll:)]; + // The gesture recognizer should only care about scroll gestures (for now) + // Set allowedTouchTypes to empty array here to not interfere with touch events + // handled by the UIView. Scroll gestures, even those coming from touch devices, + // such as trackpads will still be received as they are not touch events + m_scrollGestureRecognizer.allowedTouchTypes = [NSArray array]; +#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_13_4) + if (@available(ios 13.4, *)) { + m_scrollGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskAll; + } +#endif + m_scrollGestureRecognizer.maximumNumberOfTouches = 0; + m_lastScrollDelta = CGPointZero; + m_lastScrollCursorPos = CGPointZero; + [self addGestureRecognizer:m_scrollGestureRecognizer]; } return self; @@ -117,6 +164,7 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") - (void)dealloc { [m_accessibleElements release]; + [m_scrollGestureRecognizer release]; [super dealloc]; } @@ -470,17 +518,17 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") topLevel->requestActivateWindow(); } - [self handleTouches:touches withEvent:event withState:QEventPoint::State::Pressed withTimestamp:ulong(event.timestamp * 1000)]; + [self handleTouches:touches withEvent:event withState:QEventPoint::State::Pressed withTimestamp:getTimeStamp(event)]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - [self handleTouches:touches withEvent:event withState:QEventPoint::State::Updated withTimestamp:ulong(event.timestamp * 1000)]; + [self handleTouches:touches withEvent:event withState:QEventPoint::State::Updated withTimestamp:getTimeStamp(event)]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - [self handleTouches:touches withEvent:event withState:QEventPoint::State::Released withTimestamp:ulong(event.timestamp * 1000)]; + [self handleTouches:touches withEvent:event withState:QEventPoint::State::Released withTimestamp:getTimeStamp(event)]; // Remove ended touch points from the active set: #ifndef Q_OS_TVOS @@ -536,7 +584,7 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") m_nextTouchId = 0; m_activePencilTouch = nil; - NSTimeInterval timestamp = event ? event.timestamp : [[NSProcessInfo processInfo] systemUptime]; + ulong timestamp = event ? getTimeStamp(event) : ([[NSProcessInfo processInfo] systemUptime] * 1000); QIOSIntegration *iosIntegration = static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration()); @@ -544,7 +592,7 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") // event loop in response to the touch event (a dialog e.g.), which will deadlock // the UIKit event delivery system (QTBUG-98651). QWindowSystemInterface::handleTouchCancelEvent<QWindowSystemInterface::AsynchronousDelivery>( - self.platformWindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice()); + self.platformWindow->window(), timestamp, iosIntegration->touchDevice()); } - (int)mapPressTypeToKey:(UIPress*)press withModifiers:(Qt::KeyboardModifiers)qtModifiers text:(QString &)text @@ -558,7 +606,6 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") case UIPressTypeMenu: return Qt::Key_Menu; case UIPressTypePlayPause: return Qt::Key_MediaTogglePlayPause; } -#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_13_4) if (@available(ios 13.4, *)) { NSString *charactersIgnoringModifiers = press.key.charactersIgnoringModifiers; Qt::Key key = QAppleKeyMapper::fromUIKitKey(charactersIgnoringModifiers); @@ -567,47 +614,53 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") return QAppleKeyMapper::fromNSString(qtModifiers, press.key.characters, charactersIgnoringModifiers, text); } -#endif return Qt::Key_unknown; } -- (bool)processPresses:(NSSet *)presses withType:(QEvent::Type)type { +- (bool)isControlKey:(Qt::Key)key +{ + switch (key) { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + return true; + default: + break; + } + + return false; +} + +- (bool)handlePresses:(NSSet<UIPress *> *)presses eventType:(QEvent::Type)type +{ // Presses on Menu button will generate a Menu key event. By default, not handling // this event will cause the application to return to Headboard (tvOS launcher). // When handling the event (for example, as a back button), both press and // release events must be handled accordingly. + if (!qApp->focusWindow()) + return false; + + bool eventHandled = false; + const bool imEnabled = QIOSInputContext::instance()->inputMethodAccepted(); - bool handled = false; for (UIPress* press in presses) { Qt::KeyboardModifiers qtModifiers = Qt::NoModifier; -#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_13_4) if (@available(ios 13.4, *)) qtModifiers = QAppleKeyMapper::fromUIKitModifiers(press.key.modifierFlags); -#endif QString text; int key = [self mapPressTypeToKey:press withModifiers:qtModifiers text:text]; if (key == Qt::Key_unknown) continue; - if (QWindowSystemInterface::handleKeyEvent(self.platformWindow->window(), type, key, - qtModifiers, text)) { - handled = true; - } - } - - return handled; -} + if (imEnabled && ![self isControlKey:Qt::Key(key)]) + continue; -- (BOOL)handlePresses:(NSSet<UIPress *> *)presses eventType:(QEvent::Type)type -{ - bool handlePress = false; - if (qApp->focusWindow()) { - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (qApp->focusObject() && QCoreApplication::sendEvent(qApp->focusObject(), &queryEvent)) - handlePress = queryEvent.value(Qt::ImEnabled).toBool(); - if (!handlePress && [self processPresses:presses withType:type]) - return true; + bool keyHandled = QWindowSystemInterface::handleKeyEvent( + self.platformWindow->window(), type, key, qtModifiers, text); + eventHandled = eventHandled || keyHandled; } - return false; + + return eventHandled; } - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event @@ -619,14 +672,14 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") - (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event { if (![self handlePresses:presses eventType:QEvent::KeyPress]) - [super pressesBegan:presses withEvent:event]; + [super pressesChanged:presses withEvent:event]; [super pressesChanged:presses withEvent:event]; } - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event { if (![self handlePresses:presses eventType:QEvent::KeyRelease]) - [super pressesBegan:presses withEvent:event]; + [super pressesEnded:presses withEvent:event]; [super pressesEnded:presses withEvent:event]; } @@ -672,6 +725,65 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") UIEditingInteractionConfigurationDefault : UIEditingInteractionConfigurationNone; } +#if QT_CONFIG(wheelevent) +- (void)handleScroll:(UIPanGestureRecognizer *)recognizer +{ + if (!self.platformWindow->window()) + return; + + if (!self.canBecomeFirstResponder) + return; + + CGPoint translation = [recognizer translationInView:self]; + CGFloat deltaX = translation.x - m_lastScrollDelta.x; + CGFloat deltaY = translation.y - m_lastScrollDelta.y; + + QPoint angleDelta; + // From QNSView implementation: + // "Since deviceDelta is delivered as pixels rather than degrees, we need to + // convert from pixels to degrees in a sensible manner. + // It looks like 1/4 degrees per pixel behaves most native. + // (NB: Qt expects the unit for delta to be 8 per degree):" + const int pixelsToDegrees = 2; // 8 * 1/4 + angleDelta.setX(deltaX * pixelsToDegrees); + angleDelta.setY(deltaY * pixelsToDegrees); + + QPoint pixelDelta; + pixelDelta.setX(deltaX); + pixelDelta.setY(deltaY); + + NSTimeInterval time_stamp = [[NSProcessInfo processInfo] systemUptime]; + ulong qt_timestamp = time_stamp * 1000; + + Qt::KeyboardModifiers qt_modifierFlags = Qt::NoModifier; +#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_13_4) + if (@available(ios 13.4, *)) + qt_modifierFlags = QAppleKeyMapper::fromUIKitModifiers(recognizer.modifierFlags); +#endif + + if (recognizer.state == UIGestureRecognizerStateBegan) + // locationInView: doesn't return the cursor position at the time of the wheel event, + // but rather gives us the position with the deltas applied, so we need to save the + // cursor position at the beginning of the gesture + m_lastScrollCursorPos = [recognizer locationInView:self]; + + if (recognizer.state != UIGestureRecognizerStateEnded) { + m_lastScrollDelta.x = translation.x; + m_lastScrollDelta.y = translation.y; + } else { + m_lastScrollDelta = CGPointZero; + } + + QPoint qt_local = QPointF::fromCGPoint(m_lastScrollCursorPos).toPoint(); + QPoint qt_global = self.platformWindow->mapToGlobal(qt_local); + + qCInfo(lcQpaInputEvents).nospace() << "wheel event" << " at " << qt_local + << " pixelDelta=" << pixelDelta << " angleDelta=" << angleDelta; + + QWindowSystemInterface::handleWheelEvent(self.platformWindow->window(), qt_timestamp, qt_local, qt_global, pixelDelta, angleDelta, qt_modifierFlags); +} +#endif // QT_CONFIG(wheelevent) + @end @implementation UIView (QtHelpers) @@ -716,15 +828,7 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") + (Class)layerClass { -#ifdef TARGET_IPHONE_SIMULATOR - if (@available(ios 13.0, *)) -#endif - return [CAMetalLayer class]; - -#ifdef TARGET_IPHONE_SIMULATOR - return nil; -#endif } @end |