In my previous blog post I have shown how to create a RadialBar using QQuickPaintedItem. Now I have rewritten the RadialBar using the Qt Quick Shapes module which was available in Qt since the version Qt 5.10.

Here in this post I will explain how to use Qt Quick Shapes to create the Radial Bar Shape and with the exact set of properties which were available in the previous component.

If you look at the documentation of the Shapes module here you will find that there are few QML types available in which the main types which we are going to use are Shape & ShapePath.

Shape is a basic type which renders a path either by generating geometry via QPainterPath and manual triangulation or by using a GPU vendor extension like GL_NV_path_rendering. Unlike the QtQuickPaintedItem the path never gets rasterized in software which avoid the performance penalty for texture uploads.

To make sure the feature parity with the previous RadialBar component I have created all the properties (except the property size) same as that like below.

import QtQuick 2.15
import QtQuick.Shapes 1.15

Item {
    id: control

    implicitWidth: 200
    implicitHeight: 200

    enum DialType {
        FullDial,
        MinToMax,
        NoDial
    }

    property real startAngle: 0
    property real spanAngle: 360
    property real minValue: 0
    property real maxValue: 100
    property real value: 0
    property int dialWidth: 15

    property color backgroundColor: "transparent"
    property color dialColor: "#FF505050"
    property color progressColor: "#FFA51BAB"

    property int penStyle: Qt.RoundCap
    property int dialType: RadialBarShape.DialType.FullDial
}

I have also moved some of the properties inside a QtObject to keep it as private and to be used only inside the component like below.

QtObject {
        id: internals

        property bool isFullDial: control.dialType === RadialBarShape.DialType.FullDial
        property bool isNoDial: control.dialType === RadialBarShape.DialType.NoDial

        property real baseRadius: Math.min(control.width / 2, control.height / 2)
        property real radiusOffset: internals.isFullDial ? control.dialWidth / 2
                                                         : control.dialWidth / 2
        property real actualSpanAngle: internals.isFullDial ? 360 : control.spanAngle

        property color transparentColor: "transparent"
        property color dialColor: internals.isNoDial ? internals.transparentColor
                                                     : control.dialColor
    }

And the main part of the component is the actual Shape which consists of three ShapePath each for the Background, Dial & the Progress.The complete code of the Shape is given below.

Shape {
        id: shape
        anchors.fill: parent
        layer.enabled: true
        layer.samples: 8

        ShapePath {
            id: pathBackground
            strokeColor: internals.transparentColor
            fillColor: control.backgroundColor
            capStyle: control.penStyle

            PathAngleArc {
                radiusX: internals.baseRadius - control.dialWidth
                radiusY: internals.baseRadius - control.dialWidth
                centerX: control.width / 2
                centerY: control.height / 2
                startAngle: 0
                sweepAngle: 360
            }
        }

        ShapePath {
            id: pathDial
            strokeColor: control.dialColor
            fillColor: internals.transparentColor
            strokeWidth: control.dialWidth
            capStyle: control.penStyle

            PathAngleArc {
                radiusX: internals.baseRadius - internals.radiusOffset
                radiusY: internals.baseRadius - internals.radiusOffset
                centerX: control.width / 2
                centerY: control.height / 2
                startAngle: control.startAngle - 90
                sweepAngle: internals.actualSpanAngle
            }
        }

        ShapePath {
            id: pathProgress
            strokeColor: control.progressColor
            fillColor: internals.transparentColor
            strokeWidth: control.dialWidth
            capStyle: control.penStyle

            PathAngleArc {
                radiusX: internals.baseRadius - internals.radiusOffset
                radiusY: internals.baseRadius - internals.radiusOffset
                centerX: control.width / 2
                centerY: control.height / 2
                startAngle: control.startAngle - 90
                sweepAngle: (internals.actualSpanAngle / control.maxValue * control.value)
            }
        }
    }

And the example usage can be found below.

QtObject {
        id: feeder

        property real value: 0

        SequentialAnimation on value {
            loops: Animation.Infinite
            NumberAnimation { to: 100; duration: 2000 }
            NumberAnimation { to: 0; duration: 2000 }
        }
    }

    ColumnLayout {
        anchors.centerIn: parent
        spacing: 10
        RowLayout {
            Layout.fillWidth: true
            spacing: 20
            RadialBarShape {
                value: feeder.value
            }
            RadialBarShape {
                Layout.preferredHeight: 300
                Layout.preferredWidth: 300
                progressColor: "#e6436d"
                value: feeder.value
                spanAngle: 270
                dialType: RadialBarShape.DialType.FullDial
                backgroundColor: "#6272a4"
                penStyle: Qt.FlatCap
                dialColor: "transparent"
            }
            RadialBarShape {
                progressColor: "#8be9fd"
                value: feeder.value
                spanAngle: 270
                dialType: RadialBarShape.DialType.NoDial
                dialColor: Qt.darker("#8be9fd", 3.8)
                penStyle: Qt.SquareCap
                dialWidth: 10
            }
            RadialBarShape {
                id: newone
                progressColor: "#50fa7b"
                value: feeder.value
                startAngle: 90
                spanAngle: 270
                dialType: RadialBarShape.DialType.MinToMax
                backgroundColor: "#6272a4"
                dialWidth: 5
            }
        }
    }

Source Code

https://github.com/arunpkqt/RadialBarDemo/blob/master/RadialBarShape.qml

Screenshot

8 thoughts on “QML RadialBarShape

      1. Here is the sample code with a Rectangle pointer following the Progress with SpringAnimation

        ColumnLayout {
        spacing: 20
        anchors.centerIn: parent

        RadialBarShape {
        id: progress
        value: slider.value
        Layout.alignment: Qt.AlignHCenter

        Item {
        id: triangleContainer
        width: 20
        height: parent.height / 2 + progress.dialWidth
        x: parent.width / 2 – width / 2
        y: -progress.dialWidth – 10

        Shape {
        id: triangle
        width: parent.width
        height: width

        ShapePath {
        startX: triangle.width / 2
        startY: 0
        fillColor: “orange”
        strokeColor: “orange”
        PathLine { x: 0; y: 0 }
        PathLine { x: triangle.width; y: 0 }
        PathLine { x: triangle.width/2; y: triangle.height}
        PathLine { x: 0; y: 0}

        }
        }

        transform: Rotation {
        id: minuteRotation
        origin.x: triangleContainer.width / 2
        origin.y: triangleContainer.height + 10
        angle: progress.angle
        Behavior on angle {
        SpringAnimation { spring: 2; damping: 0.2}
        }
        }
        }
        }

        Slider {
        id: slider
        Layout.fillWidth: true
        minimumValue: 0
        maximumValue: 100
        }
        }

        Liked by 1 person

  1. Hi Arun, awesome work!

    I’m trying to add a circular dial to it so I can rotate directly from the dial. I’ve worked on that “customized dial” from Qt site and could take the handle out of the circle. The only thing that I wasn’t able to do is turning the dial into a 360° (with the initial point at 12 o’clock). I’d appreciate if you could help me. Thanks.

    That’s my circular dial code:
    ……….
    import QtQuick 2.12
    import QtQuick.Controls 2.12

    Dial {
    id: control
    background: Rectangle {
    x: control.width / 2 – width / 2
    y: control.height / 2 – height / 2
    width: Math.max(0, Math.min(control.width, control.height))
    height: width
    color: “transparent”
    radius: width / 2
    border.color: control.pressed ? “#17a81a” : “#21be2b”
    opacity: control.enabled ? 1 : 0.3
    }

    handle: Rectangle {
    id: handleItem
    x: control.background.x + control.background.width / 2 – width / 2
    y: control.background.y + control.background.height / 2 – height / 2
    width: 16
    height: 16
    color: control.pressed ? “#17a81a” : “#21be2b”
    radius: 8
    antialiasing: true
    opacity: control.enabled ? 1 : 0.3
    transform: [
    Translate {
    y: -Math.min(control.background.width, control.background.height) * 0.65 + handleItem.height / 2
    },
    Rotation {
    angle: control.angle
    origin.x: handleItem.width / 2
    origin.y: handleItem.height / 2
    }
    ]
    }
    }

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s