| 1 | // Copyright (C) 2023 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include "qquickwindowcontainer_p.h" |
| 5 | |
| 6 | #include <QtQuick/qquickrendercontrol.h> |
| 7 | |
| 8 | #include <QtQuick/private/qquickitem_p.h> |
| 9 | #include <QtQuick/private/qquickrectangle_p.h> |
| 10 | #include <QtQuick/private/qquickwindowmodule_p.h> |
| 11 | #include <QtQuick/private/qquickimplicitsizeitem_p_p.h> |
| 12 | |
| 13 | QT_BEGIN_NAMESPACE |
| 14 | |
| 15 | Q_LOGGING_CATEGORY(lcWindowContainer, "qt.quick.window.container" ) |
| 16 | |
| 17 | using namespace Qt::StringLiterals; |
| 18 | |
| 19 | /*! |
| 20 | \qmltype WindowContainer |
| 21 | \inqmlmodule QtQuick |
| 22 | \ingroup qtquick-visual |
| 23 | \inherits Item |
| 24 | \since 6.8 |
| 25 | |
| 26 | \brief Allows embedding arbitrary QWindows into a Qt Quick scene. |
| 27 | |
| 28 | The window will become a child of the item's window, |
| 29 | with its position, size, z-order, etc. managed by the item. |
| 30 | |
| 31 | Sibling items with a higher z-order than the window container |
| 32 | will not automatically overlap the embedded window, as the |
| 33 | window lives on top of the Qt Quick scene. To work around this, |
| 34 | place the sibling items inside their own dedicated child window: |
| 35 | |
| 36 | \code |
| 37 | Item { |
| 38 | id: someItem |
| 39 | WindowContainer { |
| 40 | window: foreignWindow |
| 41 | } |
| 42 | WindowContainer { |
| 43 | window: Window { |
| 44 | Item { |
| 45 | id: siblingItem |
| 46 | } |
| 47 | } |
| 48 | } |
| 49 | } |
| 50 | \endcode |
| 51 | |
| 52 | Similarly, child Items of the window container will not automatically |
| 53 | overlap the embedded window. To work around this, place the child |
| 54 | item inside a dedicated child window. |
| 55 | |
| 56 | \code |
| 57 | Item { |
| 58 | id: someItem |
| 59 | WindowContainer { |
| 60 | id: windowContainer |
| 61 | window: foreignWindow |
| 62 | WindowContainer { |
| 63 | window: Window { |
| 64 | Item { |
| 65 | id: childItem |
| 66 | } |
| 67 | } |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | \endcode |
| 72 | |
| 73 | \note The window container does not interoperate with QQuickWidget, |
| 74 | QQuickWindow::setRenderTarget(), QQuickRenderControl, or similar |
| 75 | functionality. |
| 76 | |
| 77 | \sa {QQuickWindow::}{parent()} |
| 78 | */ |
| 79 | |
| 80 | /*! |
| 81 | \qmlproperty QWindow QtQuick::WindowContainer::window |
| 82 | |
| 83 | This property holds the window to embed. |
| 84 | */ |
| 85 | |
| 86 | class QQuickWindowContainerPrivate : public QQuickImplicitSizeItemPrivate |
| 87 | { |
| 88 | Q_DECLARE_PUBLIC(QQuickWindowContainer) |
| 89 | protected: |
| 90 | bool transformChanged(QQuickItem *transformedItem) override; |
| 91 | |
| 92 | public: |
| 93 | QWindow *window = nullptr; |
| 94 | QQuickWindowContainer::ContainerMode containerMode; |
| 95 | }; |
| 96 | |
| 97 | /*! |
| 98 | \internal |
| 99 | |
| 100 | Creates a new window container. |
| 101 | |
| 102 | The container mode determines who has the last word in what the state |
| 103 | of the contained window should be. If the window container is explicitly |
| 104 | requested by the user via WindowContainer, the properties are set on the |
| 105 | item, and the embedded window should match that. If the window container |
| 106 | is implicitly created by setting a visual parent on a Window, the properties |
| 107 | are set on the Window, and the window container should respect that. |
| 108 | */ |
| 109 | QQuickWindowContainer::QQuickWindowContainer(QQuickItem *parent, ContainerMode containerMode) |
| 110 | : QQuickImplicitSizeItem(*(new QQuickWindowContainerPrivate), parent) |
| 111 | { |
| 112 | Q_D(QQuickWindowContainer); |
| 113 | |
| 114 | qCDebug(lcWindowContainer).verbosity(verbosityLevel: 1) << "Creating window container" |
| 115 | << this << "with parent" << parent << "and" << containerMode; |
| 116 | |
| 117 | d->containerMode = containerMode; |
| 118 | |
| 119 | setFlag(flag: QQuickItem::ItemObservesViewport); // For clipping |
| 120 | |
| 121 | connect(sender: this, signal: &QQuickItem::windowChanged, |
| 122 | context: this, slot: &QQuickWindowContainer::parentWindowChanged); |
| 123 | |
| 124 | if (lcWindowContainer().isDebugEnabled()) { |
| 125 | auto *debugRectangle = new QQuickRectangle(this); |
| 126 | debugRectangle->setColor(QColor(255, 0, 255, 20)); |
| 127 | auto *border = debugRectangle->border(); |
| 128 | border->setColor(Qt::magenta); |
| 129 | border->setWidth(1.0); |
| 130 | QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(item: debugRectangle); |
| 131 | rectPrivate->anchors()->setFill(this); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | QQuickWindowContainer::~QQuickWindowContainer() |
| 136 | { |
| 137 | Q_D(const QQuickWindowContainer); |
| 138 | qCDebug(lcWindowContainer) << "Destructing window container" << this; |
| 139 | |
| 140 | disconnect(receiver: this); |
| 141 | if (d->window) { |
| 142 | auto ownership = QJSEngine::objectOwnership(d->window); |
| 143 | qCDebug(lcWindowContainer) << "Contained window" << d->window |
| 144 | << "has" << (ownership == QQmlEngine::JavaScriptOwnership ? |
| 145 | "JavaScript" : "C++" ) << "ownership" ; |
| 146 | if (ownership == QQmlEngine::JavaScriptOwnership) { |
| 147 | delete d->window; |
| 148 | } else { |
| 149 | d->window->destroy(); |
| 150 | d->window->setParent(nullptr); |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | void QQuickWindowContainer::releaseResources() |
| 156 | { |
| 157 | Q_D(const QQuickWindowContainer); |
| 158 | qCDebug(lcWindowContainer) << "Destroying" << d->window |
| 159 | << "with platform window" << (d->window ? d->window->handle() : nullptr); |
| 160 | if (d->window) |
| 161 | d->window->destroy(); |
| 162 | } |
| 163 | |
| 164 | void QQuickWindowContainer::classBegin() |
| 165 | { |
| 166 | qCDebug(lcWindowContainer) << "Class begin for" << this; |
| 167 | |
| 168 | QQuickImplicitSizeItem::classBegin(); |
| 169 | } |
| 170 | |
| 171 | void QQuickWindowContainer::componentComplete() |
| 172 | { |
| 173 | Q_D(const QQuickWindowContainer); |
| 174 | |
| 175 | qCDebug(lcWindowContainer) << "Component completed for" << this; |
| 176 | QQuickImplicitSizeItem::componentComplete(); |
| 177 | |
| 178 | if (d->window) |
| 179 | initializeContainedWindow(); |
| 180 | } |
| 181 | |
| 182 | QWindow *QQuickWindowContainer::containedWindow() const |
| 183 | { |
| 184 | Q_D(const QQuickWindowContainer); |
| 185 | return d->window; |
| 186 | } |
| 187 | |
| 188 | void QQuickWindowContainer::setContainedWindow(QWindow *window) |
| 189 | { |
| 190 | qCDebug(lcWindowContainer) << "Setting contained window for" << this << "to" << window; |
| 191 | |
| 192 | Q_D(QQuickWindowContainer); |
| 193 | |
| 194 | if (window == d->window) |
| 195 | return; |
| 196 | |
| 197 | if (auto *previousWindow = d->window) { |
| 198 | qCDebug(lcWindowContainer) << "Decoupling container from" << d->window; |
| 199 | previousWindow->disconnect(receiver: this); |
| 200 | previousWindow->removeEventFilter(obj: this); |
| 201 | previousWindow->setParent(nullptr); |
| 202 | } |
| 203 | |
| 204 | d->window = window; |
| 205 | |
| 206 | if (d->window) { |
| 207 | if (d->containerMode == ItemControlsWindow) { |
| 208 | if (auto *quickWindow = qobject_cast<QQuickWindowQmlImpl*>(object: d->window)) { |
| 209 | // Make sure the Window reflects the window container as its visual parent |
| 210 | quickWindow->setVisualParent(this); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | // When the window controls the container, we need to reflect any changes |
| 215 | // in the window back to the container, so they stay in sync. And when the |
| 216 | // container controls the window, we still want to reflect width/height as |
| 217 | // new implicit size, and override any other changes with the item state. |
| 218 | connect(sender: d->window, signal: &QWindow::xChanged, context: this, slot: &QQuickWindowContainer::windowUpdated); |
| 219 | connect(sender: d->window, signal: &QWindow::yChanged, context: this, slot: &QQuickWindowContainer::windowUpdated); |
| 220 | connect(sender: d->window, signal: &QWindow::widthChanged, context: this, slot: &QQuickWindowContainer::windowUpdated); |
| 221 | connect(sender: d->window, signal: &QWindow::heightChanged, context: this, slot: &QQuickWindowContainer::windowUpdated); |
| 222 | connect(sender: d->window, signal: &QWindow::visibleChanged, context: this, slot: &QQuickWindowContainer::windowUpdated); |
| 223 | |
| 224 | connect(sender: d->window, signal: &QObject::destroyed, context: this, slot: &QQuickWindowContainer::windowDestroyed); |
| 225 | |
| 226 | d->window->installEventFilter(filterObj: this); |
| 227 | |
| 228 | if (d->componentComplete) |
| 229 | initializeContainedWindow(); |
| 230 | } else { |
| 231 | // Reset state based on not having a window |
| 232 | syncWindowToItem(); |
| 233 | } |
| 234 | |
| 235 | emit containedWindowChanged(window: d->window); |
| 236 | } |
| 237 | |
| 238 | void QQuickWindowContainer::initializeContainedWindow() |
| 239 | { |
| 240 | Q_D(const QQuickWindowContainer); |
| 241 | Q_ASSERT(d->componentComplete); |
| 242 | Q_ASSERT(d->window); |
| 243 | |
| 244 | qCDebug(lcWindowContainer) << "Doing initial sync between" << d->window << "and" << this; |
| 245 | |
| 246 | syncWindowToItem(); |
| 247 | polish(); |
| 248 | } |
| 249 | |
| 250 | static QTransform sanitizeTransform(const QTransform &transform) |
| 251 | { |
| 252 | if (transform.isRotating()) { |
| 253 | // FIXME: Can we keep more here? |
| 254 | return QTransform::fromTranslate(dx: transform.dx(), dy: transform.dy()); |
| 255 | } |
| 256 | |
| 257 | return transform; |
| 258 | } |
| 259 | |
| 260 | void QQuickWindowContainer::syncWindowToItem() |
| 261 | { |
| 262 | Q_D(const QQuickWindowContainer); |
| 263 | |
| 264 | const auto windowGeometry = d->window ? d->window->geometry() : QRect(); |
| 265 | |
| 266 | qCDebug(lcWindowContainer) << "Syncing window state from" << d->window |
| 267 | << "with geometry" << windowGeometry << "to" << this |
| 268 | << "with mode" << d->containerMode; |
| 269 | |
| 270 | const auto transform = sanitizeTransform(transform: d->windowToItemTransform()); |
| 271 | |
| 272 | // The window might have a larger size than the item's natural |
| 273 | // size, if there's a scale applied somewhere in the hierarchy. |
| 274 | auto itemSize = d->window ? transform.mapRect(windowGeometry).size() |
| 275 | : QSize(); |
| 276 | |
| 277 | if (d->containerMode == WindowControlsItem) { |
| 278 | // When the Window controls the window container the position is |
| 279 | // set up front, when creating the window container, and from that |
| 280 | // point on set exclusively via the window container, so we skip |
| 281 | // setting the position here, and only set the size. |
| 282 | setSize(itemSize); |
| 283 | setVisible(d->window ? d->window->isVisible() : false); |
| 284 | } else { |
| 285 | // Position defined by item, so don't sync from window |
| 286 | // Visible defined by item, so don't sync from window |
| 287 | setImplicitWidth(itemSize.width()); |
| 288 | setImplicitHeight(itemSize.height()); |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | /*! |
| 293 | \internal |
| 294 | |
| 295 | updatePolish() should perform any layout as required for this item. |
| 296 | |
| 297 | For us, that means propagating the item's state to the window. |
| 298 | */ |
| 299 | void QQuickWindowContainer::updatePolish() |
| 300 | { |
| 301 | Q_D(QQuickWindowContainer); |
| 302 | |
| 303 | qCDebug(lcWindowContainer) << "Propagating" << this << "state" |
| 304 | << "to" << d->window; |
| 305 | |
| 306 | auto *parentWindow = window(); |
| 307 | |
| 308 | // FIXME: If we are part of a QQuickWidget, we have a QQuickRenderControl, |
| 309 | // and should look up the parent window via that, and apply the offset we |
| 310 | // get to the item transform below. But at the moment it's not possible |
| 311 | // to observe changes to the offset, which is critical to support this |
| 312 | // for child windows. |
| 313 | |
| 314 | if (!d->window || !parentWindow) |
| 315 | return; |
| 316 | |
| 317 | if (d->window->parent() != parentWindow) { |
| 318 | qCDebug(lcWindowContainer) << "Updating window parent to" << parentWindow; |
| 319 | d->window->setParent(parentWindow); |
| 320 | } |
| 321 | |
| 322 | auto transform = sanitizeTransform(transform: d->itemToWindowTransform()); |
| 323 | |
| 324 | // Find the window's geometry, based on the item's bounding rect, |
| 325 | // mapped to the scene. The mapping includes any x/y position set |
| 326 | // on the item itself, as well as any transforms applied to the item |
| 327 | // or its ancestor (scale, translation). |
| 328 | const QRectF itemSceneRect = transform.mapRect(boundingRect()); |
| 329 | // FIXME: Rounding to a QRect here means we'll have some jitter or off |
| 330 | // placement when the underlying item is not on a integer coordinate. |
| 331 | QRect windowGeometry = itemSceneRect.toRect(); |
| 332 | if (windowGeometry != d->window->geometry()) { |
| 333 | QRectF itemRect(position(), size()); |
| 334 | qCDebug(lcWindowContainer) << "Updating window geometry to" << windowGeometry |
| 335 | << "based on item rect" << itemRect << "and scene rect" << itemSceneRect; |
| 336 | d->window->setGeometry(windowGeometry); |
| 337 | } |
| 338 | |
| 339 | // Clip the container to its own and ancestor clip rects, by setting |
| 340 | // a mask on the window. This does not necessarily clip native windows, |
| 341 | // as QWindow::setMask() is not guaranteed to visually clip the window, |
| 342 | // only to mask input, but in most cases we should be good. For the |
| 343 | // cases where this fails, we can potentially use an intermediate window |
| 344 | // as parent of the contained window, if the platform allows clipping |
| 345 | // child windows to parent window geometry. We do not want to resize the |
| 346 | // contained window, as that will just fill the content into a smaller |
| 347 | // area. |
| 348 | const auto clipMask = [&]{ |
| 349 | if (clipRect() == boundingRect()) |
| 350 | return QRect(); |
| 351 | |
| 352 | // The clip rect has all the relevant transforms applied to it, |
| 353 | // except for the item's own scale. As the mask is in window |
| 354 | // local coordinates in the possibly scaled window, we need |
| 355 | // to apply the scale manually. |
| 356 | auto scaleTransform = QTransform::fromScale(dx: transform.m11(), dy: transform.m22()); |
| 357 | auto rect = scaleTransform.mapRect(clipRect()).toRect(); |
| 358 | |
| 359 | // An empty clip rect means clip away everything, while for a |
| 360 | // window, an empty mask means mask nothing. Fake the former |
| 361 | // by setting a mask outside of the window's bounds. We have |
| 362 | // to do this check after rounding the clip rect to a QRect. |
| 363 | // FIXME: Verify this works on all platforms |
| 364 | if (rect.isEmpty()) |
| 365 | return QRect(-1, -1, 1, 1); |
| 366 | |
| 367 | return rect; |
| 368 | }(); |
| 369 | |
| 370 | if (clipMask != d->window->mask().boundingRect()) { |
| 371 | qCDebug(lcWindowContainer) << "Updating window clip mask to" << clipMask |
| 372 | << "based on clip rect" << clipRect(); |
| 373 | d->window->setMask(clipMask); |
| 374 | } |
| 375 | |
| 376 | // FIXME: Opacity support. Need to calculate effective opacity ourselves, |
| 377 | // and there doesn't seem to be any existing observer for opacity changes. |
| 378 | // Not all platforms implement opacity for child windows yet. |
| 379 | |
| 380 | // FIXME: If a scale is applied to the item or its parents, we end up |
| 381 | // with a bigger item, and window, but we don't translate the scale to |
| 382 | // an increase device-pixel-ratio of the window. As a result, the window |
| 383 | // will likely just render more content, instead of the same content at |
| 384 | // a potentially higher density. |
| 385 | |
| 386 | if (d->window->isVisible() != isVisible()) { |
| 387 | qCDebug(lcWindowContainer) << "Updating window visibility" |
| 388 | << "based on item visible" << isVisible(); |
| 389 | d->window->setVisible(isVisible()); |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | /*! |
| 394 | \internal |
| 395 | |
| 396 | QQuickItem::clipRect() doesn't take ItemClipsChildrenToShape into |
| 397 | account, so a parent item that has clip:false, but ItemIsViewport |
| 398 | will still result in affecting the clip. |
| 399 | |
| 400 | We want to stay consistent with the clipping in the scene graph, |
| 401 | which is based on QQuickItem::clip(), so we override the clipRect |
| 402 | to take ItemClipsChildrenToShape into account. |
| 403 | */ |
| 404 | QRectF QQuickWindowContainer::clipRect() const |
| 405 | { |
| 406 | QRectF rect = boundingRect(); |
| 407 | |
| 408 | for (auto *viewport = viewportItem(); viewport; viewport = viewport->viewportItem()) { |
| 409 | if (viewport == this) |
| 410 | break; |
| 411 | |
| 412 | if (viewport->flags().testFlag(flag: QQuickItem::ItemClipsChildrenToShape)) { |
| 413 | // FIXME: This fails to take into account viewports that override clipRect() |
| 414 | const auto mappedViewportRect = mapRectFromItem(item: viewport, rect: viewport->boundingRect()); |
| 415 | rect = mappedViewportRect.intersected(r: rect); |
| 416 | } |
| 417 | |
| 418 | if (viewport->viewportItem() == viewport) |
| 419 | break; // Content item returns itself as viewport |
| 420 | } |
| 421 | |
| 422 | return rect; |
| 423 | } |
| 424 | |
| 425 | // ----------------------- Window updates ----------------------- |
| 426 | |
| 427 | /*! |
| 428 | \internal |
| 429 | |
| 430 | Called when the contained QWindow is changed. |
| 431 | |
| 432 | Depending on the sync mode we need to reflect these changes |
| 433 | to the item, or override them by applying the item state. |
| 434 | */ |
| 435 | void QQuickWindowContainer::windowUpdated() |
| 436 | { |
| 437 | Q_D(const QQuickWindowContainer); |
| 438 | |
| 439 | if (lcWindowContainer().isDebugEnabled()) { |
| 440 | auto metaMethod = sender()->metaObject()->method(index: senderSignalIndex()); |
| 441 | auto signalName = QString::fromUtf8(ba: metaMethod.name()); |
| 442 | qCDebug(lcWindowContainer).noquote() << d->window << signalName; |
| 443 | } |
| 444 | |
| 445 | syncWindowToItem(); |
| 446 | |
| 447 | if (d->containerMode == ItemControlsWindow) { |
| 448 | qCDebug(lcWindowContainer) << "Overriding window state by polishing" ; |
| 449 | // Ideally we'd always call ensurePolished() here, to synchronously |
| 450 | // override the window state ASAP, rather than wait for polish to |
| 451 | // trigger it asynchronously, but due to QWindowPrivate::setVisible |
| 452 | // emitting visibleChanged before updating the platform window, we |
| 453 | // end up applying our override temporarily, only to have QWindowPrivate |
| 454 | // follow up with the original change to the platform window. |
| 455 | if (d->window->isVisible() != isVisible()) |
| 456 | polish(); |
| 457 | else |
| 458 | ensurePolished(); |
| 459 | } |
| 460 | } |
| 461 | |
| 462 | bool QQuickWindowContainer::eventFilter(QObject *object, QEvent *event) |
| 463 | { |
| 464 | Q_D(const QQuickWindowContainer); |
| 465 | Q_ASSERT(object == d->window); |
| 466 | |
| 467 | if (event->type() == QEvent::PlatformSurface) { |
| 468 | auto type = static_cast<QPlatformSurfaceEvent*>(event)->surfaceEventType(); |
| 469 | if (type == QPlatformSurfaceEvent::SurfaceCreated) { |
| 470 | qCDebug(lcWindowContainer) << "Surface created for" << object; |
| 471 | syncWindowToItem(); |
| 472 | // The surface creation has already resulted in the native window |
| 473 | // being added to its parent, on top of all other windows. We need |
| 474 | // to do a synchronous re-stacking of the windows here, to avoid |
| 475 | // leaving the window in the wrong position while waiting for the |
| 476 | // asynchronous callback to QQuickWindow::polishItems(). |
| 477 | if (auto *quickWindow = qobject_cast<QQuickWindow*>(object: window())) |
| 478 | QQuickWindowPrivate::get(c: quickWindow)->updateChildWindowStackingOrder(); |
| 479 | } |
| 480 | } |
| 481 | |
| 482 | return QQuickImplicitSizeItem::eventFilter(watched: object, event); |
| 483 | } |
| 484 | |
| 485 | void QQuickWindowContainer::windowDestroyed() |
| 486 | { |
| 487 | Q_D(QQuickWindowContainer); |
| 488 | qCDebug(lcWindowContainer) << "Window" << (void*)d->window << "destroyed" ; |
| 489 | |
| 490 | d->window->removeEventFilter(obj: this); |
| 491 | d->window = nullptr; |
| 492 | |
| 493 | syncWindowToItem(); // Reset state based on not having a window |
| 494 | emit containedWindowChanged(window: d->window); |
| 495 | } |
| 496 | |
| 497 | // ----------------------- Item updates ----------------------- |
| 498 | |
| 499 | /*! |
| 500 | \internal |
| 501 | |
| 502 | Called when the item's geometry has changed |
| 503 | */ |
| 504 | void QQuickWindowContainer::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
| 505 | { |
| 506 | qCDebug(lcWindowContainer) << this << "geometry changed from" |
| 507 | << oldGeometry << "to" << newGeometry; |
| 508 | |
| 509 | QQuickImplicitSizeItem::geometryChange(newGeometry, oldGeometry); |
| 510 | if (newGeometry.isValid()) |
| 511 | polish(); |
| 512 | } |
| 513 | |
| 514 | /*! |
| 515 | \internal |
| 516 | |
| 517 | Called when the item's (effective) state has changed |
| 518 | */ |
| 519 | void QQuickWindowContainer::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) |
| 520 | { |
| 521 | switch (change) { |
| 522 | case ItemVisibleHasChanged: |
| 523 | qCDebug(lcWindowContainer) << "Visible changed for" << this << "to" << isVisible(); |
| 524 | polish(); |
| 525 | break; |
| 526 | default: |
| 527 | break; |
| 528 | } |
| 529 | |
| 530 | QQuickImplicitSizeItem::itemChange(change, data); |
| 531 | } |
| 532 | |
| 533 | /*! |
| 534 | \internal |
| 535 | |
| 536 | Called when the window container item is moved to another window |
| 537 | */ |
| 538 | void QQuickWindowContainer::parentWindowChanged(QQuickWindow *parentWindow) |
| 539 | { |
| 540 | qCDebug(lcWindowContainer) << this << "parent window changed to" << parentWindow; |
| 541 | |
| 542 | Q_D(QQuickWindowContainer); |
| 543 | |
| 544 | if (!parentWindow) { |
| 545 | // We have been removed from the window we were part of, |
| 546 | // possibly because the window is going away. We need to |
| 547 | // make sure the contained window is no longer a child of |
| 548 | // former window, as otherwise it will be wiped out along |
| 549 | // with it. We can't wait for updatePolish() to do that |
| 550 | // as polish has no effect when an item is not part of a |
| 551 | // window. |
| 552 | if (d->window) { |
| 553 | // The window should already be destroyed from the |
| 554 | // call to releaseResources(), which is part of the |
| 555 | // removal of an item from a scene, but just in case |
| 556 | // we do it here as well. |
| 557 | d->window->destroy(); |
| 558 | |
| 559 | d->window->setParent(nullptr); |
| 560 | } |
| 561 | } else { |
| 562 | polish(); |
| 563 | } |
| 564 | } |
| 565 | |
| 566 | bool QQuickWindowContainerPrivate::transformChanged(QQuickItem *transformedItem) |
| 567 | { |
| 568 | Q_Q(QQuickWindowContainer); |
| 569 | |
| 570 | if (this->componentComplete && this->window) { |
| 571 | auto *transformedItemPrivate = QQuickItemPrivate::get(item: transformedItem); |
| 572 | qCDebug(lcWindowContainer) << "Transform changed for" << transformedItem |
| 573 | << "with dirty state" << transformedItemPrivate->dirtyToString(); |
| 574 | |
| 575 | if (transformedItemPrivate->dirtyAttributes |
| 576 | & QQuickItemPrivate::BasicTransform) { |
| 577 | // For some reason scale transforms, which result in the window |
| 578 | // being resized, end up with the window lagging a frame or two |
| 579 | // behind the item. Polish synchronously instead, to mitigate |
| 580 | // this, even if it may result in the opposite situation. |
| 581 | q->ensurePolished(); |
| 582 | } else { |
| 583 | q->polish(); |
| 584 | } |
| 585 | } |
| 586 | |
| 587 | return QQuickItemPrivate::transformChanged(transformedItem); |
| 588 | } |
| 589 | |
| 590 | QT_END_NAMESPACE |
| 591 | |