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
@@ -146,6 +146,14 @@ Singleton {
property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5) property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7) property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
property color colOutlineVariant: m3colors.m3outlineVariant 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 { rounding: QtObject {
@@ -29,6 +29,7 @@ Button {
property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
opacity: root.enabled ? 1 : 0.4
property color buttonColor: root.enabled ? (root.toggled ? property color buttonColor: root.enabled ? (root.toggled ?
(root.hovered ? colBackgroundToggledHover : (root.hovered ? colBackgroundToggledHover :
colBackgroundToggled) : colBackgroundToggled) :
@@ -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
}
}
}
}
}
@@ -2,17 +2,15 @@ import qs
import qs.services import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets import qs.modules.common.widgets
import Qt5Compat.GraphicalEffects
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
Item { Item {
id: root id: root
property int currentTab: 0 property int currentTab: 0
property var tabButtonList: [ property var tabButtonList: [
{"name": Translation.tr("Pomodoro"), "icon": "timer_play"}, {"name": Translation.tr("Pomodoro"), "icon": "search_activity"},
{"name": Translation.tr("Stopwatch"), "icon": "timer"} {"name": Translation.tr("Stopwatch"), "icon": "timer"}
] ]
@@ -157,326 +155,9 @@ Item {
currentTab = currentIndex currentTab = currentIndex
} }
// Pomodoro Timer Tab // Tabs
Item { PomodoroTimer {}
ColumnLayout { Stopwatch {}
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
}
}
}
}
}
}
}
}
} }
} }
} }
@@ -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}`
}
}
}
}
}
}
}