// The dial. // // A hairline ring with one tick per minute inside it: the ticks light up as the // minutes are spent, so the dial reads as "how much is left" at a glance and as // "how long was this?" on a second look. The time is the hero, set in the // display serif. import QtQuick import QtQuick.Controls import ".." Item { id: dial property real progress: 0 // 0..1 elapsed property string clockText: "00:00" property string label: "" property string sublabel: "" property color accent: Theme.work property bool paused: false property bool idle: false property int minutes: 25 implicitWidth: 280 implicitHeight: 280 onProgressChanged: canvas.requestPaint() onAccentChanged: canvas.requestPaint() onPausedChanged: canvas.requestPaint() onIdleChanged: canvas.requestPaint() Canvas { id: canvas anchors.fill: parent antialiasing: true onPaint: { var ctx = getContext("2d") ctx.reset() var c = Math.min(width, height) / 2 var radius = c - 26 // The ring. ctx.lineWidth = 1.5 ctx.strokeStyle = Theme.rule ctx.beginPath() ctx.arc(c, c, radius, 0, Math.PI * 2) ctx.stroke() // Minute ticks, inside the ring. var count = dial.minutes <= 60 ? Math.max(1, dial.minutes) : Math.round(dial.minutes / 5) var spent = Math.floor(count * Math.max(0, Math.min(1, dial.progress))) for (var i = 0; i < count; i++) { var angle = (i / count) * Math.PI * 2 - Math.PI / 2 var major = i % 5 === 0 var outer = radius - 7 var inner = outer - (major ? 9 : 5) ctx.beginPath() ctx.lineWidth = major ? 1.4 : 1 ctx.strokeStyle = (i < spent && !dial.idle) ? dial.accent : Theme.rule ctx.moveTo(c + inner * Math.cos(angle), c + inner * Math.sin(angle)) ctx.lineTo(c + outer * Math.cos(angle), c + outer * Math.sin(angle)) ctx.stroke() } // The remaining arc. if (!dial.idle) { var remaining = 1 - Math.max(0, Math.min(1, dial.progress)) if (remaining > 0) { ctx.beginPath() ctx.lineWidth = 2.5 ctx.lineCap = "round" ctx.globalAlpha = dial.paused ? 0.4 : 1 ctx.strokeStyle = dial.accent ctx.arc(c, c, radius, -Math.PI / 2, -Math.PI / 2 + remaining * Math.PI * 2) ctx.stroke() } } } } Column { anchors.centerIn: parent spacing: 6 width: parent.width * 0.7 Label { width: parent.width text: dial.label.toUpperCase() color: dial.idle ? Theme.muted : dial.accent font.pixelSize: 10 font.letterSpacing: 1.4 font.weight: Font.DemiBold font.family: Theme.sansFamily horizontalAlignment: Text.AlignHCenter } Label { width: parent.width text: dial.clockText color: Theme.ink opacity: dial.paused ? 0.55 : 1 font.pixelSize: Math.round(dial.width * 0.21) font.family: Theme.displayFamily font.letterSpacing: -1.5 horizontalAlignment: Text.AlignHCenter Behavior on opacity { NumberAnimation { duration: 180 } } } Label { width: parent.width visible: dial.sublabel !== "" text: dial.sublabel color: Theme.muted font.pixelSize: 12 font.family: Theme.sansFamily horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight maximumLineCount: 2 wrapMode: Text.WordWrap } } }