Add a calendar popup to the clock

Clicking the clock reveals a month grid with ISO week numbers, a year
progress meter, and month stepping. Right click walks the common label
formats and writes the chosen one back to shell.json, so the bar shows
what the config stores. The week start toggles from the grid's "W"
heading and persists as weekStartDay, defaulting to the locale's own
first day.

Rich popup widgets live in their own plugin directories, so the clock
moves out of bar/widgets/ into panels/clock/. The id is unchanged, so
existing layouts and centerAnchor keep working.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-07-26 19:10:06 -07:00
co-authored by Claude Opus 5
parent c2610c3788
commit e2d655d265
9 changed files with 1113 additions and 98 deletions
+2 -2
View File
@@ -56,7 +56,7 @@ Example `shell.json` (bar subtree only shown):
|---|---|---|
| `omarchy.menu` | Omarchy menu launcher | left = menu · right = terminal |
| `omarchy.workspaces` | Hyprland workspace switcher | left = focus workspace |
| `omarchy.clock` | Date/time label | left = alternate format · right = timezone selector |
| `omarchy.clock` | Date/time label + popup with a month grid, ISO week numbers, and month stepping | left = popup · right = cycle label format · middle = timezone selector |
| `omarchy.media` | MPRIS now-playing — scrolling track + artist, cover-art popup | left = play/pause · middle = next · scroll = prev/next · right = popup |
| `omarchy.indicators` | Manual state indicators | left = indicator action |
| `omarchy.system-update` | Available update indicator | left = update |
@@ -166,7 +166,7 @@ Widgets receive `bar` (the shell root), `moduleName` (string), and `settings` (o
- `bar.requestPopout(owner)` / `bar.releasePopout(owner)` — one-popup-at-a-time coordinator
First-party bar widgets are manifest-backed just like third-party widgets.
Simple widgets carry sibling manifests such as `widgets/Clock.manifest.json`;
Simple widgets carry sibling manifests such as `widgets/Workspaces.manifest.json`;
richer popup plugins live in feature directories such as `../panels/audio/`,
`../panels/network/`, and `../model-usage/`; and feature plugins such as
`omarchy.menu` and `omarchy.media` declare their bar-widget entry points in their own
@@ -1,20 +0,0 @@
{
"schemaVersion": 1,
"id": "omarchy.clock",
"name": "Clock",
"version": "1.0.0",
"author": "Omarchy",
"description": "Day/time label; click to toggle alternate format",
"kinds": [
"bar-widget"
],
"entryPoints": {
"barWidget": "Clock.qml"
},
"barWidget": {
"displayName": "Clock",
"description": "Day/time label; click to toggle alternate format",
"category": "Time",
"allowMultiple": false
}
}
-92
View File
@@ -1,92 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Ui
BarWidget {
id: root
moduleName: "omarchy.clock"
property bool alt: false
property date displayDate: clock.date
readonly property string activeFormat: alt
? (vertical ? setting("verticalFormatAlt", "dd\nMMM\n'W'ww\n''yy") : setting("formatAlt", "d MMMM 'W'ww yyyy"))
: (vertical ? setting("verticalFormat", "HH\n—\nmm") : setting("format", "dddd HH:mm"))
readonly property string displayText: formatted(displayDate)
readonly property var verticalLines: displayText.split("\n")
function refresh() {
displayDate = new Date()
}
function isoWeek(date) {
var d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
var day = d.getUTCDay() || 7
d.setUTCDate(d.getUTCDate() + 4 - day)
var yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))
return Math.ceil(((d - yearStart) / 86400000 + 1) / 7)
}
function isoWeekLiteral(date) {
var week = isoWeek(date)
return (week < 10 ? "0" : "") + week
}
function formatted(date) {
return Qt.formatDateTime(date, activeFormat.replace(/ww/g, isoWeekLiteral(date)))
}
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
SystemClock {
id: clock
precision: SystemClock.Minutes
onDateChanged: root.displayDate = date
}
IpcHandler {
target: "omarchy.clock"
function refresh(): void { root.broadcast("refresh") }
}
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
text: root.vertical ? "" : root.displayText
labelVisible: !root.vertical
hasVisualContent: root.vertical ? root.verticalLines.length > 0 : text !== ""
fixedHeight: root.vertical ? root.verticalLines.length * Style.bar.iconSlot : -1
horizontalMargin: 8.75
verticalPadding: 8.75
onPressed: function(button) {
if (!root.bar) return
if (button === Qt.RightButton) root.bar.run("omarchy-menu-timezone")
else root.alt = !root.alt
}
Column {
visible: root.vertical
anchors.fill: parent
Repeater {
model: root.verticalLines
OpticalGlyph {
required property string modelData
width: button.width
height: Style.bar.iconSlot
text: modelData
fontFamily: button.fontFamily
fontSize: modelData.length > 3
? button.fontSize * 0.9
: button.fontSize
color: button.foreground
}
}
}
}
}