Window embedding in Qt Quick

Qt 6.7 comes with some new and exciting APIs for window management in Qt Quick. In this blog post we'll look at the changes, and what use-case they open up.

Top level windows

Window management in Qt Quick is provided by the Window type, added way back in Qt 5.0. The type as it is today enables you to create and manage top level windows, for example:

import QtQuick

Window {
    id: mainWindow
    width: 300; height: 300
    visible: true
    color: "darkseagreen"

    MouseArea {
        anchors.fill: parent
        onClicked: dialog.show();
    }

    Window {
        id: dialog
        flags: Qt.Dialog
        color: "thistle"
    }
}

Which will give a dialog window centered on top of the main window:

The centering comes as a result of the dialog having the main window as it's transient parent, associating the dialog to the main window. For QWindow this transient relationship needs to be explicitly managed via QWindow::setTransientParent(), but in Qt Quick we automatically enable this relationship for Windows declared as a children of another Window in the QML document.

This already gives the building blocks for creating top level dialogs, popup menus, tooltips, etc.

Child windows

A use-case that's not currently handled by the Window type is child windows. Child windows live inside the top level window (or another child window), and follow the position of its parent.

On the surface this sounds exactly like any old Item in a Qt Quick scene, and normally these primitives would be sufficient, but as we'll see soon there are cases where a dedicated child window has its uses.

On the Qt GUI level child windows are managed by calling QWindow::setParent(), so in tech preview in Qt 6.7 we have introduced a new parent property to Window, that allows setting the visual parent of window.

import QtQuick

Window {
    id: mainWindow
    width: 300; height: 300
    visible: true
    color: "darkseagreen"

    Window {
        id: childWindow
        color: "thistle"
        parent: mainWindow
        visible: true
    }
}

Which will make the window a visual child of the main window:

Unlike the transientParent property, the parent property needs to be set explicitly, and is not enabled automatically for windows declared as children of other windows in the QML document. There is also no built in logic for centering, matching the behavior of QWindow::setParent().

Windows with Item parents

However, a Window can also have an Item as its visual parent:

import QtQuick

Window {
    id: mainWindow
    width: 300; height: 300
    visible: true
    color: "darkseagreen"

    Item {
        id: item
        anchors.centerIn: parent
        width: childWindow.width
        height: childWindow.height

        Window {
            id: childWindow
            color: "thistle"
            parent: item
            visible: true
        }
    }
}

Which results in the window's position being relative to the item:

Z ordering

We've also added a new z property to Window, matching the existing property for Item, which allows controlling the stacking order between sibling child windows:

import QtQuick

Window {
    id: mainWindow
    width: 300; height: 300
    visible: true
    color: "darkseagreen"

    Window {
        color: "thistle"
        parent: mainWindow
        visible: true
        z: 1
    }

    Window {
        x: 50; y: 50
        color: "darkgrey"
        parent: mainWindow
        visible: true
    }
}

Normally the stacking order of sibling windows will follow the document order, just like for items, but in this case we've made the first window stack above the second:

But why?

Now, why would a child window be useful?

One such use-case is per-window surface properties. Qt's rendering pipeline today is not color managed, meaning we don't pick up the color space of every asset (color, image, video), and color match these to a target color space. What we do support is setting the target color space of the window, via QSurfaceFormat::setColorSpace(). If you know that your source assets are in a given color space, or do your own color matching, you can set the target color space of the window to e.g. QColorSpace::AdobeRgb. Qt will do its best to not get in the way and deliver the pixels to the OS without touching them, letting the OS do the job of color matching the window to the final target color space of the display (if the platform supports it).

There's no QML API at the moment to cover this use-case, but with a small C++ helper to set the Window's surface format before making it visible, we can do something like this on macOS:

The outer window is the default color space of the screen (Display P3 in this case), while the inner window is explicitly set to Adobe RGB. The effect is subtle, but important for color accurate rendering.

A related case is high dynamic range (HDR), which allows video and image content to take full advantage of the brightness of modern displays to e.g. represent specular highlights in more detail. Rendering HDR  content requires changes to the rendering pipeline, which on some platforms may cause shifts in the brightness level of the standard dynamic range (SDR) color values. You typically don't want the rest of your application UI to be affected by playing a HDR video, so one way to mitigate this is to use a dedicated HDR-enabled child window for the HDR content. In addition, HDR formats also often specify a color space, so using a dedicated window solves this issue as well, as described earlier.

We are working on HDR support in Qt, so stay tuned for more details on this use-case.

Embedding non-Qt Quick windows

Being able to turn Qt Quick Windows into child windows is of course great, but has limited uses for most applications. The observant reader may have noticed that supporting parenting a Window to an Item means that we have the ability to control the position of a child window based on an item. Wouldn't it be nice to do that with any window?

And that's exactly what we've enabled, via the new WindowContainer type. This type is tech preview in 6.7 and mirrors the functionality provided in Qt Widgets by QWidget::createWindowContainer(), but for Qt Quick:

import QtQuick

Window {
    id: mainWindow
    width: 300; height: 300
    visible: true
    color: "darkseagreen"

    WindowContainer {
        window: openGLWindow
        anchors.centerIn: parent
    }
}

The contained window can be any QWindow. In this example it's the OpenGL window from Qt's OpenGL Window Example, exposed to QML via a simple context property, resulting in:

Important Item properties like position, size, z-order, visibility, and clip, have been plumbed from the item to the window, but just like for QWidget::createWindowContainer() there may sharp corners, so please let us know of any issues you find!

The success of the embedding also depends on how "well behaved" the embedded window is as a child window. For example, we know that Qt Widgets has some quirks when embedded into something that's not a top level QWidget (on account of never being written for this use-case), and this is something we're hoping to improve down the line.

Foreign windows

Finally, if we can embed any QWindow, that means we can also embed so-called foreign windows. In Qt we use this term for QWindows that represent native platform windows created outside of Qt, either manually using native code or via some other framework. As long as the underlying native platform window type is the same type as Qt expects (NSView, UIView, HWND, xcb_window_t, etc), you can use QWindow::fromWinId() to create a foreign QWindow representing the native window, which can then be embedded the same way as above.

This allows you to for example embed a native map view, video player, or web view:

And not only that. As these embedded foreign windows are "just another window", we can have other windows stacked on top (or bottom) of them, such as the Qt logo in the examples above. As long as the platform supports child windows with alpha channels (and most do), these can be semi transparent as well.

This closes the loop from earlier on why being able to parent a Window to another item, in this case the window container, is a useful feature.

Feedback welcome

As mentioned, this feature is tech preview in the upcoming Qt 6.7 release, but can already be tried out with the current beta release, so please test and let us know of any issues you find, or if your use-case is not covered by the existing APIs.

For a quick start you can try out the windowembedding manual test, which showcases various features of the Qt Quick window embedding.

Happy embedding! 


Blog Topics:

Comments