Details
-
Suggestion
-
Resolution: Unresolved
-
Not Evaluated
-
None
-
None
Description
(Here, "transforming" refers to both geometrical transformations as well as resizing)
Users might want to transform a window's contents, e.g.:
- To enlarge the contents of the whole window, or
- To implement an HMI on an embedded system where the device display has a different orientation than the UI orientation, for example when using a landscape GUI on a portrait display panel
Currently, it difficult for a user to transform the contents of a Qt Quick window because of insufficient/incomplete documentation and suboptimal API.
What our documentation says so far
https://doc.qt.io/Boot2Qt/b2qt-customization.html#switching-between-portrait-and-landscape-views shows a simple example that rotates a "container" item and swapping its width and height:
import QtQuick Item { id: root width: 800 height: 1280 Rectangle { width: root.height height: root.width rotation: 90 anchors.centerIn: parent // [Content here gets rotated] } }
Unfortunately, this example is insufficient when the application uses Qt Quick Controls that involve Popups (e.g. ComboBox drop-downs, Menus, Drawers, Dialogs). Users will find that their content inside Rectangle are generally transformed as expected, but many Popup elements are not.
https://doc.qt.io/qt-6/qml-qtquick-controls-popup.html#popup-sizing clarifies things a bit more: "The popup's content item gets parented to the overlay, and does not live within the popup's parent". It explains that we need to apply the transformation both to the user-implemented Item tree and to the Overlay:
import QtQuick import QtQuick.Controls.Basic Window { width: 800 height: 600 visible: true Scale { id: scale xScale: 2.0 yScale: 2.0 } ComboBox { transform: scale model: ["Alpha", "Bravo", "Charlie"] } Overlay.overlay.transform: scale }
Unfortunately, this example is insufficient when the application uses ApplicationWindow instead of Window. Users will find that their content and the Popup elements are transformed as expected, but the window's background is not.
There's no documentation to explain that one. And how do we know if anything else is missing? To get the answers, it is helpful to know some of the internal details of Qt Quick's window classes.
Three Types of Windows
Putting these details to good use for geometric transformations
From the diagram, we can see that, if there is no unscaled content in the window, then the ComboBox example above can be simplified by transforming the QQuickRootItem directly...
import QtQuick import QtQuick.Controls.Basic Window { width: 800 height: 600 visible: true contentItem.transform: Scale { // or contentItem.parent.transform, if using ApplicationWindow xScale: 2.0 yScale: 2.0 } ComboBox { model: ["Alpha", "Bravo", "Charlie"] } }
...which would auto-transform all relevant sub-trees (including ApplicationWindow.background if it exists). The only minor obstacle is that ApplicationWindow.contentItem != Window.contentItem (QTBUG-123494) which can trip up unsuspecting users. Documentation can help here, but more intuitive API would be better.
Problem solved? Not yet, because although we can apply geometric transformations to QQuickRootItem, we cannot resize it. So, window rotations need a lot more boilerplate to work.
Putting these details to good use for swapping screen orientation
Window
It would be nice and easy if we could do the same as the Scaling example above, and simply rotate+resize the QQuickRootItem. But...
Obstacles, or things that are unclear
- While we can rotate the QQuickRootItem, we cannot resize it. So we must resize its children individually instead.
- QQuickOverlay resists attempts to set its width and height (QTBUG-122962). Fortunately, anchoring works.
Solution
Putting that together, we get:
import QtQuick import QtQuick.Controls.Basic Window { id: root width: 640 height: 480 visible: true contentItem.rotation: 90 Overlay.overlay.anchors.fill: gui MyGui { id: gui width: root.height height: root.width anchors.centerIn: parent } }
QQuickView/QQuickWidget
From the diagram above, we can see that the QQuickView/QQuickWidget structure is quite similar to Window, but...{}
Obstacles, or things that are unclear
- The top-level user Item's size is tied to the QQuick(View|Widget)'s size, so we cannot resize it. So we must resize its child instead.
- That child is not a sibling of the QQuickOverlay, which blocks us from setting up anchors. So we must re-parent it.
- The QQuickRootItem and QQuickOverlay are not available when Component.onCompleted is triggered (
QTBUG-122894). So we must defer assignments/bindings.
Solution
Putting that together, we get:
import QtQuick import QtQuick.Controls.Basic Item { id: root width: 640 height: 480 visible: true Component.onCompleted: Qt.callLater(function(){ let windowContentItem = Overlay.overlay.parent // This does not exist at Component.onCompleted gui.parent = windowContentItem // Reparent the MyGui to allow the Overlay to anchor to it windowContentItem.rotation = 90 Overlay.overlay.anchors.fill = gui }) MyGui { id: gui width: root.height height: root.width anchors.centerIn: parent } }
ApplicationWindow
From the ApplicationWindow diagram above, we can see that ApplicationWindow's structure is quite different from Window's. This, along with a few other important details, are not readily apparent from our documentation...
Obstacles, or things that are unclear
- ApplicationWindow.contentItem != Window.contentItem (QTBUG-123494). So to access the QQuickRootItem, we must use contentItem.parent or Overlay.overlay.parent
- The top-level user Item is not a sibling of the QQuickOverlay, which blocks us from setting up anchors. However, we can take advantage of the fact that the background is a sibling instead.
- The QQuickContentItem is also a direct child of the QQuickRootItem. So we have a 3rd thing to resize compared to the earlier cases (plus we must make room for the menuBar/header/footer if they exist).
Solution
Putting that together, we get:
import QtQuick import QtQuick.Controls.Basic ApplicationWindow { id: root width: 640 height: 480 visible: true background: Item { anchors.centerIn: parent width: root.height height: root.width } contentItem { parent.rotation: 90 anchors.fill: background anchors.topMargin: (root.menuBar?.height ?? 0) + (root.header?.height ?? 0) anchors.bottomMargin: (root.footer?.height ?? 0) } Overlay.overlay.anchors.fill: background MyGui { id: gui anchors.fill: parent } }
Assessment of the current situation
- The code needed to rotate ApplicationWindow is very different from the code needed to rotate Window.
- Coming up with the solutions above required knowledge about the internal, undocumented details of the different window classes' item trees. Users cannot be generally expected to come up with these solutions themselves.
- It is not guaranteed that these solutions will continue to work for future versions of Qt.
Next Steps
- Documenting these solutions could be a good first step. However, it is not ideal that we are relying on implementation details of the windows' structure.
- It would be nicer in the long run to have a formal API to apply the transformations - and importantly, this API should be consistent for both Window and ApplicationWindow. (I understand that making it work seamlessly with QQuickView/QQuickWidget is less feasible)
Some ideas for the API
The core idea is to have a designated item to receive all transformations, and have it propagate to all parts of the scene graph (both user-implemented and internal)
- A new property that that returns window's QQuickRootItem?
- Named "rootItem" perhaps: The term "Content Item" does not necessarily mean root (think ApplicationWindow.contentItem and Popup.contentItem)
- Decouple the QQuickRootItem's size from the window's size?
- If we can make QQuickRootItem's width/height different from the window's width/height, then most of the boilerplate code above for rotating content will become unnecessary.
- If we don't want to allow arbitrary sizes, perhaps there can be a property that swaps width and height instead
- Insert a new container item between the QQuickRootItem and its current children?
- If it is not feasible/desirable to decouple the QQuickRootItem's size from the window's size, then perhaps we could have a "sub-root" item instead, which is the single point of applying the transformations.
Other considerations
Qt for MCUs 2.7 just introduced a system for rotating the display (https://doc.qt.io/QtForMCUs-2.7/qtul-staticscreenrotation.html ). It would be good if the system for "regular" Qt and MCUs are as similar as possible.
Attachments
Issue Links
- relates to
-
QTBUG-122799 Provide a modernized and coherent story for switching between portrait and landscape views
- Reported