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);
365 for (
auto *window : qGuiApp->allWindows()) {
366 if (window->screen() != newScreen)
368 auto *platformWindow = window->handle();
371 if (!platformWindow->hasPendingUpdateRequest())
374 qCDebug(lcQpaWindow) <<
"Starting display link for" << newScreen
375 <<
"because" << window <<
"needs update";
376 newPlatformScreen->setUpdatesPaused(
false);
384- (
void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
386 self.changingOrientation = YES;
388 [super willRotateToInterfaceOrientation:orientation duration:duration];
391- (
void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orientation
393 self.changingOrientation = NO;
395 [super didRotateFromInterfaceOrientation:orientation];
398#if !defined(Q_OS_VISIONOS)
399- (
void)willChangeStatusBarFrame:(NSNotification*)notification
401 Q_UNUSED(notification);
403 if (self.view.window.screen != [UIScreen mainScreen])
411 if (self.changingOrientation)
419 static qreal kUIStatusBarAnimationDuration = 0.35;
421 [UIView animateWithDuration:kUIStatusBarAnimationDuration animations:^{
422 [self.view setNeedsLayout];
423 [self.view layoutIfNeeded];
427- (
void)didChangeStatusBarOrientation:(NSNotification *)notification
429 Q_UNUSED(notification);
431 if (self.view.window.screen != [UIScreen mainScreen])
437 if (!self.changingOrientation)
438 [self.view setNeedsLayout];
441 if (self.platformScreen)
442 self.platformScreen->updateProperties();
446- (
void)viewWillLayoutSubviews
448 if (!QCoreApplication::instance())
454 if (self.platformScreen)
455 self.platformScreen->updateProperties();
460- (
void)updateStatusBarProperties
462 if (!isQtApplication())
465 if (!self.platformScreen || !self.platformScreen->screen())
468#if !defined(Q_OS_VISIONOS)
471 if (self.platformScreen->uiScreen() != [UIScreen mainScreen])
479 if (m_updatingProperties)
482 QScopedValueRollback<BOOL> updateRollback(m_updatingProperties, YES);
484 QWindow *focusWindow = QGuiApplication::focusWindow();
494 if (!focusWindow->screen() || focusWindow->screen()->handle() != self.platformScreen)
498 focusWindow = qt_window_private(focusWindow)->topLevelWindow();
500#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
504 UIStatusBarStyle oldStatusBarStyle = self.preferredStatusBarStyle;
505 if (focusWindow->flags() & Qt::ExpandedClientAreaHint)
506 self.preferredStatusBarStyle = UIStatusBarStyleDefault;
508 self.preferredStatusBarStyle = UIStatusBarStyleLightContent;
510 if (self.preferredStatusBarStyle != oldStatusBarStyle)
511 [self setNeedsStatusBarAppearanceUpdate];
513 bool currentStatusBarVisibility = self.prefersStatusBarHidden;
514 self.prefersStatusBarHidden = focusWindow->windowState() == Qt::WindowFullScreen;
516 if (self.prefersStatusBarHidden != currentStatusBarVisibility) {
517 [self setNeedsStatusBarAppearanceUpdate];
518 [self.view setNeedsLayout];
523- (NSArray*)keyCommands
528 NSMutableArray<UIKeyCommand *> *keyCommands = nil;
529 QShortcutMap &shortcutMap = QGuiApplicationPrivate::instance()->shortcutMap;
530 keyCommands = [[NSMutableArray<UIKeyCommand *> alloc] init];
531 const QList<QKeySequence> keys = shortcutMap.keySequences();
532 for (
const QKeySequence &seq : keys) {
533 const QString keyString = seq.toString();
534 [keyCommands addObject:[UIKeyCommand
535 keyCommandWithInput:QString(keyString[keyString.length() - 1]).toNSString()
536 modifierFlags:QAppleKeyMapper::toUIKitModifiers(seq[0].keyboardModifiers())
537 action:@selector(handleShortcut:)]];
542- (
void)handleShortcut:(UIKeyCommand *)keyCommand
544 const QString str = QString::fromNSString([keyCommand input]);
545 Qt::KeyboardModifiers qtMods = QAppleKeyMapper::fromUIKitModifiers(keyCommand.modifierFlags);
546 QChar ch = str.isEmpty() ? QChar() : str.at(0);
547 QShortcutMap &shortcutMap = QGuiApplicationPrivate::instance()->shortcutMap;
548 QKeyEvent keyEvent(QEvent::ShortcutOverride, Qt::Key(ch.toUpper().unicode()), qtMods, str);
549 shortcutMap.tryShortcut(&keyEvent);
#define gridColorWithBrightness(br)