Files
illogical-impulse/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml
T
2025-08-10 16:03:37 +07:00

208 lines
8.4 KiB
QML

import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
Item {
id: stopwatchTab
Layout.fillWidth: true
Layout.fillHeight: true
Item {
anchors {
fill: parent
topMargin: 8
leftMargin: 16
rightMargin: 16
}
RowLayout { // Elapsed
id: elapsedIndicator
anchors {
top: undefined
verticalCenter: parent.verticalCenter
left: controlButtons.left
leftMargin: 6
}
states: State {
name: "hasLaps"
when: TimerService.stopwatchLaps.length > 0
AnchorChanges {
target: elapsedIndicator
anchors.top: parent.top
anchors.verticalCenter: undefined
anchors.left: controlButtons.left
}
}
transitions: Transition {
AnchorAnimation {
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
spacing: 0
StyledText {
// Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness
font.pixelSize: 40
color: Appearance.m3colors.m3onSurface
text: {
let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100
let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')
return `${minutes}:${seconds}`
}
}
StyledText {
Layout.fillWidth: true
font.pixelSize: 40
color: Appearance.colors.colSubtext
text: {
return `:<sub>${(Math.floor(TimerService.stopwatchTime) % 100).toString().padStart(2, '0')}</sub>`
}
}
}
// Laps
StyledListView {
id: lapsList
anchors {
top: elapsedIndicator.bottom
bottom: controlButtons.top
left: parent.left
right: parent.right
topMargin: 16
bottomMargin: 16
}
spacing: 4
clip: true
popin: true
model: ScriptModel {
values: TimerService.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i])
}
delegate: Rectangle {
id: lapItem
required property int index
required property var modelData
property var horizontalPadding: 10
property var verticalPadding: 6
width: lapsList.width
implicitHeight: lapRow.implicitHeight + verticalPadding * 2
implicitWidth: lapRow.implicitWidth + horizontalPadding * 2
color: Appearance.colors.colLayer2
radius: Appearance.rounding.small
RowLayout {
id: lapRow
anchors {
fill: parent
leftMargin: lapItem.horizontalPadding
rightMargin: lapItem.horizontalPadding
topMargin: lapItem.verticalPadding
bottomMargin: lapItem.verticalPadding
}
StyledText {
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colSubtext
text: `${TimerService.stopwatchLaps.length - lapItem.index}.`
}
StyledText {
font.pixelSize: Appearance.font.pixelSize.small
text: {
const lapTime = lapItem.modelData
const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')
const totalSeconds = Math.floor(lapTime) / 100
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')
return `${minutes}:${seconds}.${_10ms}`
}
}
Item { Layout.fillWidth: true }
StyledText {
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.colors.colPrimary
text: {
const originalIndex = TimerService.stopwatchLaps.length - lapItem.index - 1
const lastTime = originalIndex > 0 ? TimerService.stopwatchLaps[originalIndex - 1] : 0
const lapTime = lapItem.modelData - lastTime
const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')
const totalSeconds = Math.floor(lapTime) / 100
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')
return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}`
}
}
}
}
}
RowLayout {
id: controlButtons
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 6
}
spacing: 4
RippleButton {
Layout.preferredHeight: 35
Layout.preferredWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: {
TimerService.toggleStopwatch()
}
colBackground: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover
colRipple: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive
contentItem: StyledText {
horizontalAlignment: Text.AlignHCenter
color: TimerService.stopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
text: TimerService.stopwatchRunning ? Translation.tr("Pause") : TimerService.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume")
}
}
RippleButton {
implicitHeight: 35
implicitWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: {
if (TimerService.stopwatchRunning)
TimerService.stopwatchRecordLap()
else
TimerService.stopwatchReset()
}
enabled: TimerService.stopwatchTime > 0 || Persistent.states.timer.stopwatch.laps.length > 0
colBackground: TimerService.stopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer
colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover
colRipple: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive
contentItem: StyledText {
horizontalAlignment: Text.AlignHCenter
text: TimerService.stopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset")
color: TimerService.stopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer
}
}
}
}
}