8#include <QtCore/qscopedvaluerollback.h>
9#include <QtCore/private/qcore_mac_p.h>
10#include <QtGui/private/qapplekeymapper_p.h>
12#include <QtGui/QGuiApplication>
13#include <QtGui/QWindow>
14#include <QtGui/QScreen>
16#include <QtGui/private/qwindow_p.h>
17#include <QtGui/private/qguiapplication_p.h>
25#include <QtCore/qpointer.h>
29@interface QIOSViewController ()
30@property (nonatomic, assign) UIWindow *window;
31@property (nonatomic, assign) QPointer<QT_PREPEND_NAMESPACE(QIOSScreen)> platformScreen;
32@property (nonatomic, assign) BOOL changingOrientation;
37@interface QIOSDesktopManagerView : UIView
40@implementation QIOSDesktopManagerView
44 if (!(self = [super init]))
47 if (qEnvironmentVariableIntValue(
"QT_IOS_DEBUG_WINDOW_MANAGEMENT")) {
48 static UIImage *gridPattern = nil;
49 static dispatch_once_t onceToken;
50 dispatch_once(&onceToken, ^{
51 CGFloat dimension = 100.f;
53 UIGraphicsBeginImageContextWithOptions(CGSizeMake(dimension, dimension), YES, 0.0f);
54 CGContextRef context = UIGraphicsGetCurrentContext();
56 CGContextTranslateCTM(context, -0.5, -0.5);
58 #define gridColorWithBrightness(br)
59 [UIColor colorWithHue:0.6
saturation:0.0
brightness:br alpha:1.0
].CGColor
62 CGContextFillRect(context, CGRectMake(0, 0, dimension, dimension));
64 CGFloat gridLines[][2] = { { 10, 0.1 }, { 20, 0.2 }, { 100, 0.3 } };
65 for (size_t l = 0; l <
sizeof(gridLines) /
sizeof(gridLines[0]); ++l) {
66 CGFloat step = gridLines[l][0];
67 for (
int c = step; c <= dimension; c += step) {
68 CGContextMoveToPoint(context, c, 0);
69 CGContextAddLineToPoint(context, c, dimension);
70 CGContextMoveToPoint(context, 0, c);
71 CGContextAddLineToPoint(context, dimension, c);
74 CGFloat brightness = gridLines[l][1];
76 CGContextStrokePath(context);
79 gridPattern = UIGraphicsGetImageFromCurrentImageContext();
80 UIGraphicsEndImageContext();
85 self.backgroundColor = [UIColor colorWithPatternImage:gridPattern];
91- (
void)didAddSubview:(UIView *)subview
98 UIWindow *uiWindow = self.qtViewController.window;
100 if (uiWindow.hidden) {
105 uiWindow.hidden = NO;
109#if !defined(Q_OS_VISIONOS)
110- (
void)willRemoveSubview:(UIView *)subview
114 UIWindow *uiWindow = self.window;
120 if (uiWindow.screen != [UIScreen mainScreen] && self.subviews.count == 1) {
124 dispatch_async(dispatch_get_main_queue(), ^{
125 uiWindow.hidden = YES;
131- (
void)layoutSubviews
133 if (QGuiApplication::applicationState() == Qt::ApplicationSuspended) {
147 QIOSScreen *screen = self.qtViewController.platformScreen;
148 qCDebug(lcQpaWindow) <<
"ignoring layout of subviews while suspended,"
149 <<
"likely system snapshot of" << screen->screen()->primaryOrientation();
153 for (
int i =
int(self.subviews.count) - 1; i >= 0; --i) {
154 UIView *view =
static_cast<UIView *>([self.subviews objectAtIndex:i]);
155 if (![view isKindOfClass:[QUIView
class]])
158 [self layoutView:
static_cast<QUIView *>(view)];
162- (
void)layoutView:(QUIView *)view
164 QWindow *window = view.qwindow;
168 if (!window->handle())
172 if (window->windowStates() & (Qt::WindowFullScreen | Qt::WindowMaximized))
173 window->handle()->setWindowState(window->windowStates());
186- (
void)setFrame:(CGRect)newFrame
189 Q_ASSERT(!self.window || self.window.rootViewController.view == self);
194 CGRect transformedWindowBounds = [self.superview convertRect:self.window.bounds fromView:self.window];
195 [super setFrame:transformedWindowBounds];
198- (
void)setBounds:(CGRect)newBounds
201 CGRect transformedWindowBounds = [self convertRect:self.window.bounds fromView:self.window];
202 [super setBounds:CGRectMake(0, 0, CGRectGetWidth(transformedWindowBounds), CGRectGetHeight(transformedWindowBounds))];
205- (
void)setCenter:(CGPoint)newCenter
208 [super setCenter:self.window.center];
211- (
void)didMoveToWindow
216 [self setFrame:self.window.bounds];
223@implementation QIOSViewController {
224 BOOL m_updatingProperties;
225 QMetaObject::Connection m_focusWindowChangeConnection;
226 QMetaObject::Connection m_appStateChangedConnection;
230@synthesize prefersStatusBarHidden;
231@synthesize preferredStatusBarUpdateAnimation;
232@synthesize preferredStatusBarStyle;
235- (instancetype)initWithWindow:(UIWindow*)window
237 if (self = [self init]) {
238 self.window = window;
239 self.platformScreen = nil;
240 [self updatePlatformScreen];
242 self.changingOrientation = NO;
245 self.prefersStatusBarHidden = infoPlistValue(@
"UIStatusBarHidden",
false);
246 self.preferredStatusBarUpdateAnimation = UIStatusBarAnimationNone;
247 self.preferredStatusBarStyle = UIStatusBarStyle(infoPlistValue(@
"UIStatusBarStyle", UIStatusBarStyleDefault));
250 m_focusWindowChangeConnection = QObject::connect(qApp, &QGuiApplication::focusWindowChanged, [self]() {
251 [self updateStatusBarProperties];
254 QIOSApplicationState *applicationState = &QIOSIntegration::instance()->applicationState;
255 m_appStateChangedConnection = QObject::connect(applicationState, &QIOSApplicationState::applicationStateDidChange,
256 [self](Qt::ApplicationState oldState, Qt::ApplicationState newState) {
257 if (oldState == Qt::ApplicationSuspended && newState != Qt::ApplicationSuspended) {
262 qCDebug(lcQpaWindow) <<
"triggering root VC layout when coming out of suspended state";
263 [self.view setNeedsLayout];
274 QObject::disconnect(m_focusWindowChangeConnection);
275 QObject::disconnect(m_appStateChangedConnection);
281 self.view = [[[QIOSDesktopManagerView alloc] init] autorelease];
288 Q_ASSERT(!qt_apple_isApplicationExtension());
290#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
291 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
292 [center addObserver:self selector:@selector(willChangeStatusBarFrame:)
293 name:UIApplicationWillChangeStatusBarFrameNotification
294 object:qt_apple_sharedApplication()];
296 [center addObserver:self selector:@selector(didChangeStatusBarOrientation:)
297 name:UIApplicationDidChangeStatusBarOrientationNotification
298 object:qt_apple_sharedApplication()];
303 for (
auto *window : qGuiApp->topLevelWindows()) {
304 if (window->screen()->handle() != self.platformScreen)
306 if (
auto *platformWindow = window->handle())
307 platformWindow->setParent(
nullptr);
313 [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:nil];
314 [super viewDidUnload];
319- (
void)updatePlatformScreen
321 auto *windowScene = self.window.windowScene;
323 QIOSScreen *newPlatformScreen = [&]{
324 for (
auto *screen : qGuiApp->screens()) {
325 auto *platformScreen =
static_cast<QIOSScreen*>(screen->handle());
326#if !defined(Q_OS_VISIONOS)
327 if (platformScreen->uiScreen() == windowScene.screen)
329 return platformScreen;
334 if (newPlatformScreen != self.platformScreen) {
335 QIOSScreen *oldPlatformScreen = self.platformScreen;
336 self.platformScreen = newPlatformScreen;
338 qCDebug(lcQpaWindow) <<
"View controller" << self <<
"moved from"
339 << oldPlatformScreen <<
"to" << newPlatformScreen;
341 QScreen *newScreen = newPlatformScreen ? newPlatformScreen->screen() :
nullptr;
343 const bool isPrimaryScene = !qt_apple_sharedApplication().supportsMultipleScenes
344 && windowScene.session.role == UIWindowSceneSessionRoleApplication;
346 if (isPrimaryScene) {
350 QWindowSystemInterface::handlePrimaryScreenChanged(newPlatformScreen);
353 for (
auto *window : qGuiApp->topLevelWindows()) {
357 if ((window->screen()->handle() == oldPlatformScreen)
358 || (isPrimaryScene && !oldPlatformScreen)) {
359 QWindowSystemInterface::handleWindowScreenChanged(window, newScreen);
367- (
void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
369 self.changingOrientation = YES;
371 [super willRotateToInterfaceOrientation:orientation duration:duration];
374- (
void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orientation
376 self.changingOrientation = NO;
378 [super didRotateFromInterfaceOrientation:orientation];
381#if !defined(Q_OS_VISIONOS)
382- (
void)willChangeStatusBarFrame:(NSNotification*)notification
384 Q_UNUSED(notification);
386 if (self.view.window.screen != [UIScreen mainScreen])
394 if (self.changingOrientation)
402 static qreal kUIStatusBarAnimationDuration = 0.35;
404 [UIView animateWithDuration:kUIStatusBarAnimationDuration animations:^{
405 [self.view setNeedsLayout];
406 [self.view layoutIfNeeded];
410- (
void)didChangeStatusBarOrientation:(NSNotification *)notification
412 Q_UNUSED(notification);
414 if (self.view.window.screen != [UIScreen mainScreen])
420 if (!self.changingOrientation)
421 [self.view setNeedsLayout];
424 if (self.platformScreen)
425 self.platformScreen->updateProperties();
429- (
void)viewWillLayoutSubviews
431 if (!QCoreApplication::instance())
437 if (self.platformScreen)
438 self.platformScreen->updateProperties();
443- (
void)updateStatusBarProperties
445 if (!isQtApplication())
448 if (!self.platformScreen || !self.platformScreen->screen())
451#if !defined(Q_OS_VISIONOS)
454 if (self.platformScreen->uiScreen() != [UIScreen mainScreen])
462 if (m_updatingProperties)
465 QScopedValueRollback<BOOL> updateRollback(m_updatingProperties, YES);
467 QWindow *focusWindow = QGuiApplication::focusWindow();
477 if (!focusWindow->screen() || focusWindow->screen()->handle() != self.platformScreen)
481 focusWindow = qt_window_private(focusWindow)->topLevelWindow();
483#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
487 UIStatusBarStyle oldStatusBarStyle = self.preferredStatusBarStyle;
488 if (focusWindow->flags() & Qt::ExpandedClientAreaHint)
489 self.preferredStatusBarStyle = UIStatusBarStyleDefault;
491 self.preferredStatusBarStyle = UIStatusBarStyleLightContent;
493 if (self.preferredStatusBarStyle != oldStatusBarStyle)
494 [self setNeedsStatusBarAppearanceUpdate];
496 bool currentStatusBarVisibility = self.prefersStatusBarHidden;
497 self.prefersStatusBarHidden = focusWindow->windowState() == Qt::WindowFullScreen;
499 if (self.prefersStatusBarHidden != currentStatusBarVisibility) {
500 [self setNeedsStatusBarAppearanceUpdate];
501 [self.view setNeedsLayout];
506- (NSArray*)keyCommands
511 NSMutableArray<UIKeyCommand *> *keyCommands = nil;
512 QShortcutMap &shortcutMap = QGuiApplicationPrivate::instance()->shortcutMap;
513 keyCommands = [[NSMutableArray<UIKeyCommand *> alloc] init];
514 const QList<QKeySequence> keys = shortcutMap.keySequences();
515 for (
const QKeySequence &seq : keys) {
516 const QString keyString = seq.toString();
517 [keyCommands addObject:[UIKeyCommand
518 keyCommandWithInput:QString(keyString[keyString.length() - 1]).toNSString()
519 modifierFlags:QAppleKeyMapper::toUIKitModifiers(seq[0].keyboardModifiers())
520 action:@selector(handleShortcut:)]];
525- (
void)handleShortcut:(UIKeyCommand *)keyCommand
527 const QString str = QString::fromNSString([keyCommand input]);
528 Qt::KeyboardModifiers qtMods = QAppleKeyMapper::fromUIKitModifiers(keyCommand.modifierFlags);
529 QChar ch = str.isEmpty() ? QChar() : str.at(0);
530 QShortcutMap &shortcutMap = QGuiApplicationPrivate::instance()->shortcutMap;
531 QKeyEvent keyEvent(QEvent::ShortcutOverride, Qt::Key(ch.toUpper().unicode()), qtMods, str);
532 shortcutMap.tryShortcut(&keyEvent);
#define gridColorWithBrightness(br)