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 # keep-sorted start
BacklightApplet 1.0 BacklightApplet.qml BacklightApplet 1.0 BacklightApplet.qml
BatteryApplet 1.0 BatteryApplet.qml BatteryApplet 1.0 BatteryApplet.qml
ClockApplet 1.0 ClockApplet.qml
CpuApplet 1.0 CpuApplet.qml CpuApplet 1.0 CpuApplet.qml
DiskApplet 1.0 DiskApplet.qml DiskApplet 1.0 DiskApplet.qml
GpuApplet 1.0 GpuApplet.qml GpuApplet 1.0 GpuApplet.qml

View file

@ -2,14 +2,59 @@ import QtQuick
import Quickshell import Quickshell
import "." as M import "." as M
import "../services" as S import "../services" as S
import "../applets" as C
M.BarSection {
id: root
spacing: S.Theme.moduleSpacing
tooltip: ""
M.BarLabel {
SystemClock { SystemClock {
id: clock id: clock
precision: SystemClock.Seconds 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 font.pixelSize: S.Theme.fontSize + 1
label: Qt.formatDateTime(clock.date, "ddd, dd. MMM HH:mm") 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
}
}
} }