Customizing Qt Labs Controls

Qt Labs Controls consist of a hierarchy (tree) of items. In order to provide a custom look and feel, the default QML implementation of each item can be replaced with a custom one. The following snippets present the default implementations of various items. These can be used as a starting point to implement a custom look and feel.

Customizing BusyIndicator

BusyIndicator consists of two visual items: background and contentItem.

Background

BusyIndicator has no background item by default.

Content item

contentItem: BusyRing {
    id: ring
    implicitWidth: 48
    implicitHeight: 48
    opacity: control.running ? 1 : 0

    Behavior on opacity { OpacityAnimator { duration: 250 } }

    BusyRingAnimator {
        target: ring
        running: control.visible && control.running
    }
}

Customizing Button

Button consists of two visual items: background and label.

Background

background: Rectangle {
    implicitWidth: 100
    implicitHeight: 40
    opacity: enabled ? 1 : 0.3
    color: control.pressed ? (control.highlighted ? "#585a5c" : "#e4e4e4") : (control.highlighted ? "#353637" : "#f6f6f6")
    border.color: control.pressed ? "#26282a" : "#353637"
}

Label

label: Text {
    x: control.leftPadding
    y: control.topPadding
    width: control.availableWidth
    height: control.availableHeight
    text: control.text
    font: control.font
    opacity: enabled || highlighted ? 1 : 0.3
    color: control.highlighted ? "#ffffff" : (control.pressed ? "#26282a" : "#353637")
    horizontalAlignment: Text.AlignHCenter
    verticalAlignment: Text.AlignVCenter
    elide: Text.ElideRight
}

Customizing CheckBox

CheckBox consists of three visual items: background, label and indicator.

Background

CheckBox has no background item by default.

Label

label: Text {
    x: control.mirrored ? control.leftPadding : (indicator.x + indicator.width + control.spacing)
    y: control.topPadding
    width: control.availableWidth - indicator.width - control.spacing
    height: control.availableHeight

    text: control.text
    font: control.font
    color: control.pressed ? "#26282a" : "#353637"
    elide: Text.ElideRight
    visible: control.text
    horizontalAlignment: Text.AlignLeft
    verticalAlignment: Text.AlignVCenter
}

Indicator

indicator: Rectangle {
    implicitWidth: 28
    implicitHeight: 28
    x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
    y: control.topPadding + (control.availableHeight - height) / 2

    color: control.enabled ? (control.pressed ? "#e4e4e4" : "#f6f6f6") : "#353637"
    border.color: control.enabled ? (control.pressed ? "#26282a" : "#353637") : "transparent"

    Image {
        x: (parent.width - width) / 2
        y: (parent.height - height) / 2
        source: "qrc:/qt-project.org/imports/Qt/labs/controls/images/check.png"
        visible: control.checkState === Qt.Checked
    }

    Rectangle {
        x: (parent.width - width) / 2
        y: (parent.height - height) / 2
        width: 16
        height: 3
        color: "#353637"
        visible: control.checkState === Qt.PartiallyChecked
    }
}

Customizing ComboBox

ComboBox consists of background, content item, popup, and delegate.

Background

background: Item {
    implicitWidth: 120
    implicitHeight: 40

    Rectangle {
        width: parent.width
        height: parent.height
        opacity: control.enabled ? 1.0 : 0.2
        color: control.pressed || popup.visible ? "#585A5C" : "#353637"
    }

    Image {
        x: parent.width - width - control.rightPadding
        y: (parent.height - height) / 2
        source: "qrc:/qt-project.org/imports/Qt/labs/controls/images/drop-indicator.png"
    }
}

Content item

contentItem: Text {
    text: control.displayText
    font: control.font
    color: "#ffffff"
    horizontalAlignment: Text.AlignLeft
    verticalAlignment: Text.AlignVCenter
    elide: Text.ElideRight
    rightPadding: 18 + control.spacing
}

Popup

popup: T.Popup {
    y: control.height - 1
    implicitWidth: control.width
    implicitHeight: listview.contentHeight
    topMargin: 6
    bottomMargin: 6

    contentItem: ListView {
        id: listview
        clip: true
        model: control.popup.visible ? control.delegateModel : null
        currentIndex: control.highlightedIndex

        Rectangle {
            z: 10
            parent: listview
            width: listview.width
            height: listview.height
            border.color: "#353637"
            color: "transparent"
        }

        T.ScrollIndicator.vertical: ScrollIndicator { }
    }

    background: Rectangle { }
}

Delegate

delegate: ItemDelegate {
    width: control.width
    text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] : model[control.textRole]) : modelData
    checkable: true
    autoExclusive: true
    checked: control.currentIndex === index
    highlighted: control.highlightedIndex === index
    pressed: highlighted && control.pressed
}

Customizing Dial

Dial consists of two visual items: background and handle.

Background

background: Rectangle {
    x: control.width / 2 - width / 2
    y: control.height / 2 - height / 2
    width: Math.max(64, Math.min(control.width, control.height))
    height: Math.max(64, Math.min(control.width, control.height))
    radius: width / 2
    border.color: "#353637"
}

Indicator

handle: Image {
    id: handleItem
    x: background.x + background.width / 2 - handle.width / 2
    y: background.y + background.height / 2 - handle.height / 2
    width: 14
    height: 10
    source: "qrc:/qt-project.org/imports/Qt/labs/controls/images/dial-indicator.png"
    antialiasing: true
    transform: [
        Translate {
            y: -background.height * 0.4 + handle.height / 2
        },
        Rotation {
            angle: control.angle
            origin.x: handle.width / 2
            origin.y: handle.height / 2
        }
    ]
}

Customizing Drawer

Drawer can have a visual background item. The navigation is implemented by the content item.

Background

Content item

Drawer has no content item by default.

Customizing Frame

Frame consists of two visual items: background and frame.

Background

Frame has no background item by default.

Frame

frame: Rectangle {
    width: parent.width
    height: parent.height

    color: "transparent"
    border.color: "#bdbebf"
}

Customizing GroupBox

GroupBox consists of three visual items: background, frame and label.

Background

GroupBox has no background item by default.

Frame

frame: Rectangle {
    y: control.topPadding - control.padding
    width: parent.width
    height: parent.height - control.topPadding + control.padding

    color: "transparent"
    border.color: "#bdbebf"
}

Label

label: Text {
    x: control.leftPadding
    width: control.availableWidth

    text: control.title
    font: control.font
    color: control.enabled ? "#353637" : "#bdbebf"
    elide: Text.ElideRight
    horizontalAlignment: Text.AlignLeft
    verticalAlignment: Text.AlignVCenter
}

Customizing ItemDelegate

ItemDelegate consists of three visual items: background, label and indicator. The indicator is only visible for checkable items.

Background

background: Rectangle {
    implicitWidth: 100
    implicitHeight: 40
    visible: control.pressed || control.highlighted
    color: control.pressed ? "#bdbebf" : "#eeeeee"
}

Label

label: Text {
    x: control.mirrored ? control.width - width - control.rightPadding : control.leftPadding
    y: control.topPadding
    width: control.availableWidth - (control.checkable ? indicator.width + control.spacing : 0)
    height: control.availableHeight

    text: control.text
    font: control.font
    color: control.enabled ? "#26282a" : "#bdbebf"
    elide: Text.ElideRight
    visible: control.text
    horizontalAlignment: Text.AlignLeft
    verticalAlignment: Text.AlignVCenter
}

Indicator

indicator: Image {
    x: control.mirrored ? control.leftPadding : control.width - width - control.rightPadding
    y: control.topPadding + (control.availableHeight - height) / 2

    visible: control.checked
    source: control.checkable ? "qrc:/qt-project.org/imports/Qt/labs/controls/images/check.png" : ""
}

Customizing Label

Label can have a visual background item.

Background

Label has no background item by default.

Customizing Menu

Menu consists of a contentItem.

Content item

contentItem: ListView {
    implicitHeight: contentHeight
    model: control.contentModel
    // TODO: improve this?
    interactive: ApplicationWindow.window ? contentHeight > ApplicationWindow.window.height : false
    clip: true
    keyNavigationWraps: false
    currentIndex: -1

    ScrollIndicator.vertical: ScrollIndicator {}
}

Customizing MenuItem

MenuItem can be customized in the same manner as Button.

Customizing PageIndicator

TODO

Customizing Pane

Pane consists of a background.

Background

background: Rectangle {
    color: "#ffffff"
}

Customizing ProgressBar

ProgressBar consists of two visual items: background and indicator.

Background

background: Rectangle {
    implicitWidth: 200
    implicitHeight: 6
    x: control.leftPadding
    y: control.topPadding + (control.availableHeight - height) / 2
    width: control.availableWidth
    height: 6

    color: "#e4e4e4"
}

Indicator

indicator: ProgressStrip {
    id: strip
    x: control.leftPadding
    y: control.topPadding + (control.availableHeight - height) / 2
    width: control.availableWidth
    height: 6
    scale: control.mirrored ? -1 : 1
    progress: control.position
    indeterminate: control.indeterminate

    ProgressStripAnimator {
        target: strip
        running: control.visible && control.indeterminate
    }
}

Customizing RadioButton

RadioButton consists of three visual items: background, label and indicator.

Background

RadioButton has no background item by default.

Label

label: Text {
    x: control.mirrored ? control.leftPadding : (indicator.x + indicator.width + control.spacing)
    y: control.topPadding
    width: control.availableWidth - indicator.width - control.spacing
    height: control.availableHeight

    text: control.text
    font: control.font
    color: control.pressed ? "#26282a" : "#353637"
    elide: Text.ElideRight
    visible: control.text
    horizontalAlignment: Text.AlignLeft
    verticalAlignment: Text.AlignVCenter
}

Indicator

indicator: Rectangle {
    implicitWidth: 28
    implicitHeight: 28
    x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
    y: control.topPadding + (control.availableHeight - height) / 2

    radius: width / 2
    border.width: 1
    border.color: (control.pressed ? "#26282a" : "#353637")
    color: control.pressed ? "#e4e4e4" : "#f6f6f6"

    Rectangle {
        x: (parent.width - width) / 2
        y: (parent.height - height) / 2
        width: 20
        height: 20
        radius: width / 2
        color: control.pressed ? "#26282a" : "#353637"
        visible: control.checked
    }
}

Customizing RangeSlider

RangeSlider consists of four visual items: background, track, first.handle and second.handle.

Background

RangeSlider has no background item by default.

Track

track: Rectangle {
    x: control.leftPadding + (horizontal ? 0 : (control.availableWidth - width) / 2)
    y: control.topPadding + (horizontal ? (control.availableHeight - height) / 2 : 0)
    implicitWidth: horizontal ? 200 : 6
    implicitHeight: horizontal ? 6 : 200
    width: horizontal ? control.availableWidth : implicitWidth
    height: horizontal ? implicitHeight : control.availableHeight
    radius: 3
    border.color: "#353637"
    color: "#ffffff"
    scale: horizontal && control.mirrored ? -1 : 1

    readonly property bool horizontal: control.orientation === Qt.Horizontal
}

First Handle

first.handle: Rectangle {
    x: control.leftPadding + (horizontal ? control.first.visualPosition * (control.availableWidth - width) : (control.availableWidth - width) / 2)
    y: control.topPadding + (horizontal ? (control.availableHeight - height) / 2 : control.first.visualPosition * (control.availableHeight - height))
    implicitWidth: 28
    implicitHeight: 28
    radius: width / 2
    border.width: activeFocus ? 2 : 1
    border.color: "#353637"
    color: first.pressed ? "#bdbebf" : "#ffffff"

    readonly property bool horizontal: control.orientation === Qt.Horizontal
}

Second Handle

second.handle: Rectangle {
    x: control.leftPadding + (horizontal ? control.second.visualPosition * (control.availableWidth - width) : (control.availableWidth - width) / 2)
    y: control.topPadding + (horizontal ? (control.availableHeight - height) / 2 : control.second.visualPosition * (control.availableHeight - height))
    implicitWidth: 28
    implicitHeight: 28
    radius: width / 2
    border.width: activeFocus ? 2 : 1
    border.color: "#353637"
    color: second.pressed ? "#bdbebf" : "#ffffff"

    readonly property bool horizontal: control.orientation === Qt.Horizontal
}

Customizing ScrollBar

ScrollBar consists of two visual items: background and handle.

Background

ScrollBar has no background item by default.

Handle

handle: Rectangle {
    id: handle

    implicitWidth: 6
    implicitHeight: 6

    radius: width / 2
    color: control.pressed ? "#28282a" : "#bdbebf"
    visible: control.size < 1.0
    opacity: 0.0

    readonly property bool horizontal: control.orientation === Qt.Horizontal
    x: control.leftPadding + (horizontal ? control.position * control.availableWidth : 0)
    y: control.topPadding + (horizontal ? 0 : control.position * control.availableHeight)
    width: horizontal ? control.size * control.availableWidth : implicitWidth
    height: horizontal ? implicitHeight : control.size * control.availableHeight

    states: State {
        name: "active"
        when: control.active
        PropertyChanges { target: handle; opacity: 0.75 }
    }

    transitions: Transition {
        from: "active"
        SequentialAnimation {
            PauseAnimation { duration: 450 }
            NumberAnimation { target: handle; duration: 200; property: "opacity"; to: 0.0 }
        }
    }
}

Customizing ScrollIndicator

ScrollIndicator consists of two visual items: background and indicator.

Background

ScrollIndicator has no background item by default.

Indicator

indicator: Rectangle {
    id: indicator

    implicitWidth: 2
    implicitHeight: 2

    color: "#bdbebf"
    visible: control.size < 1.0
    opacity: 0.0

    readonly property bool horizontal: control.orientation === Qt.Horizontal
    x: control.leftPadding + (horizontal ? control.position * control.width : 0)
    y: control.topPadding + (horizontal ? 0 : control.position * control.height)
    width: horizontal ? control.size * control.availableWidth : implicitWidth
    height: horizontal ? implicitHeight : control.size * control.availableHeight

    states: State {
        name: "active"
        when: control.active
        PropertyChanges { target: indicator; opacity: 0.75 }
    }

    transitions: [
        Transition {
            from: "active"
            SequentialAnimation {
                PauseAnimation { duration: 450 }
                NumberAnimation { target: indicator; duration: 200; property: "opacity"; to: 0.0 }
            }
        }
    ]
}

Customizing Slider

Slider consists of three visual items: background, track and handle.

Background

Slider has no background item by default.

Track

track: Rectangle {
    x: control.leftPadding + (horizontal ? 0 : (control.availableWidth - width) / 2)
    y: control.topPadding + (horizontal ? (control.availableHeight - height) / 2 : 0)
    implicitWidth: horizontal ? 200 : 6
    implicitHeight: horizontal ? 6 : 200
    width: horizontal ? control.availableWidth : implicitWidth
    height: horizontal ? implicitHeight : control.availableHeight
    radius: 3
    border.color: "#353637"
    color: "#ffffff"
    scale: horizontal && control.mirrored ? -1 : 1

    readonly property bool horizontal: control.orientation === Qt.Horizontal
}

Handle

handle: Rectangle {
    x: control.leftPadding + (horizontal ? control.visualPosition * (control.availableWidth - width) : (control.availableWidth - width) / 2)
    y: control.topPadding + (horizontal ? (control.availableHeight - height) / 2 : control.visualPosition * (control.availableHeight - height))
    implicitWidth: 28
    implicitHeight: 28
    radius: width / 2
    border.color: "#353637"
    color: control.pressed ? "#bdbebf" : "#f6f6f6"

    readonly property bool horizontal: control.orientation === Qt.Horizontal
}

Customizing SpinBox

SpinBox consists of four visual items: background, contentItem, up indicator, and down indicator.

Background

background: Rectangle {
    implicitWidth: 140
    border.color: "#bdbebf"
}

Content item

contentItem: TextInput {
    text: control.textFromValue(control.value, control.locale)

    font: control.font
    color: "#353637"
    selectionColor: "#fddd5c"
    selectedTextColor: color
    horizontalAlignment: Qt.AlignHCenter
    verticalAlignment: Qt.AlignVCenter

    validator: control.validator
    inputMethodHints: Qt.ImhFormattedNumbersOnly
}

Down indicator

down.indicator: Rectangle {
    x: control.mirrored ? parent.width - width : 0
    height: parent.height
    implicitWidth: 40
    implicitHeight: 40
    color: down.pressed ? "#e4e4e4" : "#f6f6f6"
    border.color: control.enabled ? "#353637" : "#bdbebf"

    Rectangle {
        x: (parent.width - width) / 2
        y: (parent.height - height) / 2
        width: parent.width / 3
        height: 2
        color: control.enabled ? "#353637" : "#bdbebf"
    }
}

Up indicator

up.indicator: Rectangle {
    x: control.mirrored ? 0 : parent.width - width
    height: parent.height
    implicitWidth: 40
    implicitHeight: 40
    color: up.pressed ? "#e4e4e4" : "#f6f6f6"
    border.color: control.enabled ? "#353637" : "#bdbebf"

    Rectangle {
        x: (parent.width - width) / 2
        y: (parent.height - height) / 2
        width: parent.width / 3
        height: 2
        color: control.enabled ? "#353637" : "#bdbebf"
    }
    Rectangle {
        x: (parent.width - width) / 2
        y: (parent.height - height) / 2
        width: 2
        height: parent.width / 3
        color: control.enabled ? "#353637" : "#bdbebf"
    }
}

Customizing StackView

StackView can have a visual background item, and it allows customizing the transitions that are used for push, pop, and replace operations.

Push enter

pushEnter: Transition {
    XAnimator { from: (root.mirrored ? -1 : 1) * root.width; to: 0; duration: 400; easing.type: Easing.OutCubic }
}

Push exit

pushExit: Transition {
    XAnimator { from: 0; to: (root.mirrored ? -1 : 1) * -root.width; duration: 400; easing.type: Easing.OutCubic }
}

Pop enter

popEnter: Transition {
    XAnimator { from: (root.mirrored ? -1 : 1) * -root.width; to: 0; duration: 400; easing.type: Easing.OutCubic }
}

Pop exit

popExit: Transition {
    XAnimator { from: 0; to: (root.mirrored ? -1 : 1) * root.width; duration: 400; easing.type: Easing.OutCubic }
}

Replace enter

replaceEnter: Transition {
    XAnimator { from: (root.mirrored ? -1 : 1) * root.width; to: 0; duration: 400; easing.type: Easing.OutCubic }
}

Replace exit

replaceExit: Transition {
    XAnimator { from: 0; to: (root.mirrored ? -1 : 1) * -root.width; duration: 400; easing.type: Easing.OutCubic }
}

Customizing SwipeView

SwipeView can have a visual background item. The navigation is implemented by the content item.

Background

SwipeView has no background item by default.

Content item

contentItem: ListView {
    model: control.contentModel
    currentIndex: control.currentIndex

    spacing: control.spacing
    orientation: Qt.Horizontal
    snapMode: ListView.SnapOneItem
    boundsBehavior: Flickable.StopAtBounds

    highlightRangeMode: ListView.StrictlyEnforceRange
    preferredHighlightBegin: 0
    preferredHighlightEnd: 0
    highlightMoveDuration: 250
}

Customizing Switch

Switch consists of three visual items: background, label and indicator.

Background

Switch has no background item by default.

Label

label: Text {
    x: control.mirrored ? control.leftPadding : (indicator.x + indicator.width + control.spacing)
    y: control.topPadding
    width: control.availableWidth - indicator.width - control.spacing
    height: control.availableHeight

    text: control.text
    font: control.font
    color: control.enabled ? "#26282a" : "#bdbebf"
    elide: Text.ElideRight
    visible: control.text
    horizontalAlignment: Text.AlignLeft
    verticalAlignment: Text.AlignVCenter
}

Indicator

indicator: Item {
    x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
    y: control.topPadding + (control.availableHeight - height) / 2
    implicitWidth: 56
    implicitHeight: 28

    Rectangle {
        y: parent.height / 2 - height / 2
        width: 56
        height: 16
        radius: 8
        border.width: 1
        color: control.checked ? "#353637" : "transparent"
        border.color: control.checked ? "transparent" : "#353637"
    }

    Rectangle {
        x: Math.max(0, Math.min(parent.width - width, control.visualPosition * parent.width - (width / 2)))
        y: (parent.height - height) / 2
        width: 28
        height: 28
        radius: 16
        color: control.pressed ? "#e4e4e4" : "#f6f6f6"
        border.width: 1
        border.color: control.pressed ? "#26282a" : "#353637"

        Behavior on x {
            enabled: !control.pressed
            SmoothedAnimation { velocity: 200 }
        }
    }
}

Customizing TabBar

TODO

Customizing TabButton

TODO

Customizing TextArea

TODO

Customizing TextField

TextField offers a customizable background item.

Background

background: Rectangle {
    implicitWidth: 200
    implicitHeight: 40
//        border.width: control.activeFocus ? 2 : 1
    color: control.enabled ? "transparent" : "#353637"
    border.color: control.enabled ? "#bdbebf" : "transparent"
}

Customizing ToolBar

ToolBar consists of two visual items: background and frame.

Background

background: Rectangle {
    implicitHeight: 40
    color: "#eeeeee"
}

Frame

ToolBar has no frame item by default.

Customizing ToolButton

ToolButton consists of two visual items: background and label.

Background

background: Rectangle {
    implicitWidth: 40
    implicitHeight: 40

    color: Qt.darker("#33333333", control.enabled && (control.checked || control.highlighted) ? 1.5 : 1.0)
    opacity: control.pressed ? 1.0 : control.enabled && (control.checked || control.highlighted) ? 0.5 : 0
    visible: control.pressed || (control.enabled && (control.checked || control.highlighted))
}

Label

label: Text {
    x: control.leftPadding
    y: control.topPadding
    width: control.availableWidth
    height: control.availableHeight

    text: control.text
    font: control.font
    color: control.enabled ? "#26282a" : "#c2c2c2"
    elide: Text.ElideRight
    horizontalAlignment: Text.AlignHCenter
    verticalAlignment: Text.AlignVCenter
}

Customizing Tumbler

Tumbler consists of three visual items: background, contentItem, and delegate.

Background

Tumbler has no background item by default.

Content Item

contentItem: PathView {
    id: pathView
    model: control.model
    delegate: control.delegate
    clip: true
    pathItemCount: control.visibleItemCount + 1
    preferredHighlightBegin: 0.5
    preferredHighlightEnd: 0.5
    dragMargin: width / 2

    path: Path {
        startX: pathView.width / 2
        startY: -pathView.delegateHeight / 2
        PathLine {
            x: pathView.width / 2
            y: pathView.pathItemCount * pathView.delegateHeight - pathView.delegateHeight / 2
        }
    }

    property real delegateHeight: control.availableHeight / control.visibleItemCount
}

Delegate

delegate: Text {
    id: label
    text: modelData
    color: "#666666"
    font: control.font
    opacity: 0.4 + Math.max(0, 1 - Math.abs(Tumbler.displacement)) * 0.6
    horizontalAlignment: Text.AlignHCenter
    verticalAlignment: Text.AlignVCenter
}

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/archives/qt-5.6/qtlabscontrols-customize.html