refactor: enhance TimerPage layout and functionality; update theme and tokens for improved design consistency
- Refactored TimerPage component to improve layout and user experience. - Updated theme configuration to utilize design tokens for better maintainability. - Introduced new tokens for colors and typography to align with design principles. - Simplified state management and improved timer functionality. - Enhanced accessibility and responsiveness of UI elements.
This commit is contained in:
84 files changed
+4501
-2682
No files matched your search
@@ -0,0 +1,241 @@
|
||||
// The floating mini timer.
|
||||
//
|
||||
// A small pill you keep in a corner while you work: the countdown, what you are
|
||||
// working on, and the three controls you actually reach for. It appears on
|
||||
// demand (tray, Ctrl+M, the D-Bus verb) and — if you let it — by itself
|
||||
// whenever an interval is running and the main window is out of the way.
|
||||
//
|
||||
// Everything has its own row. An earlier version saved space by swapping the
|
||||
// task line for the controls on hover and by drawing the progress bar over the
|
||||
// bottom edge; both of those put things on top of each other, and a timer you
|
||||
// have to hover to operate is a timer you cannot operate at a glance.
|
||||
//
|
||||
// Dragging uses the compositor's own move (`startSystemMove`), the only thing
|
||||
// that works on Wayland, and the window refuses keyboard focus rather than
|
||||
// being a `Qt.Tool` — Qt hides tool windows whenever the application is
|
||||
// deactivated, which made this one vanish on every click in another app.
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
|
||||
import "."
|
||||
|
||||
Window {
|
||||
id: mini
|
||||
|
||||
width: 252
|
||||
height: card.implicitHeight + 12
|
||||
color: "transparent"
|
||||
|
||||
// KWin's rule matches a substring of this, so it must stay distinct from
|
||||
// the main window's caption. Qt appends " — Kadence" to it.
|
||||
title: "Kadence mini"
|
||||
|
||||
flags: Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
|
||||
| Qt.WindowDoesNotAcceptFocus
|
||||
|
||||
readonly property color accent: Theme.kindColor(controller.kind)
|
||||
readonly property bool paused: controller.status === "paused"
|
||||
|
||||
// Placement is decided in app.py: it is the only piece that knows about
|
||||
// screens, and on Wayland it has to go through a KWin rule anyway.
|
||||
onXChanged: saveTimer.restart()
|
||||
onYChanged: saveTimer.restart()
|
||||
Timer {
|
||||
id: saveTimer
|
||||
interval: 800
|
||||
onTriggered: if (mini.visible) controller.saveMiniPosition(mini.x, mini.y)
|
||||
}
|
||||
|
||||
// A small pill button. `filled` marks the one action you want by default —
|
||||
// Resume when paused, Start when idle.
|
||||
component MiniButton: Rectangle {
|
||||
id: button
|
||||
property string label: ""
|
||||
property bool filled: false
|
||||
signal clicked()
|
||||
|
||||
implicitWidth: buttonText.implicitWidth + 18
|
||||
implicitHeight: 24
|
||||
radius: 7
|
||||
color: filled
|
||||
? (buttonHover.hovered ? Qt.lighter(mini.accent, 1.08) : mini.accent)
|
||||
: (buttonHover.hovered ? Qt.alpha(mini.accent, 0.12) : "transparent")
|
||||
border.width: filled ? 0 : 1
|
||||
border.color: buttonHover.hovered ? mini.accent : Theme.rule
|
||||
Behavior on color { ColorAnimation { duration: 110 } }
|
||||
|
||||
Label {
|
||||
id: buttonText
|
||||
anchors.fill: parent
|
||||
text: button.label
|
||||
color: button.filled ? "white" : Theme.ink
|
||||
font.pixelSize: 11
|
||||
font.weight: button.filled ? Font.DemiBold : Font.Normal
|
||||
font.family: Theme.sansFamily
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
HoverHandler { id: buttonHover; cursorShape: Qt.PointingHandCursor }
|
||||
TapHandler { onTapped: button.clicked() }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
radius: 14
|
||||
color: Theme.surface
|
||||
border.width: 1
|
||||
border.color: Theme.rule
|
||||
implicitHeight: body.implicitHeight + 24 + progressLane.height + 5
|
||||
|
||||
HoverHandler { id: hover }
|
||||
|
||||
DragHandler {
|
||||
target: null
|
||||
grabPermissions: PointerHandler.CanTakeOverFromAnything
|
||||
onActiveChanged: if (active) mini.startSystemMove()
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onDoubleTapped: controller.showWindowRequested()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: body
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 12
|
||||
spacing: 3
|
||||
|
||||
// ---- state -------------------------------------------------------
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 7
|
||||
|
||||
Rectangle {
|
||||
width: 6; height: 6; radius: 3
|
||||
color: controller.running ? mini.accent : Theme.faint
|
||||
SequentialAnimation on opacity {
|
||||
running: controller.running && !mini.paused
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { to: 0.3; duration: 900 }
|
||||
NumberAnimation { to: 1.0; duration: 900 }
|
||||
}
|
||||
}
|
||||
Label {
|
||||
text: (controller.running ? controller.kindLabel : "Idle").toUpperCase()
|
||||
+ (mini.paused ? " · PAUSED" : "")
|
||||
color: mini.paused ? Theme.amber : (controller.running ? mini.accent : Theme.muted)
|
||||
font.pixelSize: 9
|
||||
font.letterSpacing: 1.2
|
||||
font.weight: Font.DemiBold
|
||||
font.family: Theme.sansFamily
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Label {
|
||||
text: controller.completedToday + "/" + controller.goal
|
||||
color: Theme.faint
|
||||
font.pixelSize: 10
|
||||
font.family: Theme.sansFamily
|
||||
}
|
||||
Label {
|
||||
text: "✕"
|
||||
color: closeHover.hovered ? Theme.ink : Theme.faint
|
||||
font.pixelSize: 11
|
||||
opacity: hover.hovered ? 1 : 0.35
|
||||
Behavior on opacity { NumberAnimation { duration: 120 } }
|
||||
HoverHandler { id: closeHover; cursorShape: Qt.PointingHandCursor }
|
||||
TapHandler { onTapped: controller.setMiniVisible(false) }
|
||||
}
|
||||
}
|
||||
|
||||
// ---- the time ----------------------------------------------------
|
||||
Label {
|
||||
text: controller.clockText
|
||||
color: Theme.ink
|
||||
opacity: mini.paused ? 0.55 : 1
|
||||
font.pixelSize: 32
|
||||
font.family: Theme.displayFamily
|
||||
font.letterSpacing: -1
|
||||
Layout.topMargin: -2
|
||||
Behavior on opacity { NumberAnimation { duration: 150 } }
|
||||
}
|
||||
|
||||
// ---- what you are on ---------------------------------------------
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: controller.taskTitle !== ""
|
||||
? controller.taskTitle
|
||||
: (controller.running ? "no task attached" : "double-click to open Kadence")
|
||||
color: Theme.muted
|
||||
font.pixelSize: 11
|
||||
font.family: Theme.sansFamily
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// ---- controls, always visible --------------------------------------
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 4
|
||||
spacing: 6
|
||||
|
||||
MiniButton {
|
||||
label: mini.paused ? "Resume" : "Pause"
|
||||
filled: mini.paused
|
||||
visible: controller.running
|
||||
onClicked: mini.paused ? controller.resume() : controller.pause()
|
||||
}
|
||||
MiniButton {
|
||||
label: "Finish"
|
||||
visible: controller.running
|
||||
onClicked: controller.complete()
|
||||
}
|
||||
MiniButton {
|
||||
label: "+5"
|
||||
visible: controller.running
|
||||
onClicked: controller.extend(5)
|
||||
}
|
||||
MiniButton {
|
||||
label: "Start focus"
|
||||
filled: true
|
||||
visible: !controller.running
|
||||
onClicked: controller.startWork()
|
||||
}
|
||||
MiniButton {
|
||||
label: "Break"
|
||||
visible: !controller.running
|
||||
onClicked: controller.startBreak()
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
}
|
||||
|
||||
// ---- progress, in a lane of its own ----------------------------------
|
||||
Item {
|
||||
id: progressLane
|
||||
objectName: "progressLane"
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.leftMargin: 1
|
||||
anchors.rightMargin: 1
|
||||
anchors.bottomMargin: 1
|
||||
height: 3
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * Math.max(0, Math.min(1, controller.progress))
|
||||
height: parent.height
|
||||
color: mini.accent
|
||||
opacity: controller.running ? (mini.paused ? 0.45 : 1) : 0.25
|
||||
Behavior on width { NumberAnimation { duration: 400 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user