iOS: Handle geometry of maximized windows outside of QIOSScreen

Ideally we would report the available geometry of our screen in a way
that reflects the safe area margins of a potentially full screen window,
but doing so requires that we manage UIWindows via QScreen/UIScreen,
which is not in line with modern iOS window management.

We still apply safe area margins to maximized windows, as before,
but do so when applying the maximized state in QIOSWindow::setWindowState

Task-number: QTBUG-121781
Change-Id: If7b9855aea47014f01e23dfe197b7057a1305a68
Reviewed-by: Amr Elsayed <amr.elsayed@qt.io>
Reviewed-by: Doris Verria <doris.verria@qt.io>
This commit is contained in:
Tor Arne Vestbø 2024-04-05 18:05:31 +02:00
parent eb9923c67e
commit 1a0c113b2d
4 changed files with 40 additions and 37 deletions

View File

@ -26,6 +26,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow);
class QPlatformScreen;
bool isQtApplication();
bool isRunningOnVisionOS();
#ifndef Q_OS_TVOS
Qt::ScreenOrientation toQtScreenOrientation(UIDeviceOrientation uiDeviceOrientation);

View File

@ -29,6 +29,15 @@ bool isQtApplication()
return isQt;
}
bool isRunningOnVisionOS()
{
static bool result = []{
// This class is documented to only be available on visionOS
return NSClassFromString(@"UIWindowSceneGeometryPreferencesVision");
}();
return result;
}
#ifndef Q_OS_TVOS
Qt::ScreenOrientation toQtScreenOrientation(UIDeviceOrientation uiDeviceOrientation)
{

View File

@ -310,15 +310,6 @@ QString QIOSScreen::name() const
#endif
}
[[maybe_unused]] static bool isRunningOnVisionOS()
{
static bool result = []{
// This class is documented to only be available on visionOS
return NSClassFromString(@"UIWindowSceneGeometryPreferencesVision");
}();
return result;
}
void QIOSScreen::updateProperties()
{
QRect previousGeometry = m_geometry;
@ -327,26 +318,9 @@ void QIOSScreen::updateProperties()
#if defined(Q_OS_VISIONOS)
// Based on what iPad app reports
m_geometry = QRect(0, 0, 1194, 834);
m_availableGeometry = m_geometry;
m_depth = 24;
#else
m_geometry = QRectF::fromCGRect(m_uiScreen.bounds).toRect();
m_availableGeometry = m_geometry;
// For convenience, we reflect the safe area margins of the screen's UIWindow
// by reducing the available geometry of the screen. But we only do this if
// the UIWindow bounds is representative of the UIScreen.
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.
} else {
UIEdgeInsets safeAreaInsets = m_uiWindow.safeAreaInsets;
if (m_uiWindow.bounds.size.width == m_uiScreen.bounds.size.width)
m_availableGeometry.adjust(safeAreaInsets.left, 0, -safeAreaInsets.right, 0);
if (m_uiWindow.bounds.size.height == m_uiScreen.bounds.size.height)
m_availableGeometry.adjust(0, safeAreaInsets.top, 0, -safeAreaInsets.bottom);
}
#ifndef Q_OS_TVOS
if (m_uiScreen == [UIScreen mainScreen]) {
@ -390,6 +364,12 @@ void QIOSScreen::updateProperties()
#endif // defined(Q_OS_VISIONOS)
// UIScreen does not provide a consistent accessor for the safe area margins
// of the screen, and on visionOS we won't even have a UIScreen, so we report
// the available geometry of the screen to be the same as the full geometry.
// Safe area margins and maximized state is handled in QIOSWindow::setWindowState.
m_availableGeometry = m_geometry;
// At construction time, we don't yet have an associated QScreen, but we still want
// to compute the properties above so they are ready for when the QScreen attaches.
// Also, at destruction time the QScreen has already been torn down, so notifying

View File

@ -261,21 +261,34 @@ void QIOSWindow::setWindowState(Qt::WindowStates state)
// 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 {
// 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 fullscreenGeometry = screen()->geometry().intersected(uiWindowBounds);
QRect maximizedGeometry = window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint ?
fullscreenGeometry : screen()->availableGeometry().intersected(uiWindowBounds);
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);
applyGeometry(fullscreenGeometry.intersected(uiWindowBounds));
else
applyGeometry(maximizedGeometry);
applyGeometry(maximizedGeometry.intersected(uiWindowBounds));
}
} else {
applyGeometry(m_normalGeometry);