19#include <QtCore/qmath.h>
20#include <QtGui/qpointingdevice.h>
21#include <QtGui/private/qguiapplication_p.h>
22#include <QtGui/private/qwindow_p.h>
23#include <QtGui/private/qapplekeymapper_p.h>
24#include <QtGui/private/qpointingdevice_p.h>
25#include <qpa/qwindowsysteminterface_p.h>
28Q_LOGGING_CATEGORY(lcQpaInputEvents,
"qt.qpa.input.events")
31inline ulong getTimeStamp(UIEvent *event)
33#if TARGET_OS_SIMULATOR == 1
46#if defined(Q_PROCESSOR_ARM)
47 #warning The timestamp work-around for x86_64 can (probably) be removed when building for ARM
49 return ulong(NSProcessInfo.processInfo.systemUptime * 1000);
52 return ulong(event.timestamp * 1000);
56@implementation QUIView {
57 QHash<NSUInteger, QWindowSystemInterface::TouchPoint> m_activeTouches;
58 UITouch *m_activePencilTouch;
59 NSMutableArray<UIAccessibilityElement *> *m_accessibleElements;
60 UIPanGestureRecognizer *m_scrollGestureRecognizer;
61 CGPoint m_lastScrollCursorPos;
62 CGPoint m_lastScrollDelta;
63#if QT_CONFIG(tabletevent)
64 UIHoverGestureRecognizer *m_hoverGestureRecognizer;
71 return [CAEAGLLayer
class];
73 return [super layerClass];
76- (instancetype)initWithQIOSWindow:(QT_PREPEND_NAMESPACE(QIOSWindow) *)window
78 if (self = [self initWithFrame:window->geometry().toCGRect()]) {
79 self.platformWindow = window;
81 if (isQtApplication())
84 m_accessibleElements = [[NSMutableArray<UIAccessibilityElement *> alloc] init];
87 self.multipleTouchEnabled = YES;
90 m_scrollGestureRecognizer = [[UIPanGestureRecognizer alloc]
92 action:@selector(handleScroll:)];
97 m_scrollGestureRecognizer.allowedTouchTypes = [NSArray array];
98 m_scrollGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskAll;
99 m_scrollGestureRecognizer.maximumNumberOfTouches = 0;
100 m_lastScrollDelta = CGPointZero;
101 m_lastScrollCursorPos = CGPointZero;
102 [self addGestureRecognizer:m_scrollGestureRecognizer];
104#if QT_CONFIG(tabletevent)
105 m_hoverGestureRecognizer = [[UIHoverGestureRecognizer alloc]
107 action:@selector(handleHover:)];
108 [self addGestureRecognizer:m_hoverGestureRecognizer];
112 if ([self.layer isKindOfClass:CAMetalLayer.
class]) {
113 QWindow *window = self.platformWindow->window();
114 if (QColorSpace colorSpace = window->format().colorSpace(); colorSpace.isValid()) {
115 QCFType<CFDataRef> iccData = colorSpace.iccProfile().toCFData();
116 QCFType<CGColorSpaceRef> cgColorSpace = CGColorSpaceCreateWithICCData(iccData);
117 CAMetalLayer *metalLayer =
static_cast<CAMetalLayer *>(self.layer);
118 metalLayer.colorspace = cgColorSpace;
119 qCDebug(lcQpaWindow) <<
"Set" << self <<
"color space to" << metalLayer.colorspace;
123 else if ([self.layer isKindOfClass:[CAEAGLLayer
class]]) {
124 CAEAGLLayer *eaglLayer =
static_cast<CAEAGLLayer *>(self.layer);
125 eaglLayer.opaque = TRUE;
126 eaglLayer.drawableProperties = @{
127 kEAGLDrawablePropertyRetainedBacking: @(YES),
128 kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
133#if defined(Q_OS_VISIONOS)
138 self.contentScaleFactor = self.platformWindow->screen()->devicePixelRatio();
147 [m_accessibleElements release];
148 [m_scrollGestureRecognizer release];
155 NSMutableString *description = [NSMutableString stringWithString:[super description]];
157#ifndef QT_NO_DEBUG_STREAM
158 QString platformWindowDescription;
159 QDebug debug(&platformWindowDescription);
160 debug.nospace() <<
"; " << self.platformWindow <<
">";
161 NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1];
162 [description replaceCharactersInRange:lastCharacter withString:platformWindowDescription.toNSString()];
168#if !defined(Q_OS_VISIONOS)
169- (
void)willMoveToWindow:(UIWindow *)newWindow
173 self.contentScaleFactor = newWindow && newWindow.screen ?
174 newWindow.screen.scale : [[UIScreen mainScreen] scale];
180- (
void)didAddSubview:(UIView *)subview
182 if ([subview isKindOfClass:[QUIView
class]])
183 self.clipsToBounds = YES;
186- (
void)willRemoveSubview:(UIView *)subview
188 for (UIView *view in self.subviews) {
189 if (view != subview && [view isKindOfClass:[QUIView
class]])
193 self.clipsToBounds = NO;
196- (
void)setNeedsDisplay
198 [super setNeedsDisplay];
202 [self.layer setNeedsDisplay];
205- (
void)layoutSubviews
213 if (!CGAffineTransformIsIdentity(self.transform))
214 qWarning() << self <<
"has a transform set. This is not supported.";
216 QWindow *window = self.platformWindow->window();
217 QRect lastReportedGeometry = qt_window_private(window)->geometry;
218 QRect currentGeometry = QRectF::fromCGRect(self.frame).toRect();
219 qCDebug(lcQpaWindow) << self.platformWindow <<
"new geometry is" << currentGeometry;
220 QWindowSystemInterface::handleGeometryChange(window, currentGeometry);
222 if (currentGeometry.size() != lastReportedGeometry.size()) {
224 [self setNeedsDisplay];
231- (
void)displayLayer:(CALayer *)layer
234 Q_ASSERT(layer == self.layer);
236 if (!self.platformWindow)
239 [self sendUpdatedExposeEvent];
242- (
void)sendUpdatedExposeEvent
246 if (self.platformWindow->isExposed()) {
247 QSize bounds = QRectF::fromCGRect(self.layer.bounds).toRect().size();
249 Q_ASSERT(self.platformWindow->geometry().size() == bounds);
250 Q_ASSERT(self.hidden == !self.platformWindow->window()->isVisible());
252 region = QRect(QPoint(), bounds);
255 qCDebug(lcQpaWindow) << self.platformWindow << region <<
"isExposed" << self.platformWindow->isExposed();
256 QWindowSystemInterface::handleExposeEvent(self.platformWindow->window(), region);
259- (
void)safeAreaInsetsDidChange
261 QWindowSystemInterface::handleSafeAreaMarginsChanged(self.platformWindow->window());
266- (BOOL)canBecomeFirstResponder
268 return !(self.platformWindow->window()->flags() & (Qt::WindowDoesNotAcceptFocus
269 | Qt::WindowTransparentForInput));
272- (BOOL)becomeFirstResponder
278 FirstResponderCandidate firstResponderCandidate(self);
280 qImDebug() <<
"self:" << self <<
"first:" << [UIResponder qt_currentFirstResponder];
282 if (![super becomeFirstResponder]) {
283 qImDebug() << self <<
"was not allowed to become first responder";
287 qImDebug() << self <<
"became first responder";
290 if (qGuiApp->focusWindow() != self.platformWindow->window())
291 QWindowSystemInterface::handleFocusWindowChanged(self.platformWindow->window(), Qt::ActiveWindowFocusReason);
293 qImDebug() << self.platformWindow->window() <<
"already active, not sending window activation";
298- (BOOL)responderShouldTriggerWindowDeactivation:(UIResponder *)responder
302 if ([responder isKindOfClass:[QUIView
class]])
307 if ([responder isKindOfClass:[QIOSTextResponder
class]]) {
308 while ((responder = [responder nextResponder])) {
309 if ([responder isKindOfClass:[QUIView
class]])
317- (BOOL)resignFirstResponder
319 qImDebug() <<
"self:" << self <<
"first:" << [UIResponder qt_currentFirstResponder];
321 if (![super resignFirstResponder])
324 qImDebug() << self <<
"resigned first responder";
327 UIResponder *newResponder = FirstResponderCandidate::currentCandidate();
328 if ([self responderShouldTriggerWindowDeactivation:newResponder])
329 QWindowSystemInterface::handleFocusWindowChanged(
nullptr, Qt::ActiveWindowFocusReason);
335- (BOOL)isActiveWindow
341 if ([self isFirstResponder])
344 UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
345 if ([firstResponder isKindOfClass:[QIOSTextInputResponder
class]]
346 && [firstResponder nextResponder] == self)
354- (
void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
356 [super traitCollectionDidChange: previousTraitCollection];
358 QPointingDevice *touchDevice = QIOSIntegration::instance()->touchDevice();
359 auto *devicePriv = QPointingDevicePrivate::get(touchDevice);
361 auto capabilities = touchDevice->capabilities();
362 capabilities.setFlag(QPointingDevice::Capability::Pressure,
363 (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable));
364 devicePriv->setCapabilities(capabilities);
367-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
369 if (self.platformWindow->window()->flags() & Qt::WindowTransparentForInput)
371 return [super pointInside:point withEvent:event];
374#if QT_CONFIG(tabletevent)
375- (
void)handlePencilEventForLocationInView:(CGPoint)locationInView withState:(QEventPoint::State)state withTimestamp:(ulong)timeStamp
376 withForce:(CGFloat)force withMaximumPossibleForce:(CGFloat)maximumPossibleForce withZOffset:(CGFloat)zOffset
377 withAzimuthUnitVector:(CGVector)azimuth withAltitudeAngleRadian:(CGFloat)altitudeAngleRadian
379 QIOSIntegration *iosIntegration = QIOSIntegration::instance();
381 QPointF localViewPosition = QPointF::fromCGPoint(locationInView);
382 QPoint localViewPositionI = localViewPosition.toPoint();
383 QPointF globalScreenPosition = self.platformWindow->mapToGlobal(localViewPositionI) +
384 (localViewPosition - localViewPositionI);
386 if (force != 0 && maximumPossibleForce != 0)
387 pressure = force / maximumPossibleForce;
391 qreal altitudeAngle = 90 - qRadiansToDegrees(altitudeAngleRadian);
392 qreal xTilt = qBound(-60.0, altitudeAngle * azimuth.dx, 60.0);
393 qreal yTilt = qBound(-60.0, altitudeAngle * azimuth.dy, 60.0);
395 qCDebug(lcQpaTablet) <<
":" << timeStamp << localViewPosition << pressure << state <<
"azimuth" << azimuth.dx << azimuth.dy
396 <<
"altitude" << altitudeAngleRadian <<
"xTilt" << xTilt <<
"yTilt" << yTilt;
397 QWindowSystemInterface::handleTabletEvent(self.platformWindow->window(), timeStamp,
399 iosIntegration->pencilDevice(), localViewPosition, globalScreenPosition,
401 state == QEventPoint::State::Released ? Qt::NoButton : Qt::LeftButton,
403 pressure, xTilt, yTilt, 0, 0, zOffset, Qt::NoModifier);
407- (
void)handleTouches:(NSSet *)touches withEvent:(UIEvent *)event withState:(QEventPoint::State)state withTimestamp:(ulong)timeStamp
409 QIOSIntegration *iosIntegration = QIOSIntegration::instance();
410 bool supportsPressure = QIOSIntegration::instance()->touchDevice()->capabilities() & QPointingDevice::Capability::Pressure;
412#if QT_CONFIG(tabletevent)
413 if (m_activePencilTouch && [touches containsObject:m_activePencilTouch]) {
414 NSArray<UITouch *> *cTouches = [event coalescedTouchesForTouch:m_activePencilTouch];
415 for (UITouch *cTouch in cTouches) {
416 [self handlePencilEventForLocationInView:[cTouch preciseLocationInView:self] withState:state withTimestamp:timeStamp
417 withForce:cTouch.force withMaximumPossibleForce:cTouch.maximumPossibleForce withZOffset:0
418 withAzimuthUnitVector:[cTouch azimuthUnitVectorInView:self]
419 withAltitudeAngleRadian:cTouch.altitudeAngle];
424 if (m_activeTouches.isEmpty())
426 for (
auto it = m_activeTouches.begin(); it != m_activeTouches.end(); ++it) {
427 auto hash = it.key();
428 QWindowSystemInterface::TouchPoint &touchPoint = it.value();
429 UITouch *uiTouch = nil;
430 for (UITouch *touch in touches) {
431 if (touch.hash == hash) {
437 touchPoint.state = QEventPoint::State::Stationary;
439 touchPoint.state = state;
445 QPoint localViewPosition = QPointF::fromCGPoint([uiTouch locationInView:self]).toPoint();
446 QPoint globalScreenPosition = self.platformWindow->mapToGlobal(localViewPosition);
448 touchPoint.area = QRectF(globalScreenPosition, QSize(0, 0));
451 QSize screenSize = self.platformWindow->screen()->geometry().size();
452 touchPoint.normalPosition = QPointF(globalScreenPosition.x() / screenSize.width(),
453 globalScreenPosition.y() / screenSize.height());
455 if (supportsPressure) {
460 touchPoint.pressure = uiTouch.force / uiTouch.maximumPossibleForce;
464 touchPoint.pressure = (state == QEventPoint::State::Released) ? 0.0 : 1.0;
469 if ([self.window isKindOfClass:[QUIWindow
class]] &&
470 !
static_cast<QUIWindow *>(self.window).sendingEvent) {
479 QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>(
480 self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
485 QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>(
486 self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
490- (
void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
496 for (UITouch *touch in touches) {
497#if QT_CONFIG(tabletevent)
498 if (touch.type == UITouchTypeStylus) {
499 if (Q_UNLIKELY(m_activePencilTouch)) {
500 qWarning(
"ignoring additional Pencil while first is still active");
503 m_activePencilTouch = touch;
506 Q_ASSERT(!m_activeTouches.contains(touch.hash));
510 static quint16 nextTouchId = 0;
511 m_activeTouches[touch.hash].id = nextTouchId++;
512#if QT_CONFIG(tabletevent)
517 if (self.platformWindow->shouldAutoActivateWindow() && m_activeTouches.size() == 1) {
518 QPlatformWindow *topLevel = self.platformWindow;
519 while (QPlatformWindow *p = topLevel->parent())
521 if (topLevel->window() != QGuiApplication::focusWindow())
522 topLevel->requestActivateWindow();
525 [self handleTouches:touches withEvent:event withState:QEventPoint::State::Pressed withTimestamp:getTimeStamp(event)];
528- (
void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
530 [self handleTouches:touches withEvent:event withState:QEventPoint::State::Updated withTimestamp:getTimeStamp(event)];
533- (
void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
535 [self handleTouches:touches withEvent:event withState:QEventPoint::State::Released withTimestamp:getTimeStamp(event)];
539 for (UITouch *touch in touches) {
540#if QT_CONFIG(tabletevent)
541 if (touch.type == UITouchTypeStylus) {
542 m_activePencilTouch = nil;
546 m_activeTouches.remove(touch.hash);
551 m_activeTouches.clear();
555- (
void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
557 if (m_activeTouches.isEmpty() && !m_activePencilTouch)
580 NSInteger count =
static_cast<NSInteger>([touches count]);
581 if (count != 0 && count != m_activeTouches.count() && !m_activePencilTouch)
582 qWarning(
"Subset of active touches cancelled by UIKit");
584 m_activeTouches.clear();
585 m_activePencilTouch = nil;
587 ulong timestamp = event ? getTimeStamp(event) : ([[NSProcessInfo processInfo] systemUptime] * 1000);
589 QIOSIntegration *iosIntegration =
static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration());
594 QWindowSystemInterface::handleTouchCancelEvent<QWindowSystemInterface::AsynchronousDelivery>(
595 self.platformWindow->window(), timestamp, iosIntegration->touchDevice());
598- (
int)mapPressTypeToKey:(UIPress*)press withModifiers:(Qt::KeyboardModifiers)qtModifiers text:(QString &)text
600 switch (press.type) {
601 case UIPressTypeUpArrow:
return Qt::Key_Up;
602 case UIPressTypeDownArrow:
return Qt::Key_Down;
603 case UIPressTypeLeftArrow:
return Qt::Key_Left;
604 case UIPressTypeRightArrow:
return Qt::Key_Right;
605 case UIPressTypeSelect:
return Qt::Key_Select;
606 case UIPressTypeMenu:
return Qt::Key_Menu;
607 case UIPressTypePlayPause:
return Qt::Key_MediaTogglePlayPause;
609 Qt::Key key = QAppleKeyMapper::fromUIKitKey(press.key.keyCode);
610 if (key != Qt::Key_unknown)
612 NSString *charactersIgnoringModifiers = press.key.charactersIgnoringModifiers;
613 key = QAppleKeyMapper::fromUIKitKey(charactersIgnoringModifiers);
614 if (key != Qt::Key_unknown)
616 key = QAppleKeyMapper::fromNSString(qtModifiers, press.key.characters,
617 charactersIgnoringModifiers, text);
618 if (key != Qt::Key_unknown)
620 return Qt::Key_unknown;
623- (
bool)isControlKey:(Qt::Key)key
638- (
bool)handlePresses:(NSSet<UIPress *> *)presses eventType:(QEvent::Type)type
644 if (!qApp->focusWindow())
647 bool eventHandled =
false;
648 const bool imEnabled = QIOSInputContext::instance()->inputMethodAccepted();
650 for (UIPress* press in presses) {
651 Qt::KeyboardModifiers qtModifiers = QAppleKeyMapper::fromUIKitModifiers(press.key.modifierFlags);
653 int key = [self mapPressTypeToKey:press withModifiers:qtModifiers text:text];
654 if (key == Qt::Key_unknown)
656 if (imEnabled && ![self isControlKey:Qt::Key(key)])
659 bool keyHandled = QWindowSystemInterface::handleKeyEvent(
660 self.platformWindow->window(), type, key, qtModifiers, text);
661 eventHandled = eventHandled || keyHandled;
667- (
void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
669 if (![self handlePresses:presses eventType:QEvent::KeyPress])
670 [super pressesBegan:presses withEvent:event];
673- (
void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
675 if (![self handlePresses:presses eventType:QEvent::KeyPress])
676 [super pressesChanged:presses withEvent:event];
677 [super pressesChanged:presses withEvent:event];
680- (
void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
682 if (![self handlePresses:presses eventType:QEvent::KeyRelease])
683 [super pressesEnded:presses withEvent:event];
684 [super pressesEnded:presses withEvent:event];
687- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
689#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
691 return [QIOSMenu::menuActionTarget() targetForAction:action withSender:sender] != 0;
699- (
id)forwardingTargetForSelector:(SEL)selector
702#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
703 return QIOSMenu::menuActionTarget();
709- (
void)addInteraction:(id<UIInteraction>)interaction
711 if ([NSStringFromClass(interaction.
class) isEqualToString:@
"UITextInteraction"])
714 [super addInteraction:interaction];
717- (UIEditingInteractionConfiguration)editingInteractionConfiguration
725 return QIOSInputContext::instance()->inputMethodAccepted() ?
726 UIEditingInteractionConfigurationDefault : UIEditingInteractionConfigurationNone;
729#if QT_CONFIG(wheelevent)
730- (
void)handleScroll:(UIPanGestureRecognizer *)recognizer
732 if (!self.platformWindow->window())
735 if (!self.canBecomeFirstResponder)
738 CGPoint translation = [recognizer translationInView:self];
739 CGFloat deltaX = translation.x - m_lastScrollDelta.x;
740 CGFloat deltaY = translation.y - m_lastScrollDelta.y;
748 const int pixelsToDegrees = 2;
749 angleDelta.setX(deltaX * pixelsToDegrees);
750 angleDelta.setY(deltaY * pixelsToDegrees);
753 pixelDelta.setX(deltaX);
754 pixelDelta.setY(deltaY);
756 NSTimeInterval time_stamp = [[NSProcessInfo processInfo] systemUptime];
757 ulong qt_timestamp = time_stamp * 1000;
759 Qt::KeyboardModifiers qt_modifierFlags = QAppleKeyMapper::fromUIKitModifiers(recognizer.modifierFlags);
761 if (recognizer.state == UIGestureRecognizerStateBegan)
765 m_lastScrollCursorPos = [recognizer locationInView:self];
767 if (recognizer.state != UIGestureRecognizerStateEnded) {
768 m_lastScrollDelta.x = translation.x;
769 m_lastScrollDelta.y = translation.y;
771 m_lastScrollDelta = CGPointZero;
774 QPoint qt_local = QPointF::fromCGPoint(m_lastScrollCursorPos).toPoint();
775 QPoint qt_global = self.platformWindow->mapToGlobal(qt_local);
777 qCInfo(lcQpaInputEvents).nospace() <<
"wheel event" <<
" at " << qt_local
778 <<
" pixelDelta=" << pixelDelta <<
" angleDelta=" << angleDelta;
780 QWindowSystemInterface::handleWheelEvent(self.platformWindow->window(), qt_timestamp, qt_local, qt_global, pixelDelta, angleDelta, qt_modifierFlags);
784#if QT_CONFIG(tabletevent)
785- (
void)handleHover:(UIHoverGestureRecognizer *)recognizer
787 if (!self.platformWindow)
790 ulong timeStamp = [[NSProcessInfo processInfo] systemUptime] * 1000;
792 CGFloat zOffset = [recognizer zOffset];
794 CGVector azimuth = [recognizer azimuthUnitVectorInView:self];
795 CGFloat altitudeAngleRadian = recognizer.altitudeAngle;
797 [self handlePencilEventForLocationInView:[recognizer locationInView:self] withState:QEventPoint::State::Released
798 withTimestamp:timeStamp withForce:0 withMaximumPossibleForce:0 withZOffset:zOffset
799 withAzimuthUnitVector:azimuth withAltitudeAngleRadian:altitudeAngleRadian];
805@implementation UIView (QtHelpers)
809 if ([self isKindOfClass:[QUIView
class]]) {
810 if (QT_PREPEND_NAMESPACE(QIOSWindow) *w =
static_cast<QUIView *>(self).platformWindow)
819 while ((responder = [responder nextResponder])) {
820 if ([responder isKindOfClass:UIViewController.
class])
826- (QIOSViewController*)qtViewController
828 UIViewController *vc = self.viewController;
829 if ([vc isKindOfClass:QIOSViewController.
class])
830 return static_cast<QIOSViewController *>(vc);
838@implementation QUIMetalView
842 return [CAMetalLayer
class];
848#if QT_CONFIG(accessibility)
850#include "quiview_accessibility.mm"
Q_FORWARD_DECLARE_OBJC_CLASS(NSString)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
Q_FORWARD_DECLARE_OBJC_CLASS(UIViewController)