Qt has (almost) been here since the dawn of graphical user interfaces, being released in 1995, just 5 years after Windows 3.0. Needless to say, technologies, expectations and duties for a UI toolkit changed many times over the years. Organizing and layouting of graphic elements is one of those duties and the circumstances under which this is done changed substantially since the early days of Qt: From windowed applications on small desktop screens and fixed size full-screen apps on embedded devices, we came a long way to ridiculously large desktop screens and dynamically sized applications on smartphones and tablets of all thinkable form-factors and screen resolutions. Applications need to look and feel good on many devices as well as in different configurations, landscape and portrait mode, windowed and full screen. More than that, they need to be able to switch seamlessly between those modes.
The longevity of Qt is a remarkable achievement and an asset in developing stable and meaningful APIs. At the same time, it can be a burden, as we want to keep existing code working for a long time. However, basis of Qts longevity is the ability to critically review existing APIs and to update them if necessary. This is an exercise we conducted recently with the layouting system. The layouting system of Qt stayed basically the same over a long time, only slightly extended by QtQuick and its anchor and positioning concept and some adaptions for high-dpi devices. We wanted to know if this layouting system still conforms to the demands of modern devices. It is also a good opportunity to look at other technologies, in particular HTML and Material/Flutter to see what they do better. We are curious about any methods that miss a representation in the Qt API and eager to fill these gaps. We also ported the respective examples to Qt, to validate or invalidate the completeness of our current API. In the following we present some of our findings to give you an update on how we think of layouts and where the future could lead to. We hope to inspire some of you and collect feedback on the current system, our thoughts and ideas for the future. If you are passionate about Qts layout system, this is the opportunity to raise your voice and let us hear your opinion!
## HTML and CSS Inspired Adaptive Layouting
Adaptive layouts played a big role in web design from the very beginning. HTML and CSS have a clear separation of content (HTML) and looks (CSS stylesheets). This gives great flexibility, since you can easily change the layout (in CSS) in order to adapt to different screen sizes, while not having to alter the HTML content hierarchy. How this plays out in practice can be seen in the [responsive web design tutorial of w3school](https://www.w3schools.com/css/css_rwd_mediaqueries.asp).
The [example](https://www.w3schools.com/css/tryit.asp?filename=tryresponsive_mobilefirst) is straight forward: HTML tags are used to create a hierarchy of different elements, assigned to various types using the class property. The visual representation of these types is described in the CSS stylesheet, depending on the device, in particular its width. The w3school example stays simple and sets the width of the elements to one third of the total width to put three vertical blocks beside each other or sets the width of all elements to 100% of the total width, such that they appear beneath each other. The first layout makes great use of large devices in landscape format, the latter works well on small devices or in portrait mode.
We must admit that there is a kind of simplicity and ease with which this responsive layout is generated. How do the Qt layouts compare to this? The first thing we must consider is the separation of content and layout. There is no dedicated language for styling like CSS in Qt, but we have our one-for-all scripting language QML, that we can use for the content as well as for the layout and we can do so to some extent in separate places. First, we define our contents and the hierarchy with declarative QML, notably mixing styling with contents (these can be separated as well but that is a different topic). On top of our hierarchy there is a GridLayout with three columns. The header and the bottom items span the complete width of the window and therefore occupy three columns. Between the header and bottom there are three columns of items. This matches the description of the content from the responsive w3school example.
All that is missing is the description of the layout by the CSS part of the w3school example. An elegant way to do this in QML are states, modifying the properties of the columns and the layout, depending on the device or window width. We need two states, representing the narrow vertical oriented layout and the wide horizontal oriented layout. For the narrow layout, we let all columns occupy the whole width and therefore set their column span to three, corresponding to 100% as in the CSS example. The grid layout in Qt behaves the same way as CSS and automatically puts items in the next row if there is not enough space in the current row (flow layout), making the blocks of items appear beneath each other. In the wide layout, we can fit all columns next to each other, which can be achieved by setting their column span to one. The example on w3school continues to add a third intermittent state, but also this can be done easily with QML, simply [adding another state](https://www.w3schools.com/css/tryit.asp?filename=tryresponsive_col-s).
* TODO: Gif of the example with 3 states
GridLayout { // GridLayout is used to dynamically switch between RowLayout and ColumnLayout behavior
Both, CSS and QML layouts are flexible and some experimenting showed that we can create complex adaptive layouts with both. Naturally, being well accommodated to Qt and very foreign to the web world, I prefer the latter. I particularly like that we can describe everything with a single concept in QML, whereas two languages (not even considering adding JavaScript) with completely different patterns are required in HTML. Further, Qt provides additional functionality for free: Instead of relying on a fixed width as in CSS, we can let the layout make some smarter decisions, based on what the content items prefer. In contrast we need to tell CSS exactly how wide columns should be, making the layout much less useful.
## Improving the QML Layout: Align:Justify?
Is there a possibility to make the Qt experience better? We think so, yes. Looking at the example, we simply changed the layout from a three-column layout to a single column layout. After all, changing the column span of several items to implement this seems unnecessarily complicated, when apparently just the number of columns changed from three to one. So here is an even simpler solution, omitting any states, simply changing the number of columns:
GridLayout { // GridLayout is used to dynamically switched between RowLayout and ColumnLayout behavior
anchors.left: parent.left
anchors.right: parent.right
columns: (window.width > 768)? 3 : 1;
...
}
* TODO: Link to full code
This solution works well when there are only two states to represent and if these states represent the edge-cases of fully horizontal or vertical layouts. In the example with three states, it is difficult to find combinations of column span and number of columns where the layout makes sense and leaves no blank spots.
In an effort to make also the simplest code produce adaptive layouts, we tried to extend the Layout class to avoid such blank places. The idea was to extend the layout for an additional property, that we call flowAlignment for now. You can see and experiment with this property yourself with [this patch] (https://codereview.qt-project.org/c/qt/qtdeclarative/+/467014), notably an early hacky prototype. This property controls the behavior of the layout when items are moved to a new row because the current one is full. Like the alignment of a text, we want to align items to the left, right, center or justify, meaning that they are stretched to fill up the row. The flowAlignment property can take all these states and the layout will move the items accordingly. The align justify mode is probably the most useful case but in an attempt to use a familiar and complete API, we also implanted the rest. With
GridLayout { // GridLayout is used to dynamically switched between RowLayout and ColumnLayout behavior
We think this addition increases the flexibility of adaptive layouts substantially. It allows to change the columns and rows quickly without worrying or even thinking about filling up all lines. A nice side effect is, that we can make smart decisions at runtime when expanding items to fulfill the align-justify rule and increase the size of the item with the largest preferred size. Unfortunately, this addition has to live deep in the existing layout classes and work with all current APIs, which might be difficult to maintain. You can try the feature for yourself with the example added to [the patch](https://codereview.qt-project.org/c/qt/qtdeclarative/+/467014) and let us know if you would like to see this in Qt.
## Material and Flutter
Material is the dominant design language on Android devices and Google apps, and it comes with a strong opinion on how layouts should be. Qt supports Android and we think it should therefore support the design language for this platform. Aside from that, Material provides a lot of great ideas and inspiration. So maybe we can even learn a thing or two for the benefit of all other platforms as well.
The (now outdated but still interesting) [Material1 responsive layouts concept](https://m1.material.io/layout/responsive-ui.html#responsive-ui-breakpoints) provides an interesting concept: Everything is layouted in a rigid grid of twelve equally sized columns. To our surprise, this super simple concept cannot be created easily in Qt, because our layouts try to be smart and therefore do not like fixed widths, at least not without setting it individually on every item. However, making something less smart is usually not difficult, and we could easily dumb down our layouts with a new flag, that we called ignoreSizeHints for now. With this flag it is very easy to recreate the responsive layouts as suggested by Material1, of course with the use of states to adapt them to the respective device size. Might this be a good addition to our layouts? After all, the concept of fixed columns was dropped in the newer versions of Material, which might indicate that is was not the best idea to start with. This new property is also included in [the patch](https://codereview.qt-project.org/c/qt/qtdeclarative/+/467103), so take it for a ride and tell us what you think!
A good takeaway from Material might also be the classes of devices which layouts should support, namely phones in portrait mode (width < 600 device-independent pixels, dp), tablets in portrait mode (width < 840 dp) and everything else (Desktop, phone and tablet in landscape mode). These numbers come with the confidence of a fairly large company with an even larger research budget and might therefore be worth keeping in mind. Also remember to distinguish between [device-independent pixels and true physical pixels] (https://doc.qt.io/qt-6/highdpi.html).
## Material3 and Adaptive Scaffold
The renewed [Material3 adaptive layout](https://m3.material.io/foundations/layout/understanding-layout/overview) drops the rigid grid and suggests a different approach, based on various navigation bars and bodies. Androids in-house toolkit flutter provides an implementation of this layouting concept called adaptive scaffold. As an exercise we tried to recreate the respective [example](https://pub.dev/packages/flutter_adaptive_scaffold) with the layouts available in Qt.
Using states as before, we can recreate this example, changing columns and rows, turning items visible and invisible, changing parents and layouts, creating multiple instances of the same button at different positions. But is it pretty? Absolutely not. Nobody wants to switch parents in a state change which also changes the hierarchy that was meant to be static. The alternative, duplicating and hiding items with the same purpose and appearance seems equally bad. But this would be required to create the nested structure of layouts and items that are required for the flutter example. This is an unsatisfying solution, so we tried to come up with a better architecture.
The idea is to have minimalistic items that can be used as a placeholder for items in a layout. They have a single property, namely the item they stand in place for and which they should represent. We call it LayoutItemProxy for now. If the respective layout and thus the LayoutProxyItem is visible, it will take over the items position and control its visibility. The other way around, the LayoutProxyItem will forward the preferred size and all other relevant properties of the item to the layout. This way we can define multiple layouts for different devices, full of LayoutProxyItem. Depending on the device or the current window size, we can show and hide layouts and the corresponding LayoutItemProxy will pull the items in the correct place. A prototype for the LayoutItemProxy and some demonstration examples can be found in [this patch] (https://codereview.qt-project.org/c/qt/qtdeclarative/+/456325).
The example of the adaptive scaffold boils down to defining all items that we will need at some point with an arbitrary parent and at an arbitrary point in the QML hierarchy. We continue with definitions of the layouts for compact, medium and large devices, full of LayoutItemProxy targeting the actual items. Adding a small helper component to control the visibility of layouts following the device or window size finishes the example:
Once the LayoutProxyItem is working, this example is fairly simple to write in QML. It is also a very flexible concept which we tried to demonstrate in the example with various nested structures, a mix of real and proxy items and even flickables. Some attention has to be given to the event handling that might depend on the parent hierarchy in some cases but generally speaking everything should behave as normal since we simply reposition the element with the proxy.
LayoutChooser {
id: layoutChooser
width: parent.width
height: parent.height
layoutChoices: [
smallLayout,
mediumLayout,
largeLayout
]
criteria: [
width < 700,
width < 1000,
true
]
MyButton {
id: articlesButton
objectName: "articlesButton"
iconId: 0xef42 // see https://fonts.google.com/icons
}
// more buttons
Repeater { // many more items
id: rep
model: 12
Rectangle {
color: '#ffc9c5'
implicitHeight: width
implicitWidth: 256
Layout.fillWidth: true
Layout.fillHeight: true
}
}
property Item smallLayout: ColumnLayout {
height: parent.height
width: parent.width
Repeater {
model: 2
LayoutItemProxy { target: rep.itemAt(index) }
}
RowLayout {
Layout.fillHeight: false
Layout.fillWidth: true
LayoutItemProxy { target: inboxButton }
LayoutItemProxy { target: articlesButton }
LayoutItemProxy { target: chatButton }
LayoutItemProxy { target: videoButton }
}
}
property Item mediumLayout: ColumnLayout {
... // define medium layout with LayoutItemProxies
}
property Item largeLayout: ColumnLayout {
... // define large layout with LayoutItemProxies
}
...
}
* TODO: Gif of the example
Using this concept we should also be able to provide an adaptive scaffold class with Qt, facilitating application developers with their adaptive layouts. However, both the LayoutProxyItem and a hypothetical adaptive scaffold can live happily in the application code base, so Qt itself might be the wrong place for it. Making it widely independent of the rest of the layout API makes this feature also easy to maintain and we do not have to fear any collision with other functionalities. Another good news is that animations, as shown in the flutter example can be added at a single point, namely the LayoutProxyItem for all items. Again, we invite you to play around with the example and the code to see weather you like or what you are missing.
By the way: We needed to replicate the Material icons for this example. This was fairly easy using a FontLoader and the Material3 icon font.
## Wrap up
These are our current ideas for adaptive layouts. Generally, we think that Qt is well suited for most problems and we could implement most examples with simple state machines. Changing the hierarchy of the layout as shown in the Adaptive Scaffold example is currently not well represented in the Qt API and requires the addition of the LayoutProxyItem. We are curious to hear from you what you think of this solution and whether you want to see more of it. Do you want it to be part of Qt or would you be happy copying some example code into your application? We also added two more APIs to simplify the solution to other issues, namely the flowAlignment and ignoreSizeHints properties. We are equally curious to hear from you what you think about those.
Do you know of any other concept for layouting elements that you would like to see reflected in the Qt API? Do you have any more complex cases that we do not cover with the examples shown here? Leave us a comment or a link below and we promise to have a look at it.