add ClockApplet with locale-aware calendar grid, refactor ClockModule with hover+pin

This commit is contained in:
Damocles 2026-04-22 22:34:33 +02:00
parent 0e9c1723f2
commit 472b4e62ab
3 changed files with 198 additions and 4 deletions

View file

@ -0,0 +1,148 @@
import QtQuick
import "../services" as S
Column {
id: root
required property color accentColor
required property date currentDate
readonly property var _locale: Qt.locale()
readonly property int _year: currentDate.getFullYear()
readonly property int _month: currentDate.getMonth()
readonly property int _day: currentDate.getDate()
readonly property int _firstDayOfWeek: _locale.firstDayOfWeek
// Week number (ISO 8601)
function _weekNumber(d) {
const tmp = new Date(d.getTime());
tmp.setHours(0, 0, 0, 0);
tmp.setDate(tmp.getDate() + 3 - (tmp.getDay() + 6) % 7);
const jan4 = new Date(tmp.getFullYear(), 0, 4);
return 1 + Math.round(((tmp.getTime() - jan4.getTime()) / 86400000 - 3 + (jan4.getDay() + 6) % 7) / 7);
}
// Build 6x7 grid of day numbers for the current month view
readonly property var _calendarDays: {
const first = new Date(_year, _month, 1);
const firstDow = first.getDay();
// Offset: how many days from previous month to show
let offset = (firstDow - _firstDayOfWeek + 7) % 7;
const startDate = new Date(_year, _month, 1 - offset);
const days = [];
for (let i = 0; i < 42; i++) {
const d = new Date(startDate.getTime() + i * 86400000);
days.push({
day: d.getDate(),
month: d.getMonth(),
year: d.getFullYear(),
isCurrentMonth: d.getMonth() === _month,
isToday: d.getDate() === _day && d.getMonth() === _month && d.getFullYear() === _year
});
}
return days;
}
// Weekday header names (short, locale-aware)
readonly property var _dayNames: {
const names = [];
for (let i = 0; i < 7; i++) {
const idx = (_firstDayOfWeek + i) % 7;
names.push(_locale.dayName(idx, Locale.NarrowFormat));
}
return names;
}
// Date header
Item {
width: parent.width
height: 28
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: root._locale.standaloneMonthName(root._month, Locale.LongFormat) + " " + root._year
color: root.accentColor
font.pixelSize: S.Theme.fontSize
font.family: S.Theme.fontFamily
font.bold: true
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: "W" + root._weekNumber(root.currentDate)
color: S.Theme.base04
font.pixelSize: S.Theme.fontSize - 2
font.family: S.Theme.fontFamily
}
}
// Day-of-week headers
Row {
anchors.horizontalCenter: parent.horizontalCenter
Repeater {
model: root._dayNames
Text {
required property string modelData
width: Math.floor((root.width - 24) / 7)
height: 18
horizontalAlignment: Text.AlignHCenter
text: modelData
color: S.Theme.base04
font.pixelSize: S.Theme.fontSize - 3
font.family: S.Theme.fontFamily
font.bold: true
}
}
}
// Calendar grid (6 rows x 7 cols)
Grid {
anchors.horizontalCenter: parent.horizontalCenter
columns: 7
Repeater {
model: root._calendarDays
Item {
required property var modelData
width: Math.floor((root.width - 24) / 7)
height: Math.floor((root.width - 24) / 7)
Rectangle {
anchors.centerIn: parent
width: Math.min(parent.width, parent.height) - 4
height: width
radius: width / 2
color: modelData.isToday ? root.accentColor : "transparent"
}
Text {
anchors.centerIn: parent
text: modelData.day
color: modelData.isToday ? S.Theme.base00 : modelData.isCurrentMonth ? S.Theme.base05 : S.Theme.base03
font.pixelSize: S.Theme.fontSize - 1
font.family: S.Theme.fontFamily
font.bold: modelData.isToday
}
}
}
}
// Full date line
Text {
width: parent.width - 24
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: root.currentDate.toLocaleDateString(root._locale, Locale.LongFormat)
color: S.Theme.base04
font.pixelSize: S.Theme.fontSize - 2
font.family: S.Theme.fontFamily
}
Item {
width: 1
height: 4
}
}

View file

@ -2,6 +2,7 @@ module applets
# keep-sorted start
BacklightApplet 1.0 BacklightApplet.qml
BatteryApplet 1.0 BatteryApplet.qml
ClockApplet 1.0 ClockApplet.qml
CpuApplet 1.0 CpuApplet.qml
DiskApplet 1.0 DiskApplet.qml
GpuApplet 1.0 GpuApplet.qml

View file

@ -2,14 +2,59 @@ import QtQuick
import Quickshell
import "." as M
import "../services" as S
import "../applets" as C
M.BarSection {
id: root
spacing: S.Theme.moduleSpacing
tooltip: ""
M.BarLabel {
SystemClock {
id: clock
precision: SystemClock.Seconds
}
property bool _pinned: false
readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered
readonly property bool _showPanel: _anyHover || _pinned
on_AnyHoverChanged: {
if (_anyHover)
_unpinTimer.stop();
else if (_pinned)
_unpinTimer.start();
}
Timer {
id: _unpinTimer
interval: 500
onTriggered: root._pinned = false
}
M.BarLabel {
font.pixelSize: S.Theme.fontSize + 1
label: Qt.formatDateTime(clock.date, "ddd, dd. MMM HH:mm")
tooltip: Qt.formatDateTime(clock.date, "dddd, dd. MMMM yyyy\nHH:mm:ss")
minText: "Wed, 00. Sep 00:00"
anchors.verticalCenter: parent.verticalCenter
TapHandler {
onTapped: root._pinned = !root._pinned
}
}
M.HoverPanel {
id: hoverPanel
showPanel: root._showPanel
screen: QsWindow.window?.screen ?? null
anchorItem: root
accentColor: root.accentColor
panelNamespace: "nova-clock"
panelTitle: Qt.formatTime(clock.date, "HH:mm:ss")
contentWidth: 220
C.ClockApplet {
width: hoverPanel.contentWidth
accentColor: root.accentColor
currentDate: clock.date
}
}
}