pomodoro: move tabs to their own files, adjust colors

This commit is contained in:
end-4 2025-08-09 16:28:17 +07:00
parent de759b5120
commit 0ee9afba4f
5 changed files with 359 additions and 323 deletions

View file

@ -146,6 +146,14 @@ Singleton {
property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
property color colOutlineVariant: m3colors.m3outlineVariant
property color colError: m3colors.m3error
property color colErrorHover: ColorUtils.mix(m3colors.m3error, colLayer1Hover, 0.85)
property color colErrorActive: ColorUtils.mix(m3colors.m3error, colLayer1Active, 0.7)
property color colOnError: m3colors.m3onError
property color colErrorContainer: m3colors.m3errorContainer
property color colErrorContainerHover: ColorUtils.mix(m3colors.m3errorContainer, m3colors.m3onErrorContainer, 0.90)
property color colErrorContainerActive: ColorUtils.mix(m3colors.m3errorContainer, m3colors.m3onErrorContainer, 0.70)
property color colOnErrorContainer: m3colors.m3onErrorContainer
}
rounding: QtObject {

View file

@ -29,6 +29,7 @@ Button {
property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
opacity: root.enabled ? 1 : 0.4
property color buttonColor: root.enabled ? (root.toggled ?
(root.hovered ? colBackgroundToggledHover :
colBackgroundToggled) :

View file

@ -0,0 +1,176 @@
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 {
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 20
RowLayout {
spacing: 40
// The Pomodoro timer circle
CircularProgress {
Layout.alignment: Qt.AlignHCenter
lineWidth: 7
gapAngle: Math.PI / 14
value: {
let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime
return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime
}
size: 125
primaryColor: Appearance.m3colors.m3onSecondaryContainer
secondaryColor: Appearance.colors.colSecondaryContainer
enableAnimation: true
ColumnLayout {
anchors.centerIn: parent
spacing: 0
StyledText {
Layout.alignment: Qt.AlignHCenter
text: {
let minutes = Math.floor(Pomodoro.getPomodoroSecondsLeft / 60).toString().padStart(2, '0')
let seconds = Math.floor(Pomodoro.getPomodoroSecondsLeft % 60).toString().padStart(2, '0')
return `${minutes}:${seconds}`
}
font.pixelSize: Appearance.font.pixelSize.hugeass + 4
color: Appearance.m3colors.m3onSurface
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus")
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSurface
}
}
}
// The Start/Stop and Reset buttons
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 10
RippleButton {
contentItem: StyledText {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.getPomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume")
color: Pomodoro.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
}
implicitHeight: 35
implicitWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: Pomodoro.togglePomodoro()
colBackground: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
colBackgroundHover: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
}
RippleButton {
implicitHeight: 35
implicitWidth: 90
onClicked: Pomodoro.pomodoroReset()
enabled: (Pomodoro.getPomodoroSecondsLeft < Pomodoro.focusTime)
font.pixelSize: Appearance.font.pixelSize.larger
colBackground: Appearance.colors.colErrorContainer
colBackgroundHover: Appearance.colors.colErrorContainerHover
colRipple: Appearance.colors.colErrorContainerActive
contentItem: StyledText {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: Translation.tr("Reset")
color: Appearance.colors.colOnErrorContainer
}
}
}
}
// The SpinBoxes for adjusting duration
GridLayout {
Layout.alignment: Qt.AlignHCenter
columns: 2
uniformCellWidths: true
columnSpacing: 20
rowSpacing: 4
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Translation.tr("Focus")
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Translation.tr("Break")
}
ConfigSpinBox {
id: focusSpinBox
spacing: 0
Layout.leftMargin: 0
Layout.rightMargin: 0
value: Config.options.time.pomodoro.focus / 60
onValueChanged: {
Config.options.time.pomodoro.focus = value * 60
if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state
Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime
Pomodoro.timeLeft = Pomodoro.focusTime
}
}
}
ConfigSpinBox {
id: breakSpinBox
spacing: 0
Layout.leftMargin: 0
Layout.rightMargin: 0
value: Config.options.time.pomodoro.breakTime / 60
onValueChanged: {
Config.options.time.pomodoro.breakTime = value * 60
}
}
StyledText {
Layout.topMargin: 6
Layout.alignment: Qt.AlignHCenter
text: Translation.tr("Cycle")
}
StyledText {
Layout.topMargin: 6
Layout.alignment: Qt.AlignHCenter
text: Translation.tr("Long break")
}
ConfigSpinBox {
id: cycleSpinBox
spacing: 0
from: 1
Layout.leftMargin: 0
Layout.rightMargin: 0
value: Config.options.time.pomodoro.cycle
onValueChanged: {
Config.options.time.pomodoro.cycle = value
}
}
ConfigSpinBox {
id: longBreakSpinBox
spacing: 0
Layout.leftMargin: 0
Layout.rightMargin: 0
value: Config.options.time.pomodoro.longBreak / 60
onValueChanged: {
Config.options.time.pomodoro.longBreak = value * 60
}
}
}
}
}

View file

@ -2,17 +2,15 @@ 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: root
property int currentTab: 0
property var tabButtonList: [
{"name": Translation.tr("Pomodoro"), "icon": "timer_play"},
{"name": Translation.tr("Pomodoro"), "icon": "search_activity"},
{"name": Translation.tr("Stopwatch"), "icon": "timer"}
]
@ -157,326 +155,9 @@ Item {
currentTab = currentIndex
}
// Pomodoro Timer Tab
Item {
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 20
RowLayout {
spacing: 40
// The Pomodoro timer circle
CircularProgress {
Layout.alignment: Qt.AlignHCenter
lineWidth: 7
gapAngle: Math.PI / 14
value: {
let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime
return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime
}
size: 125
primaryColor: Appearance.m3colors.m3onSecondaryContainer
secondaryColor: Appearance.colors.colSecondaryContainer
enableAnimation: true
ColumnLayout {
anchors.centerIn: parent
spacing: 0
StyledText {
Layout.alignment: Qt.AlignHCenter
text: {
let minutes = Math.floor(Pomodoro.getPomodoroSecondsLeft / 60).toString().padStart(2, '0')
let seconds = Math.floor(Pomodoro.getPomodoroSecondsLeft % 60).toString().padStart(2, '0')
return `${minutes}:${seconds}`
}
font.pixelSize: Appearance.font.pixelSize.hugeass + 4
color: Appearance.m3colors.m3onSurface
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus")
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSurface
}
}
}
// The Start/Stop and Reset buttons
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 10
RippleButton {
contentItem: StyledText {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start")
color: Appearance.colors.colSecondary
}
Layout.preferredHeight: 35
Layout.preferredWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: Pomodoro.togglePomodoro()
colBackground: Appearance.colors.colSecondaryContainer
colBackgroundHover: Appearance.colors.colSecondaryContainer
}
RippleButton {
contentItem: StyledText {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: Translation.tr("Reset")
color: Appearance.colors.colSecondary
}
Layout.preferredHeight: 35
Layout.preferredWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: Pomodoro.pomodoroReset()
colBackground: Appearance.m3colors.m3onError
colBackgroundHover: Appearance.m3colors.m3onError
}
}
}
// The SpinBoxes for adjusting duration
GridLayout {
Layout.alignment: Qt.AlignHCenter
columns: 2
uniformCellWidths: true
columnSpacing: 20
rowSpacing: 6
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Translation.tr("Focus")
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Translation.tr("Break")
}
ConfigSpinBox {
id: focusSpinBox
spacing: 0
Layout.leftMargin: 0
Layout.rightMargin: 0
value: Config.options.time.pomodoro.focus / 60
onValueChanged: {
Config.options.time.pomodoro.focus = value * 60
if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state
Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime
Pomodoro.timeLeft = Pomodoro.focusTime
}
}
}
ConfigSpinBox {
id: breakSpinBox
spacing: 0
Layout.leftMargin: 0
Layout.rightMargin: 0
value: Config.options.time.pomodoro.breakTime / 60
onValueChanged: {
Config.options.time.pomodoro.breakTime = value * 60
}
}
StyledText {
Layout.topMargin: 6
Layout.alignment: Qt.AlignHCenter
text: Translation.tr("Cycle")
}
StyledText {
Layout.topMargin: 6
Layout.alignment: Qt.AlignHCenter
text: Translation.tr("Long break")
}
ConfigSpinBox {
id: cycleSpinBox
spacing: 0
from: 1
Layout.leftMargin: 0
Layout.rightMargin: 0
value: Config.options.time.pomodoro.cycle
onValueChanged: {
Config.options.time.pomodoro.cycle = value
}
}
ConfigSpinBox {
id: longBreakSpinBox
spacing: 0
Layout.leftMargin: 0
Layout.rightMargin: 0
value: Config.options.time.pomodoro.longBreak / 60
onValueChanged: {
Config.options.time.pomodoro.longBreak = value * 60
}
}
}
}
}
// Stopwatch Tab
Item {
id: stopwatchTab
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors {
fill: parent
leftMargin: 20
rightMargin: 20
}
spacing: 20
ColumnLayout {
spacing: 8
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: false
RowLayout { // Elapsed
id: elapsedIndicator
Layout.alignment: Qt.AlignHCenter
spacing: 0
StyledText {
Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness
font.pixelSize: 40
color: Appearance.m3colors.m3onSurface
text: {
let totalSeconds = Math.floor(Pomodoro.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(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0')}</sub>`
}
}
}
// The Start/Stop and Reset buttons
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 4
RippleButton {
contentItem: StyledText {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Pomodoro.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume")
color: Appearance.colors.colSecondary
}
Layout.preferredHeight: 35
Layout.preferredWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: Pomodoro.toggleStopwatch()
colBackground: Appearance.colors.colSecondaryContainer
colBackgroundHover: Appearance.colors.colSecondaryContainer
}
RippleButton {
contentItem: StyledText {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: Pomodoro.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset")
color: Appearance.colors.colSecondary
}
Layout.preferredHeight: 35
Layout.preferredWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: Pomodoro.stopwatchResetOrLaps()
colBackground: Appearance.m3colors.m3onError
colBackgroundHover: Appearance.m3colors.m3onError
}
}
}
// Laps
StyledListView {
id: lapsList
Layout.fillWidth: true
Layout.fillHeight: true
spacing: lapsListItemSpacing
clip: true
popin: true
model: ScriptModel {
values: Pomodoro.stopwatchLaps
}
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: `${Pomodoro.stopwatchLaps.length - lapItem.index}.`
}
StyledText {
font.pixelSize: Appearance.font.pixelSize.small
text: {
let lapTime = lapItem.modelData
let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')
let totalSeconds = Math.floor(lapTime) / 100
let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
let 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: {
if (lapItem.index != Pomodoro.stopwatchLaps.length - 1) { // except first lap
let lapTime = lapItem.modelData - Pomodoro.stopwatchLaps[lapItem.index + 1]
let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')
let totalSeconds = Math.floor(lapTime) / 100
let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')
return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}`
} else {
return `` // Nothing for first lap
}
}
}
}
}
}
}
}
// Tabs
PomodoroTimer {}
Stopwatch {}
}
}
}

View file

@ -0,0 +1,170 @@
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
ColumnLayout {
anchors {
fill: parent
leftMargin: 20
rightMargin: 20
}
spacing: 20
ColumnLayout {
spacing: 8
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: false
RowLayout { // Elapsed
id: elapsedIndicator
Layout.alignment: Qt.AlignHCenter
spacing: 0
StyledText {
Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness
font.pixelSize: 40
color: Appearance.m3colors.m3onSurface
text: {
let totalSeconds = Math.floor(Pomodoro.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(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0')}</sub>`
}
}
}
// The Start/Stop and Reset buttons
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 4
RippleButton {
Layout.preferredHeight: 35
Layout.preferredWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: Pomodoro.toggleStopwatch()
colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover
colRipple: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive
contentItem: StyledText {
horizontalAlignment: Text.AlignHCenter
color: Pomodoro.isStopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
text: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Pomodoro.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume")
}
}
RippleButton {
implicitHeight: 35
implicitWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: Pomodoro.stopwatchResetOrLaps()
enabled: Pomodoro.stopwatchTime !== 0
colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer
colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover
colRipple: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive
contentItem: StyledText {
horizontalAlignment: Text.AlignHCenter
text: Pomodoro.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset")
color: Pomodoro.isStopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer
}
}
}
}
// Laps
StyledListView {
id: lapsList
Layout.fillWidth: true
Layout.fillHeight: true
spacing: lapsListItemSpacing
clip: true
popin: true
model: ScriptModel {
values: Pomodoro.stopwatchLaps
}
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: `${Pomodoro.stopwatchLaps.length - lapItem.index}.`
}
StyledText {
font.pixelSize: Appearance.font.pixelSize.small
text: {
let lapTime = lapItem.modelData
let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')
let totalSeconds = Math.floor(lapTime) / 100
let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
let 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: {
let lastTime = (lapItem.index != Pomodoro.stopwatchLaps.length - 1) ? Pomodoro.stopwatchLaps[lapItem.index + 1] : 0
let lapTime = lapItem.modelData - lastTime
let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')
let totalSeconds = Math.floor(lapTime) / 100
let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')
return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}`
}
}
}
}
}
}
}