Files
dotfiles/quickshell/popouts/CalendarPopout.qml
T
2026-08-10 23:57:26 +02:00

261 lines
7.9 KiB
QML

import QtQuick
import Quickshell
import "root:/config"
import "root:/components"
import "root:/services"
// Month grid with the current day slashed in crimson, plus uptime and a couple
// of world clocks.
Popout {
id: root
property date clockDate: new Date()
contentWidth: 320
contentHeight: 352
// Month currently on display, as an offset from the real one.
property int monthOffset: 0
onShownChanged: if (!shown) monthOffset = 0
readonly property date viewDate: {
const d = new Date(root.clockDate);
d.setDate(1);
d.setMonth(d.getMonth() + root.monthOffset);
return d;
}
readonly property int viewYear: viewDate.getFullYear()
readonly property int viewMonth: viewDate.getMonth()
// Monday-first grid, 6 rows of 7.
readonly property var cells: {
const first = new Date(root.viewYear, root.viewMonth, 1);
const lead = (first.getDay() + 6) % 7;
const start = new Date(root.viewYear, root.viewMonth, 1 - lead);
const out = [];
for (let i = 0; i < 42; i++) {
const d = new Date(start);
d.setDate(start.getDate() + i);
out.push({
day: d.getDate(),
inMonth: d.getMonth() === root.viewMonth,
today: d.toDateString() === root.clockDate.toDateString()
});
}
return out;
}
PopoutSurface {
anchors.fill: parent
tone: Theme.accent
PopoutHeader {
id: header
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
title: Qt.formatDateTime(root.viewDate, "MMMM")
icon: "󰃭"
accent: Theme.accent
subtitle: Qt.formatDateTime(root.viewDate, "yyyy")
}
// Month stepper.
Row {
anchors.top: header.bottom
anchors.topMargin: Theme.padS
anchors.right: parent.right
spacing: Theme.padS
z: 2
StepButton {
glyph: "󰅁"
onClicked: root.monthOffset--
}
StepButton {
glyph: "󰅂"
onClicked: root.monthOffset++
}
}
// Weekday header, clear of the month stepper above it.
Row {
id: dayNames
anchors.top: header.bottom
anchors.topMargin: 36
anchors.left: parent.left
anchors.right: parent.right
Repeater {
model: ["MO", "TU", "WE", "TH", "FR", "SA", "SU"]
Text {
required property var modelData
required property int index
width: dayNames.width / 7
horizontalAlignment: Text.AlignHCenter
text: modelData
color: index >= 5 ? Theme.alpha(Theme.accent, 0.8) : Theme.muted
font.family: Theme.fontMono
font.pixelSize: 8
font.letterSpacing: 1
renderType: Text.NativeRendering
}
}
}
Grid {
id: grid
anchors.top: dayNames.bottom
anchors.topMargin: Theme.padS
anchors.left: parent.left
anchors.right: parent.right
columns: 7
Repeater {
model: root.cells
Item {
id: cell
required property var modelData
width: grid.width / 7
height: 30
// Today gets a leaning crimson slab.
Rectangle {
anchors.centerIn: parent
width: 26
height: 24
visible: cell.modelData.today
color: Theme.accent
transform: Matrix4x4 {
matrix: Qt.matrix4x4(1, -Theme.skew, 0, 24 * Theme.skew,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
}
}
Text {
anchors.centerIn: parent
text: cell.modelData.day
color: {
if (cell.modelData.today) return Theme.text;
if (!cell.modelData.inMonth) return Theme.alpha(Theme.muted, 0.45);
return Theme.subtext;
}
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
font.italic: true
font.weight: cell.modelData.today ? Font.Black : Font.Medium
renderType: Text.NativeRendering
}
}
}
}
// Footer: uptime and a second time zone.
Row {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
Column {
width: parent.width / 2
spacing: -2
Text {
text: "UPTIME"
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: 8
font.letterSpacing: 1.5
renderType: Text.NativeRendering
}
Text {
text: Sys.uptime
color: Theme.text
font.family: Theme.fontMono
font.pixelSize: Theme.fsSmall
font.weight: Font.DemiBold
renderType: Text.NativeRendering
}
}
Column {
width: parent.width / 2
spacing: -2
Text {
anchors.right: parent.right
text: "TOKYO"
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: 8
font.letterSpacing: 1.5
renderType: Text.NativeRendering
}
Text {
anchors.right: parent.right
text: {
// Tokyo is UTC+9 year-round.
const utc = root.clockDate.getTime()
+ root.clockDate.getTimezoneOffset() * 60000;
const tokyo = new Date(utc + 9 * 3600000);
return Qt.formatDateTime(tokyo, "HH:mm");
}
color: Theme.primary
font.family: Theme.fontMono
font.pixelSize: Theme.fsSmall
font.weight: Font.DemiBold
renderType: Text.NativeRendering
}
}
}
}
component StepButton: Item {
id: step
property string glyph: ""
signal clicked()
implicitWidth: 22
implicitHeight: 22
Rectangle {
anchors.fill: parent
color: stepHover.hovered ? Theme.alpha(Theme.accent, 0.25) : "transparent"
border.width: 1
border.color: Theme.alpha(Theme.outline, 0.7)
transform: Matrix4x4 {
matrix: Qt.matrix4x4(1, -Theme.skew, 0, 22 * Theme.skew,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
}
}
Icon {
anchors.centerIn: parent
text: step.glyph
color: stepHover.hovered ? Theme.text : Theme.subtext
font.pixelSize: Theme.fsBody
}
HoverHandler {
id: stepHover
cursorShape: Qt.PointingHandCursor
}
TapHandler {
onTapped: step.clicked()
}
}
}