From 472b4e62ab883bbc5c1f5f6bce548741b8554063 Mon Sep 17 00:00:00 2001 From: Damocles Date: Wed, 22 Apr 2026 22:34:33 +0200 Subject: [PATCH] add ClockApplet with locale-aware calendar grid, refactor ClockModule with hover+pin --- shell/applets/ClockApplet.qml | 148 ++++++++++++++++++++++++++++++++++ shell/applets/qmldir | 1 + shell/modules/ClockModule.qml | 53 +++++++++++- 3 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 shell/applets/ClockApplet.qml diff --git a/shell/applets/ClockApplet.qml b/shell/applets/ClockApplet.qml new file mode 100644 index 0000000..ac46380 --- /dev/null +++ b/shell/applets/ClockApplet.qml @@ -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 + } +} diff --git a/shell/applets/qmldir b/shell/applets/qmldir index 13113c1..f74b6f7 100644 --- a/shell/applets/qmldir +++ b/shell/applets/qmldir @@ -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 diff --git a/shell/modules/ClockModule.qml b/shell/modules/ClockModule.qml index 8d31b55..fa06723 100644 --- a/shell/modules/ClockModule.qml +++ b/shell/modules/ClockModule.qml @@ -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 } - 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") + 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") + 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 + } + } }