Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Dynamic height for card/rectangle used as ListView delegate



  • Hi, long-time web dev here, building a native app for desktop for the first time on QT/QT Creator. Been learning QT/QML for a day, and I've got this far, but I need help.

    I'm building an a fairly simple interface to my design, where in I have a list view whose delegate is a custom component which in itself is a card that display a bunch of information about a gitlab issue. This issue (its text and content are completely dynamic)

    Here is an example a card. Every part marked with a red square can have a variable height either due to:

    1. text length
    2. tab content varying in height: (i.e. list tab is just a set of icons, assignee is a set of assignees and labels, and estimate is a simple number entry

    task card

    The problem I've having is the layout of the cards. I'm trying to use the RoyLayout and ColumnLayout types, because I know they have their own intrinsic heights calculated from children. This is important to me, because, and this is really important I don't want to have to specify explicit heights for any of the content. I want the cards to be as compact as possible and grow with their content, including the internal card menu, which is only visible if the card is selected.

    I'm almost there, but I just can't quite get some of the margins correct (especially on the bottom margin) which seems to be be flush against the bottom of the card, and in fact, despite being invisible, seems to be reserving space for the hidden buttons:

    ListView

    ColumnLayout {
        id: taskList
        Layout.fillWidth: true
        Layout.fillHeight: true
        spacing: 0
    
        ListView {
            id: taskListView
            Layout.fillHeight: true
            Layout.fillWidth: true
            orientation: Qt.Vertical
            spacing: 10
    
            model: ListModel {
                id: listModel
                Component.onCompleted: {
                    for (var i = 0; i < 20; i++)
                    listModel.append({"Name": "Item " + i})
                    taskListView.currentIndex = -1
                }
            }
    
            delegate: TaskCard {
                margin: 12
                cardIndex: index
                isSelected: taskListView.currentIndex == index
                onSelected: {
                    taskListView.currentIndex = index
                }
            }
    
            focus: true
            clip: true
        }
    }
    
    

    TaskCard

    Item {
        id: taskCard
    
        // data props
        property int cardIndex
        property int taskId
        property string name: "[EXAMPLE] Do something great today."
        property string reference: "aliengen/aProject#1"
        property var assignee: {
            assignee_id: 1
            name: 'Example guy'
        }
        property string stage: "To Do"
        property real estimate: 0
        property real elapsed: 0
        property bool shouldShowAssignee: false
        property bool isSelected: false
        property int selectedMenuType: 1
    
        // layout props
        property real margin: 12
    
        height: taskCardContent.childrenRect.height
        anchors {
            left: parent.left
            right: parent.right
        }
    
    
        // signals
        signal selected(int id)
    
        // states
        states: [
            State {
                name: 'SELECTED'
                when: taskCard.isSelected
            }
        ]
    
    
    
    
        Rectangle {
            id: taskCardContent
            anchors {
                fill: parent
                leftMargin: taskCard.margin
                rightMargin: taskCard.margin
            }
    
            height: taskCardContent.childrenRect.height
    
    
            color: styles.c_secondary
            border {
                width: taskCard.state == 'SELECTED' ? 1 : 0
                color: styles.c_ag_green
            }
    
            MouseArea {
               onClicked: taskCard.selected(taskCard.cardIndex)
               anchors.fill: parent
            }
    
    
            ColumnLayout {
               id: taskCardContentLayout
               anchors {
                   left: parent.left
                   right: parent.right
                   leftMargin: 16
                   rightMargin: 16
               }
    
    
    
               RowLayout {
                   id: taskCardMetaLayout
                   Layout.fillWidth: true
    
    
                   Label {
                      text: taskCard.stage
                      font.pixelSize: styles.f_xs
                      font.weight: Font.Medium
                      color: styles.c_ag_green
                      Layout.fillWidth: true
                      Layout.alignment: Qt.AlignLeft
                   }
    
                   Rectangle {
                       id: taskCardTimingLayout
                       Layout.alignment: Qt.AlignRight
                       Layout.fillWidth: true
                       height: taskCardTimingLayout.childrenRect.height
                       color: styles.c_secondary
    
                       Label {
                          id: taskCardTimingEstimate
                          text: taskCard.estimate + "h / "
                          font.pixelSize: styles.f_xs
                          font.weight: Font.Normal
                          color: styles.c_ag_green
                          anchors {
                              right: taskCardTimingRemaining.left
                          }
                       }
    
                       Label {
                          id: taskCardTimingRemaining
                          text: taskCard.elapsed + "h"
                          font.pixelSize: styles.f_xs
                          font.weight: Font.Normal
                          color: styles.c_ag_blue
                          anchors {
                              right: parent.right
                          }
                       }
    
                   }
    
    
    
    
               }
    
               RowLayout {
                   id: taskTitleRowLayout
                   Layout.fillWidth: true
    
                   Label {
                       id: taskCardName
                       text: taskCard.name
                       color: styles.c_primary
                       minimumPixelSize: styles.f_m
                       font.pixelSize: styles.f_m
                       font.weight: Font.Bold
                       wrapMode: Text.WordWrap
                       Layout.fillWidth: true
                       Layout.alignment: Qt.AlignLeft
                   }
    
                   Button {
                       width: 40
                       Layout.alignment: Qt.AlignRight
                       id: taskCardTimerButton
                       background: Rectangle {
                           color: 'transparent'
                       }
                       icon.source: './images/IconPlayOutline.svg'
                       icon.name: 'IconPlayOutline'
                       icon.color: styles.c_ag_green
                   }
               }
    
               RowLayout {
                   id: taskCardReferenceLayout
                   Layout.fillWidth: true
    
                   Label {
                      id: taskCardReference
                      text: taskCard.reference
                      font.pixelSize: styles.f_s
                      font.weight: Font.Medium
                      color: styles.c_accent_1
                      wrapMode: Text.WordWrap
                      Layout.fillWidth: true
                   }
               }
    
    
               RowLayout {
                   visible: taskCard.isSelected
                   id: taskCardControlMenuLayout
                   Layout.fillWidth: true
    
                   ColumnLayout {
                       id: taskCardControlMenuInnerLayout
                       Layout.fillWidth: true
    
                       RowLayout {
                           id: taskCardControlMenuTabs
                           Layout.fillWidth: true
                           spacing: 0
    
                           Button {
                               id: taskCardControlMenuTabListChooser
                               Layout.fillWidth: true
                               Layout.preferredHeight: 20
                               background: Rectangle {
                                   color: styles.c_secondary
                               }
                               contentItem: Text {
                                   text:  qsTr("LIST")
                                   color: selectedMenuType == 1 ? styles.c_ag_green : styles.c_accent_1
                                   font.pixelSize: styles.f_s
                                   font.weight: selectedMenuType == 1 ? Font.Bold : Font.Normal
                                   horizontalAlignment: Text.AlignHCenter
                                   verticalAlignment: Text.AlignVCenter
                               }
                           }
    
                           Button {
                               id: taskCardControlMenuTabAssigneeChooser
                               Layout.fillWidth: true
                               Layout.preferredHeight: 20
                               background: Rectangle {
                                   color: styles.c_secondary
                               }
                               contentItem: Text {
                                   text:  qsTr("ASSIGNMENT")
                                   color: selectedMenuType == 2 ? styles.c_ag_green : styles.c_accent_1
                                   font.pixelSize: styles.f_s
                                   font.weight: selectedMenuType == 2 ? Font.Bold : Font.Normal
                                   horizontalAlignment: Text.AlignHCenter
                                   verticalAlignment: Text.AlignVCenter
                               }
                           }
    
                           Button {
                               id: taskCardControlMenuTabEstimateProvider
                               Layout.fillWidth: true
                               Layout.preferredHeight: 20
                               background: Rectangle {
                                   color: styles.c_secondary
                               }
                               contentItem: Text {
                                   text:  qsTr("ESTIMATE")
                                   color: selectedMenuType == 3 ? styles.c_ag_green : styles.c_accent_1
                                   font.pixelSize: styles.f_s
                                   font.weight: selectedMenuType == 3 ? Font.Bold : Font.Normal
                                   horizontalAlignment: Text.AlignHCenter
                                   verticalAlignment: Text.AlignVCenter
                               }
                           }
                       }
                   }
               }
    
    
            }
    
    
        }
    

    Resulting render:

    How can I fix these things?

    1. There is too much space on the bottom side of the card
    2. The buttons overflow the box, and the border
    3. there is no padding on the box on the top side

    rendered cards

    Any help would be appreciated



  • real task card

    Solved it for those who care. Had to jump through some hoops:

    Task Card

    Item {
        id: taskCard
    
        // data props
        property int cardIndex
        property int taskId
        property string name: "[EXAMPLE] Do something great today."
        property string reference: "aliengen/aProject#1"
        property var assignee: {
            assignee_id: 1
            name: 'Example guy'
        }
        property string listType: 'BACKLOG'
        property string stage: "To Do"
        property real estimate: 0
        property real elapsed: 0
        property bool shouldShowAssignee: false
        property bool isSelected: false
        property int selectedMenuType: 1
    
    
        // layout props
        property real margin: 12
    
        anchors {
            left: parent.left
            right: parent.right
        }
        height: taskCard.childrenRect.height
    
    
        // signals
        signal selected(int id)
    
        // states
        states: [
            State {
                name: 'SELECTED'
                when: taskCard.isSelected
            }
        ]
    
    
    
    
        Rectangle {
            id: taskCardContent
            anchors {
                left: parent.left
                right: parent.right
                leftMargin: taskCard.margin
                rightMargin: taskCard.margin
            }
    
            height: taskCardContentLayout.height
    
            color: styles.c_secondary
            border {
                width: taskCard.state == 'SELECTED' ? 1 : 0
                color: styles.c_ag_green
            }
    
    
    
    
            MouseArea {
               onClicked: taskCard.selected(taskCard.cardIndex)
               anchors.fill: parent
            }
    
    
            Rectangle {
                id: taskCardInnerSpacer
                height: taskCardContentLayout.height - 2
                anchors {
                    left: parent.left
                    right: parent.right
                    leftMargin: 16
                    rightMargin: 16
                    topMargin: 12
                    bottomMargin: 12
                }
                color: styles.c_secondary
                y: 1
    
    
                ColumnLayout {
                   id: taskCardContentLayout
                   anchors {
                       left: parent.left
                       right: parent.right
                   }
    
                   RowLayout {
                       id: taskCardMetaLayout
                       Layout.fillWidth: true
                       Layout.topMargin: 16
                       spacing: 0
    
    
                       Label {
                          text: taskCard.stage
                          font.pixelSize: styles.f_xs
                          font.weight: Font.Medium
                          color: styles.c_ag_green
                          Layout.fillWidth: true
                          Layout.alignment: Qt.AlignLeft
                       }
    
                       Rectangle {
                           id: taskCardTimingLayout
                           Layout.alignment: Qt.AlignRight
                           Layout.fillWidth: true
                           height: taskCardTimingLayout.childrenRect.height
                           color: styles.c_secondary
    
                           Label {
                              id: taskCardTimingEstimate
                              text: taskCard.estimate + "h / "
                              font.pixelSize: styles.f_xs
                              font.weight: Font.Normal
                              color: styles.c_ag_green
                              anchors {
                                  right: taskCardTimingRemaining.left
                              }
                           }
    
                           Label {
                              id: taskCardTimingRemaining
                              text: taskCard.elapsed + "h"
                              font.pixelSize: styles.f_xs
                              font.weight: Font.Normal
                              color: styles.c_ag_blue
                              anchors {
                                  right: parent.right
                              }
                           }
    
                       }
    
    
    
    
                   }
    
                   RowLayout {
                       id: taskTitleRowLayout
                       Layout.fillWidth: true
                       spacing: 0
    
                       Label {
                           id: taskCardName
                           text: taskCard.name
                           color: styles.c_primary
                           minimumPixelSize: styles.f_m
                           font.pixelSize: styles.f_m
                           font.weight: Font.Bold
                           wrapMode: Text.WordWrap
                           Layout.fillWidth: true
                           Layout.alignment: Qt.AlignLeft
                       }
    
                       Button {
                           width: 40
                           Layout.alignment: Qt.AlignRight
                           id: taskCardTimerButton
                           background: Rectangle {
                               color: 'transparent'
                           }
                           icon.source: './images/IconPlayOutline.svg'
                           icon.name: 'IconPlayOutline'
                           icon.color: styles.c_ag_green
                       }
                   }
    
                   RowLayout {
                       id: taskCardReferenceLayout
                       Layout.fillWidth: true
                       Layout.bottomMargin: taskCard.isSelected ? 0 : 16
                       spacing: 0
    
                       Label {
                          id: taskCardReference
                          text: taskCard.reference
                          font.pixelSize: styles.f_s
                          font.weight: Font.Medium
                          color: styles.c_accent_1
                          wrapMode: Text.WordWrap
                          Layout.fillWidth: true
                       }
                   }
    
    
    
                   RowLayout {
                       id: taskCardControlsLayout
                       Layout.fillWidth: true
                       Layout.bottomMargin: taskCard.isSelected ? 16 : 0
                       spacing: 0
                       visible: taskCard.isSelected
    
                       ColumnLayout {
                           id: taskCardControlMenuInnerLayout
                           Layout.fillWidth: true
                           spacing: 0
    
                           RowLayout {
                               id: taskCardControlMenuTabs
                               Layout.fillWidth: true
                               spacing: 0
    
    
                               Button {
                                   id: taskCardControlMenuTabListChooser
                                   Layout.fillWidth: true
                                   Layout.preferredHeight: 20
                                   background: Rectangle {
                                       color: styles.c_secondary
                                   }
                                   contentItem: Text {
                                       text:  qsTr("LIST")
                                       color: selectedMenuType == 1 ? styles.c_ag_green : styles.c_accent_1
                                       font.pixelSize: styles.f_s
                                       font.weight: selectedMenuType == 1 ? Font.Bold : Font.Normal
                                       horizontalAlignment: Text.AlignHCenter
                                       verticalAlignment: Text.AlignVCenter
                                   }
                                   onClicked: selectedMenuType = 1
                               }
    
                               Button {
                                   id: taskCardControlMenuTabAssigneeChooser
                                   Layout.fillWidth: true
                                   Layout.preferredHeight: 20
                                   background: Rectangle {
                                       color: styles.c_secondary
                                   }
                                   contentItem: Text {
                                       text:  qsTr("ASSIGNMENT")
                                       color: selectedMenuType == 2 ? styles.c_ag_green : styles.c_accent_1
                                       font.pixelSize: styles.f_s
                                       font.weight: selectedMenuType == 2 ? Font.Bold : Font.Normal
                                       horizontalAlignment: Text.AlignHCenter
                                       verticalAlignment: Text.AlignVCenter
                                   }
                                   onClicked: selectedMenuType = 2
                               }
    
                               Button {
                                   id: taskCardControlMenuTabEstimateProvider
                                   Layout.fillWidth: true
                                   Layout.preferredHeight: 20
                                   background: Rectangle {
                                       color: styles.c_secondary
                                   }
                                   contentItem: Text {
                                       text:  qsTr("ESTIMATE")
                                       color: selectedMenuType == 3 ? styles.c_ag_green : styles.c_accent_1
                                       font.pixelSize: styles.f_s
                                       font.weight: selectedMenuType == 3 ? Font.Bold : Font.Normal
                                       horizontalAlignment: Text.AlignHCenter
                                       verticalAlignment: Text.AlignVCenter
                                   }
                                   onClicked: selectedMenuType = 3
                               }
                           }
    
                           RowLayout {
                               id: taskControlsControlContainer
                               Layout.fillWidth: true
                               spacing: 0
    
                               Rectangle {
                                    id: taskCardControlsListChooser
                                    visible: selectedMenuType == 1
                                    Layout.fillWidth: true
                                    height: taskCardControlsListChooserLayout.height
                                    color: styles.c_secondary
                                    anchors.margins: 4
    
                                    RowLayout {
                                        id: taskCardControlsListChooserLayout
                                        anchors.left: parent.left
                                        anchors.right: parent.right
                                        spacing: 0
    
                                        Button {
                                            icon.height: 14
                                            icon.width: 14
                                            icon.source: './images/IconBacklog.svg'
                                            icon.name: 'IconBacklog'
                                            icon.color: taskCard.listType == 'BACKLOG' ? styles.c_ag_green : styles.c_accent_1
                                            onClicked: taskCard.listType = 'BACKLOG'
                                            background: Rectangle {
                                                color: styles.c_secondary
                                                anchors.fill: parent
                                            }
                                            Layout.fillWidth: true
    
                                        }
                                        Button {
                                            icon.height: 14
                                            icon.width: 14
                                            icon.source: './images/IconPlay.svg'
                                            icon.name: 'IconThisWeek'
                                            icon.color: taskCard.listType == 'THIS' ? styles.c_ag_green : styles.c_accent_1
                                            onClicked: taskCard.listType = 'THIS'
                                            background: Rectangle {
                                                color: styles.c_secondary
                                                anchors.fill: parent
                                            }
                                            Layout.fillWidth: true
    
                                        }
                                        Button {
                                            icon.height: 14
                                            icon.width: 14
                                            icon.source: './images/IconNext.svg'
                                            icon.name: 'IconNextWeek'
                                            icon.color: taskCard.listType == 'NEXT' ? styles.c_ag_green : styles.c_accent_1
                                            onClicked: taskCard.listType = 'NEXT'
                                            background: Rectangle {
                                                color: styles.c_secondary
                                                anchors.fill: parent
                                            }
                                            Layout.fillWidth: true
                                        }
                                        Button {
                                            icon.height: 14
                                            icon.width: 14
                                            icon.source: './images/IconSnooze.svg'
                                            icon.name: 'IconLater'
                                            icon.color: taskCard.listType == 'LATER' ? styles.c_ag_green : styles.c_accent_1
                                            onClicked: taskCard.listType = 'LATER'
                                            background: Rectangle {
                                                color: styles.c_secondary
                                                anchors.fill: parent
                                            }
                                            Layout.fillWidth: true
                                        }
                                    }
                               }
    
                               Rectangle {
                                    id: taskCardControlsAssigneesChooser
                                    visible: selectedMenuType == 2
                               }
    
                               Rectangle {
                                    id: taskCardControlsEstimateProvider
                                    visible: selectedMenuType == 3
                               }
                           }
                       }
                   }
    
    
    
    
                }
    
    
            }
        }
    
    
    
    
    
    
    }
    

Log in to reply