refactor: enhance TimerPage layout and functionality; update theme and tokens for improved design consistency
- Refactored TimerPage component to improve layout and user experience. - Updated theme configuration to utilize design tokens for better maintainability. - Introduced new tokens for colors and typography to align with design principles. - Simplified state management and improved timer functionality. - Enhanced accessibility and responsiveness of UI elements.
This commit is contained in:
84 files changed
+4501
-2682
No files matched your search
@@ -1,27 +1,34 @@
|
||||
// An inline warning strip — used for "the backend is gone" and "the helper is
|
||||
// not installed", the two states where the app cannot do its job.
|
||||
// An inline warning strip for the two states where the app cannot do its job:
|
||||
// the backend is unreachable, or the privileged helper is missing.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import ".."
|
||||
|
||||
Rectangle {
|
||||
id: banner
|
||||
property string text: ""
|
||||
property string actionText: ""
|
||||
signal actionClicked()
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: visible ? row.implicitHeight + 20 : 0
|
||||
color: "#e34948"
|
||||
opacity: 0.92
|
||||
radius: Theme.radius
|
||||
color: Qt.alpha(Theme.clay, 0.12)
|
||||
border.width: 1
|
||||
border.color: Qt.alpha(Theme.clay, 0.4)
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
anchors.margins: 12
|
||||
spacing: 12
|
||||
Label {
|
||||
text: banner.text
|
||||
color: "white"
|
||||
color: Theme.ink
|
||||
font.pixelSize: 12
|
||||
font.family: Theme.sansFamily
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
@@ -29,7 +36,6 @@ Rectangle {
|
||||
visible: banner.actionText !== ""
|
||||
text: banner.actionText
|
||||
flat: true
|
||||
Material.foreground: "white"
|
||||
onClicked: banner.actionClicked()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
// A tiny bar chart: magnitude over time, one hue, baseline at zero.
|
||||
//
|
||||
// The ramp step says "did this day clear the goal" without adding a second
|
||||
// colour, and every bar carries its own tooltip, so no bar needs a label.
|
||||
// Pomodoros per day: magnitude over time, so bars, one hue, baseline at zero.
|
||||
// The goal line is the only reference mark; days that cleared it take a darker
|
||||
// step of the same ramp, which keeps "did I hit it" readable without a second
|
||||
// colour.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import ".."
|
||||
|
||||
Item {
|
||||
id: chart
|
||||
property var days: [] // [{date, pomodoros, focus_minutes, goal_met}]
|
||||
property var days: []
|
||||
property int goal: 8
|
||||
property int barHeight: 110
|
||||
property int barHeight: 108
|
||||
|
||||
implicitHeight: barHeight + 18
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: barHeight + 16
|
||||
|
||||
readonly property int maxValue: {
|
||||
var top = goal
|
||||
@@ -25,8 +28,7 @@ Item {
|
||||
anchors.right: parent.right
|
||||
y: chart.barHeight - (chart.goal / chart.maxValue) * chart.barHeight
|
||||
height: 1
|
||||
color: Material.hintTextColor
|
||||
opacity: 0.45
|
||||
color: Theme.rule
|
||||
visible: chart.goal > 0
|
||||
}
|
||||
|
||||
@@ -48,15 +50,16 @@ Item {
|
||||
width: parent.width
|
||||
height: Math.max(modelData.pomodoros > 0
|
||||
? (modelData.pomodoros / chart.maxValue) * chart.barHeight : 2, 2)
|
||||
radius: 4
|
||||
color: modelData.pomodoros === 0 ? Material.dividerColor
|
||||
: (modelData.goal_met ? "#256abf" : "#6da7ec")
|
||||
radius: 3
|
||||
color: modelData.pomodoros === 0
|
||||
? Theme.rule
|
||||
: (modelData.goal_met ? Theme.sequential[4] : Theme.sequential[2])
|
||||
|
||||
ToolTip.visible: hover.hovered
|
||||
ToolTip.text: modelData.date + ": " + modelData.pomodoros
|
||||
ToolTip.text: modelData.date + " · " + modelData.pomodoros
|
||||
+ " pomodoros · " + modelData.focus_minutes + " min"
|
||||
HoverHandler { id: hover }
|
||||
Behavior on height { NumberAnimation { duration: 250 } }
|
||||
Behavior on height { NumberAnimation { duration: 220 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,12 +71,12 @@ Item {
|
||||
anchors.right: parent.right
|
||||
Label {
|
||||
text: chart.days.length ? chart.days[0].date.substring(5) : ""
|
||||
font.pixelSize: 10; opacity: 0.6
|
||||
font.pixelSize: 10; color: Theme.faint; font.family: Theme.sansFamily
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
Label {
|
||||
text: chart.days.length ? chart.days[chart.days.length - 1].date.substring(5) : ""
|
||||
font.pixelSize: 10; opacity: 0.6
|
||||
font.pixelSize: 10; color: Theme.faint; font.family: Theme.sansFamily
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// A Material surface with a title. Used everywhere instead of bare Panes so the
|
||||
// pages share one radius, one padding and one elevation.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Layouts
|
||||
|
||||
Pane {
|
||||
id: card
|
||||
property string title: ""
|
||||
property string subtitle: ""
|
||||
default property alias content: inner.data
|
||||
|
||||
Material.elevation: 1
|
||||
Material.roundedScale: Material.LargeScale
|
||||
padding: 18
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: Rectangle {
|
||||
radius: 18
|
||||
color: Material.dialogColor
|
||||
border.width: 1
|
||||
border.color: Material.dividerColor
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
visible: card.title !== ""
|
||||
Layout.fillWidth: true
|
||||
Label { text: card.title; font.pixelSize: 16; font.weight: Font.DemiBold }
|
||||
Label {
|
||||
text: card.subtitle
|
||||
visible: card.subtitle !== ""
|
||||
font.pixelSize: 12
|
||||
opacity: 0.7
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: inner
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// The outlined button, for everything that is not the primary action.
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
import ".."
|
||||
|
||||
Button {
|
||||
id: control
|
||||
implicitHeight: 34
|
||||
leftPadding: 14
|
||||
rightPadding: 14
|
||||
|
||||
background: Rectangle {
|
||||
radius: 8
|
||||
color: control.hovered ? Qt.alpha(Theme.ember, 0.07) : "transparent"
|
||||
border.width: 1
|
||||
border.color: control.hovered ? Theme.ember : Theme.rule
|
||||
Behavior on color { ColorAnimation { duration: 110 } }
|
||||
}
|
||||
contentItem: Label {
|
||||
text: control.text
|
||||
color: control.enabled ? Theme.ink : Theme.faint
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
font.family: Theme.sansFamily
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Eyebrow, title, actions, rule — the same opening every page uses.
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import ".."
|
||||
|
||||
ColumnLayout {
|
||||
property string eyebrow: ""
|
||||
property string title: ""
|
||||
property string caption: ""
|
||||
default property alias actions: actionRow.data
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 16
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
Label {
|
||||
visible: eyebrow !== ""
|
||||
text: eyebrow.toUpperCase()
|
||||
color: Theme.muted
|
||||
font.pixelSize: 10
|
||||
font.letterSpacing: 1.3
|
||||
font.weight: Font.DemiBold
|
||||
font.family: Theme.sansFamily
|
||||
}
|
||||
Label {
|
||||
text: title
|
||||
color: Theme.ink
|
||||
font.pixelSize: 27
|
||||
font.family: Theme.displayFamily
|
||||
font.letterSpacing: -0.6
|
||||
}
|
||||
Label {
|
||||
visible: caption !== ""
|
||||
text: caption
|
||||
color: Theme.muted
|
||||
font.pixelSize: 13
|
||||
font.family: Theme.sansFamily
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout { id: actionRow; spacing: 8; Layout.alignment: Qt.AlignRight | Qt.AlignVCenter }
|
||||
}
|
||||
|
||||
Rectangle { Layout.fillWidth: true; height: 1; color: Theme.rule }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// A surface with a hairline border. No shadow, no tint — the border does the
|
||||
// separating, which is what keeps a page of panels calm.
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import ".."
|
||||
|
||||
Rectangle {
|
||||
id: panel
|
||||
default property alias content: inner.data
|
||||
property int padding: Theme.pad
|
||||
property alias spacing: inner.spacing
|
||||
|
||||
color: Theme.surface
|
||||
radius: Theme.radius
|
||||
border.width: 1
|
||||
border.color: Theme.rule
|
||||
implicitHeight: inner.implicitHeight + padding * 2
|
||||
implicitWidth: inner.implicitWidth + padding * 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
id: inner
|
||||
anchors.fill: parent
|
||||
anchors.margins: panel.padding
|
||||
spacing: 10
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,28 @@
|
||||
// The filled Material button, painted explicitly.
|
||||
//
|
||||
// Relying on `highlighted: true` leaves the fill to the style, and under some
|
||||
// renderers that fill never appears — white text on a white surface. The
|
||||
// primary action on a page is exactly the thing that must never be invisible,
|
||||
// so it draws its own pill.
|
||||
// The filled button: one per view, painted explicitly so no style can lose it.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Controls
|
||||
|
||||
import ".."
|
||||
|
||||
Button {
|
||||
id: control
|
||||
|
||||
Material.foreground: "white"
|
||||
font.weight: Font.Medium
|
||||
leftPadding: 20
|
||||
rightPadding: 20
|
||||
implicitHeight: 40
|
||||
implicitHeight: 34
|
||||
leftPadding: 16
|
||||
rightPadding: 16
|
||||
|
||||
background: Rectangle {
|
||||
radius: height / 2
|
||||
color: control.enabled ? Material.accent : Material.buttonDisabledColor
|
||||
radius: 8
|
||||
color: control.enabled ? Theme.ember : Theme.rule
|
||||
opacity: control.pressed ? 0.82 : (control.hovered ? 0.92 : 1)
|
||||
Behavior on opacity { NumberAnimation { duration: 120 } }
|
||||
Behavior on opacity { NumberAnimation { duration: 110 } }
|
||||
}
|
||||
|
||||
contentItem: Label {
|
||||
text: control.text
|
||||
font: control.font
|
||||
color: control.enabled ? "white" : Material.hintTextColor
|
||||
color: control.enabled ? "white" : Theme.faint
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
font.family: Theme.sansFamily
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// An eyebrow label with a rule running to the right edge.
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import ".."
|
||||
|
||||
RowLayout {
|
||||
property string text: ""
|
||||
property string trailing: ""
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
Label {
|
||||
text: parent.text.toUpperCase()
|
||||
color: Theme.muted
|
||||
font.pixelSize: 10
|
||||
font.letterSpacing: 1.3
|
||||
font.weight: Font.DemiBold
|
||||
font.family: Theme.sansFamily
|
||||
}
|
||||
Rectangle { Layout.fillWidth: true; height: 1; color: Theme.rule }
|
||||
Label {
|
||||
visible: parent.trailing !== ""
|
||||
text: parent.trailing
|
||||
color: Theme.faint
|
||||
font.pixelSize: 11
|
||||
font.family: Theme.sansFamily
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// Transient messages. Material's own popup, anchored to the bottom.
|
||||
// Transient messages, bottom centre, on an ink slab.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Controls
|
||||
|
||||
import ".."
|
||||
|
||||
Popup {
|
||||
id: snackbar
|
||||
@@ -13,19 +15,23 @@ Popup {
|
||||
}
|
||||
|
||||
x: Math.round((parent.width - width) / 2)
|
||||
y: parent.height - height - 72
|
||||
width: Math.min(parent.width - 48, Math.max(260, label.implicitWidth + 48))
|
||||
padding: 16
|
||||
y: parent.height - height - 28
|
||||
width: Math.min(parent.width - 48, Math.max(240, label.implicitWidth + 36))
|
||||
padding: 14
|
||||
modal: false
|
||||
focus: false
|
||||
closePolicy: Popup.NoAutoClose
|
||||
Material.elevation: 6
|
||||
Material.roundedScale: Material.SmallScale
|
||||
|
||||
Label {
|
||||
background: Rectangle {
|
||||
color: Theme.ink
|
||||
radius: 10
|
||||
}
|
||||
contentItem: Label {
|
||||
id: label
|
||||
anchors.fill: parent
|
||||
text: snackbar.message
|
||||
color: Theme.paper
|
||||
font.pixelSize: 13
|
||||
font.family: Theme.sansFamily
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// A number and its name. The figure is set in the display serif, which is what
|
||||
// makes a screen of statistics read like a page rather than a readout.
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import ".."
|
||||
|
||||
ColumnLayout {
|
||||
property string label: ""
|
||||
property string value: "—"
|
||||
property string caption: ""
|
||||
property color accent: Theme.ink
|
||||
property int size: 30
|
||||
|
||||
spacing: 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
text: label.toUpperCase()
|
||||
color: Theme.muted
|
||||
font.pixelSize: 10
|
||||
font.letterSpacing: 1.2
|
||||
font.weight: Font.DemiBold
|
||||
font.family: Theme.sansFamily
|
||||
}
|
||||
Label {
|
||||
text: value
|
||||
color: accent
|
||||
font.pixelSize: size
|
||||
font.family: Theme.displayFamily
|
||||
font.letterSpacing: -0.5
|
||||
}
|
||||
Label {
|
||||
visible: caption !== ""
|
||||
text: caption
|
||||
color: Theme.faint
|
||||
font.pixelSize: 11
|
||||
font.family: Theme.sansFamily
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// One number, read at a glance. Not a chart.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Layouts
|
||||
|
||||
Pane {
|
||||
property string label: ""
|
||||
property string value: "—"
|
||||
property string caption: ""
|
||||
property color accent: Material.foreground
|
||||
|
||||
Material.elevation: 1
|
||||
Material.roundedScale: Material.LargeScale
|
||||
padding: 16
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: Rectangle {
|
||||
radius: 16
|
||||
color: Material.dialogColor
|
||||
border.width: 1
|
||||
border.color: Material.dividerColor
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 2
|
||||
Label { text: label.toUpperCase(); font.pixelSize: 10; font.letterSpacing: 1; opacity: 0.6 }
|
||||
Label { text: value; font.pixelSize: 26; font.weight: Font.Light; color: accent }
|
||||
Label {
|
||||
text: caption
|
||||
visible: caption !== ""
|
||||
font.pixelSize: 11
|
||||
opacity: 0.7
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// The little "what is the timer doing" chip, shown in the rail and the toolbar.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
|
||||
Rectangle {
|
||||
property bool running: false
|
||||
property bool paused: false
|
||||
property bool connected: true
|
||||
property string text: ""
|
||||
property color accent: "#2a78d6"
|
||||
|
||||
implicitWidth: Math.max(78, label.implicitWidth + 28)
|
||||
implicitHeight: 32
|
||||
radius: 16
|
||||
color: !connected ? "#e34948" : (running ? accent : "transparent")
|
||||
opacity: paused ? 0.6 : 1
|
||||
border.width: running || !connected ? 0 : 1
|
||||
border.color: Material.hintTextColor
|
||||
|
||||
Label {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
text: parent.text
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.DemiBold
|
||||
font.family: "Roboto Mono, monospace"
|
||||
color: (parent.running || !parent.connected) ? "white" : Material.foreground
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,32 @@
|
||||
// The countdown ring, drawn with Canvas.
|
||||
// The dial.
|
||||
//
|
||||
// A hero number inside a progress arc: the number is what you read, the arc is
|
||||
// the glance. The arc colour is the only thing that says focus vs break.
|
||||
// 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.Material
|
||||
import QtQuick.Controls
|
||||
|
||||
import ".."
|
||||
|
||||
Item {
|
||||
id: dial
|
||||
property real progress: 0 // 0..1 elapsed
|
||||
property real progress: 0 // 0..1 elapsed
|
||||
property string clockText: "00:00"
|
||||
property string label: ""
|
||||
property string sublabel: ""
|
||||
property color accent: "#2a78d6"
|
||||
property color accent: Theme.work
|
||||
property bool paused: false
|
||||
property bool idle: false
|
||||
property int minutes: 25
|
||||
|
||||
implicitWidth: 260
|
||||
implicitHeight: 260
|
||||
implicitWidth: 280
|
||||
implicitHeight: 280
|
||||
|
||||
onProgressChanged: canvas.requestPaint()
|
||||
onAccentChanged: canvas.requestPaint()
|
||||
onPausedChanged: canvas.requestPaint()
|
||||
onIdleChanged: canvas.requestPaint()
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
@@ -29,59 +36,85 @@ Item {
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
var centre = Math.min(width, height) / 2
|
||||
var stroke = Math.max(10, centre * 0.11)
|
||||
var radius = centre - stroke
|
||||
|
||||
ctx.lineCap = "round"
|
||||
ctx.lineWidth = stroke
|
||||
var c = Math.min(width, height) / 2
|
||||
var radius = c - 26
|
||||
|
||||
// The ring.
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.strokeStyle = Theme.rule
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = Qt.rgba(0.5, 0.5, 0.5, 0.18)
|
||||
ctx.arc(centre, centre, radius, 0, Math.PI * 2)
|
||||
ctx.arc(c, c, radius, 0, Math.PI * 2)
|
||||
ctx.stroke()
|
||||
|
||||
var remaining = 1 - Math.max(0, Math.min(1, dial.progress))
|
||||
if (remaining > 0) {
|
||||
// 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.globalAlpha = dial.paused ? 0.45 : 1
|
||||
ctx.strokeStyle = dial.accent
|
||||
ctx.arc(centre, centre, radius, -Math.PI / 2, -Math.PI / 2 + remaining * Math.PI * 2)
|
||||
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: 4
|
||||
width: parent.width * 0.72
|
||||
spacing: 6
|
||||
width: parent.width * 0.7
|
||||
|
||||
Label {
|
||||
width: parent.width
|
||||
text: dial.label.toUpperCase()
|
||||
font.pixelSize: 11
|
||||
font.letterSpacing: 1.5
|
||||
opacity: 0.65
|
||||
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
|
||||
font.pixelSize: Math.round(dial.width * 0.2)
|
||||
font.weight: Font.Light
|
||||
font.family: "Roboto Mono, monospace"
|
||||
opacity: dial.paused ? 0.6 : 1
|
||||
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
|
||||
text: dial.sublabel
|
||||
visible: dial.sublabel !== ""
|
||||
font.pixelSize: 13
|
||||
opacity: 0.75
|
||||
elide: Text.ElideRight
|
||||
text: dial.sublabel
|
||||
color: Theme.muted
|
||||
font.pixelSize: 12
|
||||
font.family: Theme.sansFamily
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user