summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/ios/qioswindow.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/ios/qioswindow.mm')
-rw-r--r--src/plugins/platforms/ios/qioswindow.mm283
1 files changed, 178 insertions, 105 deletions
diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm
index 1b6a802ca2..6a1080e238 100644
--- a/src/plugins/platforms/ios/qioswindow.mm
+++ b/src/plugins/platforms/ios/qioswindow.mm
@@ -1,57 +1,27 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the plugins of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qioswindow.h"
#include "qiosapplicationdelegate.h"
-#include "qioscontext.h"
#include "qiosglobal.h"
#include "qiosintegration.h"
#include "qiosscreen.h"
#include "qiosviewcontroller.h"
#include "quiview.h"
+#include "qiosinputcontext.h"
+
+#include <QtCore/private/qcore_mac_p.h>
#include <QtGui/private/qwindow_p.h>
+#include <QtGui/private/qhighdpiscaling_p.h>
#include <qpa/qplatformintegration.h>
+#if QT_CONFIG(opengl)
#import <QuartzCore/CAEAGLLayer.h>
-#ifdef Q_OS_IOS
+#endif
+
+#if QT_CONFIG(metal)
#import <QuartzCore/CAMetalLayer.h>
#endif
@@ -59,31 +29,48 @@
QT_BEGIN_NAMESPACE
-QIOSWindow::QIOSWindow(QWindow *window)
+enum {
+ defaultWindowWidth = 160,
+ defaultWindowHeight = 160
+};
+
+QIOSWindow::QIOSWindow(QWindow *window, WId nativeHandle)
: QPlatformWindow(window)
, m_windowLevel(0)
{
-#ifdef Q_OS_IOS
- if (window->surfaceType() == QSurface::MetalSurface)
- m_view = [[QUIMetalView alloc] initWithQIOSWindow:this];
- else
+ if (nativeHandle) {
+ m_view = reinterpret_cast<UIView *>(nativeHandle);
+ [m_view retain];
+ } else {
+#if QT_CONFIG(metal)
+ if (window->surfaceType() == QSurface::RasterSurface)
+ window->setSurfaceType(QSurface::MetalSurface);
+
+ if (window->surfaceType() == QSurface::MetalSurface)
+ m_view = [[QUIMetalView alloc] initWithQIOSWindow:this];
+ else
#endif
- m_view = [[QUIView alloc] initWithQIOSWindow:this];
+ m_view = [[QUIView alloc] initWithQIOSWindow:this];
+ }
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QIOSWindow::applicationStateChanged);
- setParent(QPlatformWindow::parent());
+ if (QPlatformWindow::parent())
+ setParent(QPlatformWindow::parent());
- // Resolve default window geometry in case it was not set before creating the
- // platform window. This picks up eg. minimum-size if set, and defaults to
- // the "maxmized" geometry (even though we're not in that window state).
- // FIXME: Detect if we apply a maximized geometry and send a window state
- // change event in that case.
- m_normalGeometry = initialGeometry(window, QPlatformWindow::geometry(),
- screen()->availableGeometry().width(), screen()->availableGeometry().height());
+ if (!isForeignWindow()) {
+ // Resolve default window geometry in case it was not set before creating the
+ // platform window. This picks up eg. minimum-size if set.
+ m_normalGeometry = initialGeometry(window, QPlatformWindow::geometry(),
+ defaultWindowWidth, defaultWindowHeight);
- setWindowState(window->windowStates());
- setOpacity(window->opacity());
+ setWindowState(window->windowStates());
+ setOpacity(window->opacity());
+ setMask(QHighDpi::toNativeLocalRegion(window->mask(), window));
+ } else {
+ // Pick up essential foreign window state
+ QPlatformWindow::setGeometry(QRectF::fromCGRect(m_view.frame).toRect());
+ }
Qt::ScreenOrientation initialOrientation = window->contentOrientation();
if (initialOrientation != Qt::PrimaryOrientation) {
@@ -100,13 +87,20 @@ QIOSWindow::~QIOSWindow()
// According to the UIResponder documentation, Cocoa Touch should react to system interruptions
// that "might cause the view to be removed from the window" by sending touchesCancelled, but in
// practice this doesn't seem to happen when removing the view from its superview. To ensure that
- // Qt's internal state for touch and mouse handling is kept consistent, we therefor have to force
+ // Qt's internal state for touch and mouse handling is kept consistent, we therefore have to force
// cancellation of all touch events.
[m_view touchesCancelled:[NSSet set] withEvent:0];
clearAccessibleCache();
- m_view.platformWindow = 0;
- [m_view removeFromSuperview];
+
+ quiview_cast(m_view).platformWindow = nullptr;
+
+ // Remove from superview, unless we're a foreign window without a
+ // Qt window parent, in which case the foreign window is used as
+ // a window container for a Qt UI hierarchy inside a native UI.
+ if (!(isForeignWindow() && !QPlatformWindow::parent()))
+ [m_view removeFromSuperview];
+
[m_view release];
}
@@ -145,7 +139,7 @@ void QIOSWindow::setVisible(bool visible)
if (visible && shouldAutoActivateWindow()) {
if (!window()->property("_q_showWithoutActivating").toBool())
requestActivateWindow();
- } else if (!visible && [m_view isActiveWindow]) {
+ } else if (!visible && [quiview_cast(m_view) isActiveWindow]) {
// Our window was active/focus window but now hidden, so relinquish
// focus to the next possible window in the stack.
NSArray<UIView *> *subviews = m_view.viewController.view.subviews;
@@ -234,7 +228,7 @@ void QIOSWindow::applyGeometry(const QRect &rect)
QMargins QIOSWindow::safeAreaMargins() const
{
- UIEdgeInsets safeAreaInsets = m_view.qt_safeAreaInsets;
+ UIEdgeInsets safeAreaInsets = m_view.safeAreaInsets;
return QMargins(safeAreaInsets.left, safeAreaInsets.top,
safeAreaInsets.right, safeAreaInsets.bottom);
}
@@ -258,22 +252,45 @@ void QIOSWindow::setWindowState(Qt::WindowStates state)
if (state & Qt::WindowMinimized) {
applyGeometry(QRect());
} else if (state & (Qt::WindowFullScreen | Qt::WindowMaximized)) {
- // When an application is in split-view mode, the UIScreen still has the
- // same geometry, but the UIWindow is resized to the area reserved for the
- // application. We use this to constrain the geometry used when applying the
- // fullscreen or maximized window states. Note that we do not do this
- // in applyGeometry(), as we don't want to artificially limit window
- // placement "outside" of the screen bounds if that's what the user wants.
-
QRect uiWindowBounds = QRectF::fromCGRect(m_view.window.bounds).toRect();
- QRect fullscreenGeometry = screen()->geometry().intersected(uiWindowBounds);
- QRect maximizedGeometry = window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint ?
- fullscreenGeometry : screen()->availableGeometry().intersected(uiWindowBounds);
+ if (NSProcessInfo.processInfo.iOSAppOnMac) {
+ // iOS apps running as "Designed for iPad" on macOS do not match
+ // our current window management implementation where a single
+ // UIWindow is tied to a single screen. And even if we're on the
+ // right screen, the UIScreen does not account for the 77% scale
+ // of the UIUserInterfaceIdiomPad environment, so we can't use
+ // it to clamp the window geometry. Instead just use the UIWindow
+ // directly, which represents our "screen".
+ applyGeometry(uiWindowBounds);
+ } else if (isRunningOnVisionOS()) {
+ // On visionOS there is no concept of a screen, and hence no concept of
+ // screen-relative system UI that we should keep top level windows away
+ // from, so don't apply the UIWindow safe area insets to the screen.
+ applyGeometry(uiWindowBounds);
+ } else {
+ QRect fullscreenGeometry = screen()->geometry();
+ QRect maximizedGeometry = fullscreenGeometry;
+
+#if !defined(Q_OS_VISIONOS)
+ if (!(window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint)) {
+ // If the safe area margins reflect the screen's outer edges,
+ // then reduce the maximized geometry accordingly. Otherwise
+ // leave it as is, and assume the client will take the safe
+ // are margins into account explicitly.
+ UIScreen *uiScreen = m_view.window.windowScene.screen;
+ UIEdgeInsets safeAreaInsets = m_view.window.safeAreaInsets;
+ if (m_view.window.bounds.size.width == uiScreen.bounds.size.width)
+ maximizedGeometry.adjust(safeAreaInsets.left, 0, -safeAreaInsets.right, 0);
+ if (m_view.window.bounds.size.height == uiScreen.bounds.size.height)
+ maximizedGeometry.adjust(0, safeAreaInsets.top, 0, -safeAreaInsets.bottom);
+ }
+#endif
- if (state & Qt::WindowFullScreen)
- applyGeometry(fullscreenGeometry);
- else
- applyGeometry(maximizedGeometry);
+ if (state & Qt::WindowFullScreen)
+ applyGeometry(fullscreenGeometry.intersected(uiWindowBounds));
+ else
+ applyGeometry(maximizedGeometry.intersected(uiWindowBounds));
+ }
} else {
applyGeometry(m_normalGeometry);
}
@@ -281,21 +298,26 @@ void QIOSWindow::setWindowState(Qt::WindowStates state)
void QIOSWindow::setParent(const QPlatformWindow *parentWindow)
{
- UIView *parentView = parentWindow ? reinterpret_cast<UIView *>(parentWindow->winId())
- : isQtApplication() ? static_cast<QIOSScreen *>(screen())->uiWindow().rootViewController.view : 0;
-
- [parentView addSubview:m_view];
+ UIView *superview = nullptr;
+ if (parentWindow)
+ superview = reinterpret_cast<UIView *>(parentWindow->winId());
+ else if (isQtApplication() && !isForeignWindow())
+ superview = rootViewForScreen(window()->screen());
+
+ if (superview)
+ [superview addSubview:m_view];
+ else if (quiview_cast(m_view.superview))
+ [m_view removeFromSuperview];
}
void QIOSWindow::requestActivateWindow()
{
// Note that several windows can be active at the same time if they exist in the same
// hierarchy (transient children). But only one window can be QGuiApplication::focusWindow().
- // Dispite the name, 'requestActivateWindow' means raise and transfer focus to the window:
+ // Despite the name, 'requestActivateWindow' means raise and transfer focus to the window:
if (blockedByModal())
return;
- Q_ASSERT(m_view.window);
[m_view.window makeKeyWindow];
[m_view becomeFirstResponder];
@@ -305,8 +327,6 @@ void QIOSWindow::requestActivateWindow()
void QIOSWindow::raiseOrLower(bool raise)
{
- // Re-insert m_view at the correct index among its sibling views
- // (QWindows) according to their current m_windowLevel:
if (!isQtApplication())
return;
@@ -314,17 +334,27 @@ void QIOSWindow::raiseOrLower(bool raise)
if (subviews.count == 1)
return;
- for (int i = int(subviews.count) - 1; i >= 0; --i) {
- UIView *view = static_cast<UIView *>([subviews objectAtIndex:i]);
- if (view.hidden || view == m_view || !view.qwindow)
- continue;
- int level = static_cast<QIOSWindow *>(view.qwindow->handle())->m_windowLevel;
- if (m_windowLevel > level || (raise && m_windowLevel == level)) {
- [m_view.superview insertSubview:m_view aboveSubview:view];
- return;
+ if (m_view.superview == m_view.qtViewController.view) {
+ // We're a top level window, so we need to take window
+ // levels into account.
+ for (int i = int(subviews.count) - 1; i >= 0; --i) {
+ UIView *view = static_cast<UIView *>([subviews objectAtIndex:i]);
+ if (view.hidden || view == m_view || !view.qwindow)
+ continue;
+ int level = static_cast<QIOSWindow *>(view.qwindow->handle())->m_windowLevel;
+ if (m_windowLevel > level || (raise && m_windowLevel == level)) {
+ [m_view.superview insertSubview:m_view aboveSubview:view];
+ return;
+ }
}
+ [m_view.superview insertSubview:m_view atIndex:0];
+ } else {
+ // Child window, or embedded into a non-Qt view controller
+ if (raise)
+ [m_view.superview bringSubviewToFront:m_view];
+ else
+ [m_view.superview sendSubviewToBack:m_view];
}
- [m_view.superview insertSubview:m_view atIndex:0];
}
void QIOSWindow::updateWindowLevel()
@@ -353,20 +383,13 @@ void QIOSWindow::updateWindowLevel()
m_windowLevel = qMax(transientParentWindow->m_windowLevel, m_windowLevel);
}
-void QIOSWindow::handleContentOrientationChange(Qt::ScreenOrientation orientation)
-{
- // Update the QWindow representation straight away, so that
- // we can update the statusbar orientation based on the new
- // content orientation.
- qt_window_private(window())->contentOrientation = orientation;
-
- [m_view.qtViewController updateProperties];
-}
-
void QIOSWindow::applicationStateChanged(Qt::ApplicationState)
{
+ if (isForeignWindow())
+ return;
+
if (window()->isExposed() != isExposed())
- [m_view sendUpdatedExposeEvent];
+ [quiview_cast(m_view) sendUpdatedExposeEvent];
}
qreal QIOSWindow::devicePixelRatio() const
@@ -376,7 +399,10 @@ qreal QIOSWindow::devicePixelRatio() const
void QIOSWindow::clearAccessibleCache()
{
- [m_view clearAccessibleCache];
+ if (isForeignWindow())
+ return;
+
+ [quiview_cast(m_view) clearAccessibleCache];
}
void QIOSWindow::requestUpdate()
@@ -384,11 +410,27 @@ void QIOSWindow::requestUpdate()
static_cast<QIOSScreen *>(screen())->setUpdatesPaused(false);
}
+void QIOSWindow::setMask(const QRegion &region)
+{
+ if (!region.isEmpty()) {
+ QCFType<CGMutablePathRef> maskPath = CGPathCreateMutable();
+ for (const QRect &r : region)
+ CGPathAddRect(maskPath, nullptr, r.toCGRect());
+ CAShapeLayer *maskLayer = [CAShapeLayer layer];
+ maskLayer.path = maskPath;
+ m_view.layer.mask = maskLayer;
+ } else {
+ m_view.layer.mask = nil;
+ }
+}
+
+#if QT_CONFIG(opengl)
CAEAGLLayer *QIOSWindow::eaglLayer() const
{
Q_ASSERT([m_view.layer isKindOfClass:[CAEAGLLayer class]]);
return static_cast<CAEAGLLayer *>(m_view.layer);
}
+#endif
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug debug, const QIOSWindow *window)
@@ -403,6 +445,37 @@ QDebug operator<<(QDebug debug, const QIOSWindow *window)
}
#endif // !QT_NO_DEBUG_STREAM
-#include "moc_qioswindow.cpp"
+/*!
+ Returns the view cast to a QUIview if possible.
+
+ If the view is not a QUIview, nil is returned, which is safe to
+ send messages to, effectively making [quiview_cast(view) message]
+ a no-op.
+
+ For extra verbosity and clearer code, please consider checking
+ that the platform window is not a foreign window before using
+ this cast, via QPlatformWindow::isForeignWindow().
+
+ Do not use this method solely to check for foreign windows, as
+ that will make the code harder to read for people not working
+ primarily on iOS, who do not know the difference between the
+ UIView and QUIView cases.
+*/
+QUIView *quiview_cast(UIView *view)
+{
+ return qt_objc_cast<QUIView *>(view);
+}
+
+bool QIOSWindow::isForeignWindow() const
+{
+ return ![m_view isKindOfClass:QUIView.class];
+}
+
+UIView *QIOSWindow::view() const
+{
+ return m_view;
+}
QT_END_NAMESPACE
+
+#include "moc_qioswindow.cpp"