My application is made using QML+JS and I am looking to create a circular progress bar widget. I can create the circle using a QML Rectangle and settings its radius equal to its
The best way would be to use PNG file image. Because it runs faster than pure qml, particulary if you use gradient. If you want pure qml only, I didn't find anyway except if you add a custom C++ module to youproject. See http://qt-project.org/doc/qt-4.8/qml-extending.html
I've implemented a basic circular progress using a Canvas.
import QtQml 2.2
import QtQuick 2.0
// draws two arcs (portion of a circle)
// fills the circle with a lighter secondary color
// when pressed
Canvas {
id: canvas
width: 240
height: 240
antialiasing: true
property color primaryColor: "orange"
property color secondaryColor: "lightblue"
property real centerWidth: width / 2
property real centerHeight: height / 2
property real radius: Math.min(canvas.width, canvas.height) / 2
property real minimumValue: 0
property real maximumValue: 100
property real currentValue: 33
// this is the angle that splits the circle in two arcs
// first arc is drawn from 0 radians to angle radians
// second arc is angle radians to 2*PI radians
property real angle: (currentValue - minimumValue) / (maximumValue - minimumValue) * 2 * Math.PI
// we want both circle to start / end at 12 o'clock
// without this offset we would start / end at 9 o'clock
property real angleOffset: -Math.PI / 2
property string text: "Text"
signal clicked()
onPrimaryColorChanged: requestPaint()
onSecondaryColorChanged: requestPaint()
onMinimumValueChanged: requestPaint()
onMaximumValueChanged: requestPaint()
onCurrentValueChanged: requestPaint()
onPaint: {
var ctx = getContext("2d");
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
// fills the mouse area when pressed
// the fill color is a lighter version of the
// secondary color
if (mouseArea.pressed) {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.fillStyle = Qt.lighter(canvas.secondaryColor, 1.25);
ctx.arc(canvas.centerWidth,
canvas.centerHeight,
canvas.radius,
0,
2*Math.PI);
ctx.fill();
}
// First, thinner arc
// From angle to 2*PI
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = primaryColor;
ctx.arc(canvas.centerWidth,
canvas.centerHeight,
canvas.radius,
angleOffset + canvas.angle,
angleOffset + 2*Math.PI);
ctx.stroke();
// Second, thicker arc
// From 0 to angle
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = canvas.secondaryColor;
ctx.arc(canvas.centerWidth,
canvas.centerHeight,
canvas.radius,
canvas.angleOffset,
canvas.angleOffset + canvas.angle);
ctx.stroke();
ctx.restore();
}
Text {
anchors.centerIn: parent
text: canvas.text
color: canvas.primaryColor
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: canvas.clicked()
onPressedChanged: canvas.requestPaint()
}
}
I found a kinda elegant solution in plain QML which can be also used for styling a regular QtQuick ProgressBar component. The idea behind this is to use a ConicalGradient
on a border-only Rectangle
.
Here is the code:
import QtQuick 2.3
import QtQuick.Controls.Styles 1.2
import QtGraphicalEffects 1.0
ProgressBarStyle
{
panel : Rectangle
{
color: "transparent"
implicitWidth: 80
implicitHeight: implicitWidth
Rectangle
{
id: outerRing
z: 0
anchors.fill: parent
radius: Math.max(width, height) / 2
color: "transparent"
border.color: "gray"
order.width: 8
}
Rectangle
{
id: innerRing
z: 1
anchors.fill: parent
anchors.margins: (outerRing.border.width - border.width) / 2
radius: outerRing.radius
color: "transparent"
border.color: "darkgray"
border.width: 4
ConicalGradient
{
source: innerRing
anchors.fill: parent
gradient: Gradient
{
GradientStop { position: 0.00; color: "white" }
GradientStop { position: control.value; color: "white" }
GradientStop { position: control.value + 0.01; color: "transparent" }
GradientStop { position: 1.00; color: "transparent" }
}
}
}
Text
{
id: progressLabel
anchors.centerIn: parent
color: "black"
text: (control.value * 100).toFixed() + "%"
}
}
}
I know the solution using rotation
property. See example
https://gitorious.org/apps-4-me/staq-me/source/fd20fe5b6fec053f364219842905e2afc5cfdc9d:ui.qml#L172
I came across an example by Diego Dotta on GitHub using two rotating circles that seems to work nicely for this use case. It involves setting the duration of a PropertyAnimation. So while this works well for a timer that you can set, it would need a different approach for something you didn't know how long it would take. This is tweaked a bit and ported to QtQuick 2.0:
main.qml:
import QtQuick 2.0
import Ubuntu.Components 0.1
Rectangle {
width: units.gu(50)
height: units.gu(50)
property int seconds : 0
LoadCircle {
id: circle
anchors.centerIn: parent
loadtimer: 10*1000 // 10 seconds
Component.onCompleted: start();
onFinishedChanged: {
timer.stop();
borderColor = "green"
}
}
Rectangle {
id : theTimer
anchors.centerIn: parent
width : units.gu(10) ; height: units.gu(10)
Label {
text: seconds
font.bold: true
fontSize: "x-large"
anchors.centerIn: parent
}
}
Timer {
id: timer
interval: 1000; running: true; repeat: true;
onTriggered: seconds++;
}
}
LoadCircle.qml:
import QtQuick 2.0
import Ubuntu.Components 0.1
Row{
id: circle
property int loadtimer: 4000
property color circleColor: "transparent"
property color borderColor: "red"
property int borderWidth: 10
property alias running: initCircle.running
property bool finished: false;
width: units.gu(30)
height: width
function start(){
part1.rotation = 180
part2.rotation = 180
initCircle.start()
}
function stop(){
initCircle.stop()
}
Item{
width: parent.width/2
height: parent.height
clip: true
Item{
id: part1
width: parent.width
height: parent.height
clip: true
rotation: 180
transformOrigin: Item.Right
Rectangle{
width: circle.width-(borderWidth*2)
height: circle.height-(borderWidth*2)
radius: width/2
x:borderWidth
y:borderWidth
color: circleColor
border.color: borderColor
border.width: borderWidth
smooth: true
}
}
}
Item{
width: parent.width/2
height: parent.height
clip: true
Item{
id: part2
width: parent.width
height: parent.height
clip: true
rotation: 180
transformOrigin: Item.Left
Rectangle{
width: circle.width-(borderWidth*2)
height: circle.height-(borderWidth*2)
radius: width/2
x: -width/2
y: borderWidth
color: circleColor
border.color: borderColor
border.width: borderWidth
smooth: true
}
}
}
SequentialAnimation{
id: initCircle
PropertyAnimation{ target: part2; property: "rotation"; to:360; duration:loadtimer/2 }
PropertyAnimation{ target: part1; property: "rotation"; to:360; duration:loadtimer/2 }
ScriptAction { script: finished = true; }
}
}
Just use EEIoT (https://github.com/IndeemaSoftware/EEIoT) Knob component. Change parameters fromAngle: 0 and toAngle: Math.PI * 2. Also reverse: true if you need progress to be reversed
Knob {
id: knob
x: 0
y: 83
width: 100
height: 100
from:0
to: 100
fromAngle: 0
toAngle: Math.PI*2
reverse: false
}