Search code examples
qtqmlqtquick2qt-quick

Qt Quick Layouts: distribute row items horizontally?


I am using a combination of ColumnLayout with nested RowLayouts:

enter image description here

and I'm trying to distribute items horizontally, so that the left edge of every row's first item lines up, and the right edge of every row's last item lines up, with a uniform spacing in between.

As an additional constraint, I want the window to automatically adjust its size to fit its contents, and I believe I achieved this by propagating implicitWidth/implicitHeight from root downwards.

I used Rectangles and Repeaters to keep the code short:

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Layouts 1.12

Window {
    width: rootRect.implicitWidth
    height: rootRect.implicitHeight
    visible: true
    title: qsTr("Hello World")

    readonly property var items: [
        [[120, 80, 'red'], [100, 80, 'green'], [170, 40, 'blue']],
        [[60, 120, 'yellow'], [140, 80, 'orange'], [60, 100, 'cyan']],
        [[150, 100, 'purple'], [100, 80, 'blue'], [170, 40, 'orange']],
    ]

    Rectangle {
        id: rootRect
        // achieve a padding of 5
        implicitWidth: columnLayout.implicitWidth + 10
        implicitHeight: columnLayout.implicitHeight + 10

        ColumnLayout {
            id: columnLayout
            anchors.centerIn: parent // for above padding

            Repeater {
                model: items

                Item {
                    id: row
                    implicitWidth: rowLayout.implicitWidth //[*]
                    //[*] Layout.fillWidth: true
                    implicitHeight: rowLayout.implicitHeight
                    readonly property var rowItems: modelData

                    RowLayout {
                        id: rowLayout
                        //[*] Layout.fillWidth: true

                        Repeater {
                            model: rowItems

                            Rectangle {
                                width: modelData[0]
                                height: modelData[1]
                                color: modelData[2]
                            }
                        }
                    }
                }
            }
        }
    }
}

However, no matter what I try (e.g. any combination of adding/removing the lines with //[*] ...), I cannot get the items to distribute horizontally.

It looks like a non-trivial operation, as the row with maximum size should do what it is currently doing, while the other rows should simply set their size to follow, and distribute their items accordingly... or perhaps I overlook some property of Qt Quick Layouts.

Note: I'm using layouts and not positioners, as in reality I'm laying out Qt Quick Controls that could benefit from being resized by a layout.

EDIT: when I add an item to style rows, it breaks again: try it online


Solution

  • Here's another answer. This solution is based on the QML style in your question with minor tweaks.

    I made use of the Frame component. Frame, if you haven't used it before, is a very convenient component for wrapping your contents with padding. So, instead of Rectangle-ColumnLayout. It's Frame-ColumnLayout. This is really useful because it allows you to delete the unnecessary pixel math that you had in both your Rectangle and your ColumnLayout.

    I used Item to solve your horizontal alignment problem. I did this by setting the Item's Layout.preferredWidth as well as setting Layout.fillWidth: true. Both properties, when used together will cause the Item to grow in size and distribute the spacing equally horizontally.

    Finally, I do some anchoring to make sure the Rectangle inside the Item is anchored left, middle, or right.

    import QtQuick
    import QtQuick.Controls
    import QtQuick.Layouts
    Page {
        background: Rectangle { color: "#444" }
        readonly property var items: [
            [[120, 80, 'red'], [100, 80, 'green'], [170, 40, 'blue']],
            [[60, 120, 'yellow'], [140, 80, 'orange'], [60, 100, 'cyan']],
            [[150, 100, 'purple'], [100, 80, 'blue'], [170, 40, 'orange']],
        ]
        Frame {
            background: Rectangle { }
            ColumnLayout {
                Repeater {
                    model: items
                    RowLayout {
                        id: rowLayout
                        Layout.fillWidth: true
                        Repeater {
                            model: modelData
                            Item {
                                Layout.preferredWidth: modelData[0]
                                Layout.preferredHeight: modelData[1]
                                Layout.fillWidth: true
                                Rectangle {
                                    anchors.centerIn: index === 1 ? parent : undefined
                                    anchors.right: index === 2 ? parent.right : undefined
                                    width: modelData[0]
                                    height: modelData[1]
                                    color: modelData[2]
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    

    You can Try it Online!

    final-result

    Here's a demo link for your followup question. Each RowLayout is stylized with a Frame.