Watch
1
0
Fork
You've already forked souveraine
0

publish: the public projection begins here

This is a projection, not a development branch. The tree above was constructed
from the internal source named below under a manifest that decides which paths
may leave, then scanned as a whole tree rather than as a series of patches, and
only then published.

Public history starts here because the history before it was not admissible,
and neither was the tree. What used to stand in this repository included a
rescue copy of another machine, a directory of phone handoffs, deployment
wired to one house, and a submodule pointing at a forge no stranger can reach.
None of that was ever the product. It stays in the private forge, which is
allowed to hold the whole working organism, and this is what was deliberately
sent out instead.

Three mechanisms produced this tree, in decreasing order of trust. A top-level
path the manifest does not name never arrives at all, which is the one that
catches directories nobody has thought of yet. Named internal files inside
admitted roots are dropped. A short, reviewed table replaces deployment
defaults that a public build must not carry -- an endpoint aimed at one LAN, a
VPN profile belonging to one phone, packaging built from one checkout path.

Everything after this commit is an ordinary publication with the same three
trailers, so a force push stops being routine and starts meaning that
something deliberate happened. The trailers bind the projection to its source
without pretending the public SHA is the private one: same lineage, different
tree, and the record says so.

Source-Sha: 8f27b1e76a8fef560a336aba18e6990713ff1047
Policy-Sha: 6b261d2f3e6e1fb19874846ba4bb1dfe15565d25b8618c1c1afba0419c101d27
Tree-Digest: 18ec3563c5e5ef9a414993a9f6734b251ff9ed3cd56eebdd6cac01e45c6e3067
This commit is contained in:
Fimeg 2026-09-04 15:55:48 -04:00
commit 8f42fc953d
1476 changed files with 238455 additions and 0 deletions

View file

@ -0,0 +1,368 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Layouts
StyledFlickable {
id: root
clip: true
contentWidth: width
contentHeight: settingsColumn.implicitHeight
function setMinutes(optionName, minutes) {
Config.options.time.pomodoro[optionName] = Math.max(1, minutes) * 60;
if (!TimerService.pomodoroRunning) {
TimerService.resetPomodoro();
}
}
function minutes(optionName) {
return Math.round(Config.options.time.pomodoro[optionName] / 60);
}
function applyPreset(focus, shortBreak, longBreak, cycles) {
Config.options.time.pomodoro.focus = focus * 60;
Config.options.time.pomodoro.breakTime = shortBreak * 60;
Config.options.time.pomodoro.longBreak = longBreak * 60;
Config.options.time.pomodoro.cyclesBeforeLongBreak = cycles;
if (!TimerService.pomodoroRunning) {
TimerService.resetPomodoro();
}
}
function setCycles(cycles) {
Config.options.time.pomodoro.cyclesBeforeLongBreak = cycles;
if (!TimerService.pomodoroRunning) {
TimerService.resetPomodoro();
}
}
ColumnLayout {
id: settingsColumn
width: root.width
spacing: 10
Rectangle {
Layout.fillWidth: true
Layout.leftMargin: 4
Layout.rightMargin: 12
implicitHeight: 108
radius: Appearance.rounding.normal
color: Appearance.colors.colLayer2
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 8
RowLayout {
Layout.fillWidth: true
spacing: 10
Rectangle {
Layout.alignment: Qt.AlignVCenter
implicitWidth: 42
implicitHeight: 42
radius: Appearance.rounding.full
color: Appearance.colors.colSecondaryContainer
MaterialSymbol {
anchors.centerIn: parent
text: "search_activity"
iconSize: Appearance.font.pixelSize.hugeass
color: Appearance.colors.colOnSecondaryContainer
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 1
StyledText {
text: Translation.tr("Focus profile")
font.pixelSize: Appearance.font.pixelSize.normal
font.weight: Font.Medium
color: Appearance.colors.colOnLayer2
}
StyledText {
text: `${root.minutes("focus")} / ${root.minutes("breakTime")} / ${root.minutes("longBreak")} min • ${Config.options.time.pomodoro.cyclesBeforeLongBreak} cycles`
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.colors.colSubtext
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: 8
ProfileChip {
iconName: "search_activity"
label: `${root.minutes("focus")}m`
color: Appearance.colors.colSecondaryContainer
textColor: Appearance.colors.colOnSecondaryContainer
}
ProfileChip {
iconName: "coffee"
label: `${root.minutes("breakTime")}m`
color: Appearance.colors.colTertiaryContainer
textColor: Appearance.colors.colOnTertiaryContainer
}
ProfileChip {
iconName: "spa"
label: `${root.minutes("longBreak")}m`
color: Appearance.colors.colLayer1
textColor: Appearance.colors.colOnLayer1
}
Item { Layout.fillWidth: true }
RowLayout {
spacing: 3
Repeater {
model: Config.options.time.pomodoro.cyclesBeforeLongBreak
Rectangle {
implicitWidth: 7
implicitHeight: 7
radius: Appearance.rounding.full
color: Appearance.colors.colOnLayer2
opacity: 0.45
}
}
}
}
}
}
ContentSection {
icon: "timer"
title: Translation.tr("Pomodoro")
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: 8
Layout.rightMargin: 8
spacing: 6
uniformCellSizes: true
PresetButton {
label: "25/5"
onClicked: root.applyPreset(25, 5, 15, 4)
}
PresetButton {
label: "50/10"
onClicked: root.applyPreset(50, 10, 25, 4)
}
PresetButton {
label: "15/5"
onClicked: root.applyPreset(15, 5, 15, 4)
}
}
PomodoroSpinRow {
iconName: "search_activity"
label: Translation.tr("Focus")
suffix: Translation.tr("min")
from: 1
to: 180
stepSize: 5
value: root.minutes("focus")
onValueModified: value => root.setMinutes("focus", value)
}
PomodoroSpinRow {
iconName: "coffee"
label: Translation.tr("Break")
suffix: Translation.tr("min")
from: 1
to: 60
stepSize: 1
value: root.minutes("breakTime")
onValueModified: value => root.setMinutes("breakTime", value)
}
PomodoroSpinRow {
iconName: "spa"
label: Translation.tr("Long break")
suffix: Translation.tr("min")
from: 1
to: 120
stepSize: 5
value: root.minutes("longBreak")
onValueModified: value => root.setMinutes("longBreak", value)
}
PomodoroSpinRow {
iconName: "repeat"
label: Translation.tr("Cycles")
suffix: ""
from: 1
to: 12
stepSize: 1
value: Config.options.time.pomodoro.cyclesBeforeLongBreak
onValueModified: value => root.setCycles(value)
}
}
ContentSection {
icon: "notifications"
title: Translation.tr("Alerts")
ConfigRow {
uniform: true
ConfigSwitch {
buttonIcon: "notifications"
text: Translation.tr("Notifications")
checked: Config.options.time.pomodoro.notifications
onCheckedChanged: Config.options.time.pomodoro.notifications = checked
}
ConfigSwitch {
buttonIcon: "notification_sound"
text: Translation.tr("Sound")
checked: Config.options.sounds.pomodoro
onCheckedChanged: Config.options.sounds.pomodoro = checked
}
}
RippleButton {
Layout.fillWidth: true
implicitHeight: 40
buttonRadius: Appearance.rounding.full
colBackground: Appearance.colors.colLayer2
colBackgroundHover: Appearance.colors.colLayer2Hover
colRipple: Appearance.colors.colLayer2Active
onClicked: Audio.playSystemSound("alarm-clock-elapsed")
contentItem: RowLayout {
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 12
spacing: 8
MaterialSymbol {
text: "play_circle"
iconSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colOnLayer2
}
StyledText {
Layout.fillWidth: true
text: Translation.tr("Test sound")
color: Appearance.colors.colOnLayer2
}
}
}
}
Item {
Layout.fillWidth: true
implicitHeight: 4
}
}
component PresetButton: RippleButton {
id: preset
property string label
Layout.fillWidth: true
implicitHeight: 34
buttonRadius: Appearance.rounding.full
colBackground: Appearance.colors.colLayer2
colBackgroundHover: Appearance.colors.colLayer2Hover
colRipple: Appearance.colors.colLayer2Active
contentItem: StyledText {
anchors.centerIn: parent
text: preset.label
horizontalAlignment: Text.AlignHCenter
font.family: Appearance.font.family.monospace
font.weight: Font.DemiBold
color: Appearance.colors.colOnLayer2
}
}
component ProfileChip: Rectangle {
property string iconName
property string label
property color textColor
Layout.fillWidth: true
implicitHeight: 28
radius: Appearance.rounding.full
RowLayout {
anchors.centerIn: parent
spacing: 4
MaterialSymbol {
text: parent.parent.iconName
iconSize: 14
color: parent.parent.textColor
}
StyledText {
text: parent.parent.label
font.family: Appearance.font.family.monospace
font.pixelSize: Appearance.font.pixelSize.smaller
font.weight: Font.DemiBold
color: parent.parent.textColor
}
}
}
component PomodoroSpinRow: RowLayout {
id: row
property string iconName
property string label
property string suffix
property alias value: spinBox.value
property alias from: spinBox.from
property alias to: spinBox.to
property alias stepSize: spinBox.stepSize
signal valueModified(int value)
Layout.fillWidth: true
Layout.leftMargin: 8
Layout.rightMargin: 8
spacing: 10
MaterialSymbol {
text: row.iconName
iconSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colOnSecondaryContainer
}
StyledText {
Layout.fillWidth: true
text: row.label
color: Appearance.colors.colOnSecondaryContainer
}
StyledSpinBox {
id: spinBox
Layout.preferredWidth: 96
stepSize: 1
onValueModified: row.valueModified(value)
}
StyledText {
Layout.preferredWidth: 24
text: row.suffix
color: Appearance.colors.colSubtext
}
}
}

View file

@ -0,0 +1,187 @@
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
implicitHeight: contentColumn.implicitHeight
implicitWidth: contentColumn.implicitWidth
readonly property color stateColor: TimerService.pomodoroBreak ? Appearance.colors.colTertiaryContainer : Appearance.colors.colSecondaryContainer
readonly property color stateTextColor: TimerService.pomodoroBreak ? Appearance.colors.colOnTertiaryContainer : Appearance.colors.colOnSecondaryContainer
readonly property string stateLabel: TimerService.pomodoroLongBreak ? Translation.tr("Long break") : TimerService.pomodoroBreak ? Translation.tr("Break") : Translation.tr("Focus")
readonly property int cyclePosition: TimerService.pomodoroCycle + 1
ColumnLayout {
id: contentColumn
anchors.fill: parent
spacing: 10
// The Pomodoro timer circle
CircularProgress {
Layout.alignment: Qt.AlignHCenter
lineWidth: 10
value: {
return TimerService.pomodoroSecondsLeft / TimerService.pomodoroLapDuration;
}
implicitSize: 200
colPrimary: root.stateTextColor
colSecondary: root.stateColor
enableAnimation: true
ColumnLayout {
anchors.centerIn: parent
spacing: 2
StyledText {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 150
horizontalAlignment: Text.AlignHCenter
text: {
let minutes = Math.floor(TimerService.pomodoroSecondsLeft / 60).toString().padStart(2, '0');
let seconds = Math.floor(TimerService.pomodoroSecondsLeft % 60).toString().padStart(2, '0');
return `${minutes}:${seconds}`;
}
font.family: Appearance.font.family.monospace
font.pixelSize: 40
font.weight: Font.DemiBold
color: Appearance.m3colors.m3onSurface
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: root.stateLabel
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.colors.colSubtext
}
}
Rectangle {
radius: Appearance.rounding.full
color: root.stateColor
anchors {
right: parent.right
bottom: parent.bottom
}
implicitWidth: 58
implicitHeight: 36
RowLayout {
anchors.centerIn: parent
spacing: 3
MaterialSymbol {
text: "repeat"
iconSize: 14
color: root.stateTextColor
}
StyledText {
id: cycleText
font.family: Appearance.font.family.monospace
font.weight: Font.DemiBold
color: root.stateTextColor
text: `${root.cyclePosition}/${TimerService.cyclesBeforeLongBreak}`
}
}
}
}
Rectangle {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 210
Layout.preferredHeight: 30
radius: Appearance.rounding.full
color: Appearance.colors.colLayer2
RowLayout {
anchors.centerIn: parent
spacing: 6
MaterialSymbol {
text: TimerService.pomodoroBreak ? "coffee" : "search_activity"
iconSize: Appearance.font.pixelSize.larger
color: root.stateTextColor
}
StyledText {
text: root.stateLabel
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.colors.colOnLayer2
}
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 5
Repeater {
model: TimerService.cyclesBeforeLongBreak
Rectangle {
required property int index
implicitWidth: index === TimerService.pomodoroCycle ? 18 : 8
implicitHeight: 8
radius: Appearance.rounding.full
color: index <= TimerService.pomodoroCycle ? root.stateTextColor : Appearance.colors.colLayer2
Behavior on implicitWidth {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
}
// The Start/Stop and Reset buttons
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 10
RippleButton {
buttonRadius: Appearance.rounding.full
contentItem: StyledText {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: TimerService.pomodoroRunning ? Translation.tr("Pause") : (TimerService.pomodoroSecondsLeft === TimerService.focusTime) ? Translation.tr("Start") : Translation.tr("Resume")
color: TimerService.pomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
}
implicitHeight: 38
implicitWidth: 96
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: TimerService.togglePomodoro()
colBackground: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
colBackgroundHover: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
}
RippleButton {
buttonRadius: Appearance.rounding.full
implicitHeight: 38
implicitWidth: 96
onClicked: TimerService.resetPomodoro()
enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.pomodoroBreak
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
}
}
}
}
}

View file

@ -0,0 +1,68 @@
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
property var tabButtonList: [
{"name": Translation.tr("Timer"), "icon": "search_activity"},
{"name": Translation.tr("Customize"), "icon": "tune"}
]
// Pomodoro keybinds
Keys.onPressed: (event) => {
if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { // Switch tabs
if (event.key === Qt.Key_PageDown) {
tabBar.incrementCurrentIndex();
} else if (event.key === Qt.Key_PageUp) {
tabBar.decrementCurrentIndex();
}
event.accepted = true
} else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S
if (tabBar.currentIndex === 0) {
TimerService.togglePomodoro()
}
event.accepted = true
} else if (event.key === Qt.Key_R) { // Reset with R
if (tabBar.currentIndex === 0) {
TimerService.resetPomodoro()
}
event.accepted = true
}
}
ColumnLayout {
anchors.fill: parent
spacing: 0
SecondaryTabBar {
id: tabBar
currentIndex: swipeView.currentIndex
Repeater {
model: root.tabButtonList
delegate: SecondaryTabButton {
buttonText: modelData.name
buttonIcon: modelData.icon
}
}
}
SwipeView {
id: swipeView
Layout.topMargin: 10
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 10
clip: true
currentIndex: tabBar.currentIndex
// Tabs
PomodoroTimer {}
PomodoroSettings {}
}
}
}

View file

@ -0,0 +1,207 @@
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
}
}
}
}
}

View file

@ -0,0 +1,63 @@
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Layouts
Item {
id: root
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Space || event.key === Qt.Key_S) {
TimerService.toggleStopwatch();
event.accepted = true;
} else if (event.key === Qt.Key_R) {
TimerService.stopwatchReset();
event.accepted = true;
} else if (event.key === Qt.Key_L) {
TimerService.stopwatchRecordLap();
event.accepted = true;
}
}
ColumnLayout {
anchors.fill: parent
spacing: 8
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: 4
Layout.rightMargin: 14
spacing: 8
MaterialSymbol {
text: "timer"
iconSize: Appearance.font.pixelSize.hugeass
color: Appearance.colors.colOnSecondaryContainer
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
StyledText {
text: Translation.tr("Stopwatch")
font.pixelSize: Appearance.font.pixelSize.larger
font.weight: Font.Medium
color: Appearance.colors.colOnLayer1
}
StyledText {
text: TimerService.stopwatchRunning ? Translation.tr("Running") : Translation.tr("Ready")
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.colors.colSubtext
}
}
}
Stopwatch {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}

View file

@ -0,0 +1,5 @@
PomodoroSettings 1.0 PomodoroSettings.qml
PomodoroTimer 1.0 PomodoroTimer.qml
PomodoroWidget 1.0 PomodoroWidget.qml
Stopwatch 1.0 Stopwatch.qml
StopwatchWidget 1.0 StopwatchWidget.qml