Garden was welded onto the notification list in CenterWidgetGroup — always on, unrelated, stealing space from notifications that said "Nothing". Moved it into the BottomWidgetGroup drawer as a fifth tab alongside Calendar, To Do, Pomodoro, and Stopwatch. CenterWidgetGroup is now just the notification list. Lens events (note_created, synced) emit through notify-send so they land in the notification system properly. Forked calendar, todo, pomodoro, notifications out of ii-base into our tree — the right sidebar no longer borrows any widget files from ii. Toggle dialogs (bluetooth, wifi, nightLight) still symlinked as infrastructure.
207 lines
No EOL
8.4 KiB
QML
207 lines
No EOL
8.4 KiB
QML
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |