Files
dms-gamebar/widgets/TimerWidget.qml
T

350 lines
11 KiB
QML

import QtQuick
import qs.Common
import qs.Widgets
Item {
id: widget
property var service: null
property int tabIndex: 0
readonly property bool onTimerTab: tabIndex === 0
readonly property var presets: [1, 3, 5, 10, 15, 25, 45, 60]
component ActionButton: Rectangle {
id: button
property string icon: ""
property string label: ""
property bool accent: false
property bool enabled: true
signal triggered
width: buttonRow.width + Theme.spacingM * 2
height: 34
radius: Theme.cornerRadius
color: accent ? Theme.primary : (buttonArea.containsMouse && enabled ? Theme.surfaceHover : Theme.withAlpha(Theme.surfaceLight, Theme.popupTransparency))
border.color: accent ? Theme.primary : Theme.outlineLight
border.width: Theme.layerOutlineWidth
opacity: enabled ? 1 : 0.45
Row {
id: buttonRow
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: button.icon
size: Theme.iconSize - 8
color: button.accent ? Theme.onPrimary : Theme.surfaceText
visible: button.icon !== ""
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: button.label
font.pixelSize: Theme.fontSizeSmall
font.weight: button.accent ? Font.Medium : Font.Normal
color: button.accent ? Theme.onPrimary : Theme.surfaceText
visible: button.label !== ""
}
}
MouseArea {
id: buttonArea
anchors.fill: parent
hoverEnabled: true
enabled: button.enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: button.triggered()
}
}
DankTabBar {
id: tabs
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
tabHeight: 34
showIcons: false
currentIndex: widget.tabIndex
model: [
{
"text": "Timer"
},
{
"text": "Stopwatch"
}
]
onTabClicked: index => widget.tabIndex = index
}
// ── Timer ────────────────────────────────────────────────────────────────
Item {
anchors.top: tabs.bottom
anchors.topMargin: Theme.spacingS
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
visible: widget.onTimerTab
StyledText {
id: remaining
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
text: widget.service ? widget.service.formatDuration(widget.service.remainingMs) : "00:00"
font.pixelSize: 40
font.weight: Font.Medium
color: (widget.service && widget.service.timerFinished) ? Theme.error : Theme.surfaceText
SequentialAnimation on opacity {
running: widget.service !== null && widget.service.timerFinished
loops: Animation.Infinite
NumberAnimation {
to: 0.35
duration: 500
}
NumberAnimation {
to: 1
duration: 500
}
}
}
Rectangle {
id: progress
anchors.top: remaining.bottom
anchors.topMargin: Theme.spacingXS
anchors.left: parent.left
anchors.right: parent.right
height: 4
radius: 2
color: Theme.withAlpha(Theme.outline, 0.4)
Rectangle {
width: parent.width * (widget.service ? widget.service.timerProgress : 0)
height: parent.height
radius: parent.radius
color: (widget.service && widget.service.timerFinished) ? Theme.error : Theme.primary
Behavior on width {
NumberAnimation {
duration: 180
}
}
}
}
DankTextField {
id: labelField
anchors.top: progress.bottom
anchors.topMargin: Theme.spacingS
anchors.left: parent.left
anchors.right: parent.right
height: 34
placeholderText: "What is it for? (shown when it ends)"
text: widget.service ? widget.service.timerLabel : ""
onTextChanged: {
if (widget.service)
widget.service.timerLabel = text;
}
}
Flow {
id: presetFlow
anchors.top: labelField.bottom
anchors.topMargin: Theme.spacingS
anchors.left: parent.left
anchors.right: parent.right
spacing: Theme.spacingXS
Repeater {
model: widget.presets
delegate: ActionButton {
required property var modelData
label: modelData + "m"
onTriggered: {
if (!widget.service)
return;
widget.service.resetTimer();
widget.service.startTimer(modelData * 60 * 1000);
}
}
}
}
Row {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXS
ActionButton {
icon: "remove"
label: "1m"
enabled: widget.service !== null
onTriggered: widget.service.addTime(-60 * 1000)
}
ActionButton {
icon: (widget.service && widget.service.timerRunning) ? "pause" : "play_arrow"
label: (widget.service && widget.service.timerRunning) ? "Pause" : "Start"
accent: true
enabled: widget.service !== null
onTriggered: widget.service.toggleTimer()
}
ActionButton {
icon: "add"
label: "1m"
enabled: widget.service !== null
onTriggered: widget.service.addTime(60 * 1000)
}
ActionButton {
icon: "restart_alt"
label: (widget.service && widget.service.timerFinished) ? "Dismiss" : "Reset"
enabled: widget.service !== null
onTriggered: {
if (widget.service.timerFinished)
widget.service.dismissFinished();
else
widget.service.resetTimer();
}
}
}
}
// ── Stopwatch ────────────────────────────────────────────────────────────
Item {
anchors.top: tabs.bottom
anchors.topMargin: Theme.spacingS
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
visible: !widget.onTimerTab
StyledText {
id: elapsed
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
text: widget.service ? widget.service.formatStopwatch(widget.service.stopwatchElapsedMs) : "00:00.00"
font.pixelSize: 40
font.weight: Font.Medium
color: Theme.surfaceText
}
DankFlickable {
id: lapList
anchors.top: elapsed.bottom
anchors.topMargin: Theme.spacingS
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: stopwatchControls.top
anchors.bottomMargin: Theme.spacingS
clip: true
contentHeight: lapColumn.height
contentWidth: width
Column {
id: lapColumn
width: lapList.width
spacing: 2
Repeater {
model: widget.service ? widget.service.laps : []
delegate: Item {
required property var modelData
width: lapColumn.width
height: 22
StyledText {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: "Lap " + modelData.index
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
anchors.right: totalText.left
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
text: "+" + widget.service.formatStopwatch(modelData.split)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
id: totalText
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: widget.service.formatStopwatch(modelData.total)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
}
}
}
}
}
StyledText {
anchors.centerIn: lapList
text: "No laps yet"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
visible: !widget.service || widget.service.laps.length === 0
}
Row {
id: stopwatchControls
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXS
ActionButton {
icon: (widget.service && widget.service.stopwatchRunning) ? "pause" : "play_arrow"
label: (widget.service && widget.service.stopwatchRunning) ? "Pause" : "Start"
accent: true
enabled: widget.service !== null
onTriggered: widget.service.toggleStopwatch()
}
ActionButton {
icon: "flag"
label: "Lap"
enabled: widget.service !== null && widget.service.stopwatchElapsedMs > 0
onTriggered: widget.service.lap()
}
ActionButton {
icon: "restart_alt"
label: "Reset"
enabled: widget.service !== null && widget.service.stopwatchElapsedMs > 0
onTriggered: widget.service.resetStopwatch()
}
}
}
}