From 4941fe4bf9c65c81c4ef46511993ade7e4c83ad4 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 20 May 2026 16:59:38 +0200 Subject: [PATCH] Replace calendar widget with daytime label --- migrations/1779287628.sh | 46 +++++ shell/README.md | 8 +- shell/Ui/WidgetButton.qml | 5 +- shell/plugins/bar/Bar.qml | 4 +- shell/plugins/bar/README.md | 8 +- shell/plugins/bar/widgets/calendar.qml | 198 --------------------- shell/plugins/bar/widgets/daytime.qml | 48 +++++ shell/plugins/dev-gallery/GalleryPanel.qml | 6 +- shell/plugins/settings/SettingsPanel.qml | 22 +-- shell/shell-defaults.json | 6 +- shell/shell.qml | 6 +- 11 files changed, 127 insertions(+), 230 deletions(-) create mode 100644 migrations/1779287628.sh delete mode 100644 shell/plugins/bar/widgets/calendar.qml create mode 100644 shell/plugins/bar/widgets/daytime.qml diff --git a/migrations/1779287628.sh b/migrations/1779287628.sh new file mode 100644 index 00000000..b606ef4b --- /dev/null +++ b/migrations/1779287628.sh @@ -0,0 +1,46 @@ +echo "Rename the default Calendar bar widget to Daytime" + +config_file="$HOME/.config/omarchy/shell.json" + +if [[ -f $config_file ]] && omarchy-cmd-present jq; then + tmp=$(mktemp) + jq ' + def update_daytime_entry: + if type == "string" and (. == "calendar" or . == "datetime") then + "daytime" + elif type == "object" then + ( + if .id == "calendar" or .id == "datetime" then + .id = "daytime" + else + . + end + ) | ( + if .id == "daytime" and (.formatAlt // "") == "dd MMMM \u0027W\u0027ww yyyy" then + .formatAlt = "dd MMMM yyyy" + else + . + end + ) + else + . + end; + + .bar.centerAnchor |= if . == "calendar" or . == "datetime" then "daytime" else . end | + .bar.layout |= ( + if type == "object" then + with_entries( + .value |= ( + if type == "array" then + map(update_daytime_entry) + else + . + end + ) + ) + else + . + end + ) + ' "$config_file" >"$tmp" && mv "$tmp" "$config_file" || rm -f "$tmp" +fi diff --git a/shell/README.md b/shell/README.md index 9cfc2911..981958ed 100644 --- a/shell/README.md +++ b/shell/README.md @@ -168,10 +168,10 @@ rewrites the `bar` subtree from the current `shell-defaults.json`. "bar": { "position": "top", "transparent": false, - "centerAnchor": "calendar", + "centerAnchor": "daytime", "layout": { "left": [ { "id": "omarchy" }, { "id": "workspaces" } ], - "center": [ { "id": "calendar", "format": "HH:mm" } ], + "center": [ { "id": "daytime", "format": "HH:mm" } ], "right": [ { "id": "audioPanel" } ] @@ -194,8 +194,8 @@ rewrites the `bar` subtree from the current `shell-defaults.json`. settings UI adds/removes layout entries; other plugin kinds are enabled with the shell IPC. First-party plugins are always enabled. 4. **Multiple instances** are allowed when a manifest sets - `allowMultiple: true`. Each instance is independent — e.g. two clocks - in different timezones are just two `{"id":"calendar", "timezone": ...}` + `allowMultiple: true`. Each instance is independent — e.g. two daytime + widgets in different timezones are just two `{"id":"daytime", "timezone": ...}` entries with their own values. 5. **Idle timings are top-level.** `idle.screensaver` and `idle.lock` are seconds since user idle began, so the default lock fires at 300s diff --git a/shell/Ui/WidgetButton.qml b/shell/Ui/WidgetButton.qml index d5e2fe80..ad24ff56 100644 --- a/shell/Ui/WidgetButton.qml +++ b/shell/Ui/WidgetButton.qml @@ -21,6 +21,7 @@ Item { property bool dimmed: false property bool concealed: false property bool interactive: true + property bool pressable: true property bool useActiveColor: true property bool maintainIndicatorReveal: false property string tooltipText: "" @@ -91,7 +92,7 @@ Item { acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton enabled: root.interactive hoverEnabled: true - cursorShape: Qt.PointingHandCursor + cursorShape: root.pressable ? Qt.PointingHandCursor : Qt.ArrowCursor onEntered: { if (root.bar) { root.bar.showTooltip(root, root.tooltipText) @@ -106,7 +107,7 @@ Item { root.bar.setIndicatorItemHovered(false) } } - onClicked: function(mouse) { root.triggerPress(mouse.button) } + onClicked: function(mouse) { if (root.pressable) root.triggerPress(mouse.button) } onWheel: function(wheel) { root.wheelMoved(wheel.angleDelta.y) } } } diff --git a/shell/plugins/bar/Bar.qml b/shell/plugins/bar/Bar.qml index b89246b4..0716a0f2 100644 --- a/shell/plugins/bar/Bar.qml +++ b/shell/plugins/bar/Bar.qml @@ -34,7 +34,7 @@ Item { property var fallbackBarConfig: ({ position: "top", transparent: false, - centerAnchor: "calendar", + centerAnchor: "daytime", layout: { left: [], center: [], right: [] } }) property var layoutConfig: fallbackBarConfig.layout @@ -311,7 +311,7 @@ Item { "networkPanel": { displayName: "Network", description: "Wi-Fi list and connection state", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Network" }, "powerPanel": { displayName: "Power", description: "Battery, power profile, and system stats", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Power" }, "bluetoothPanel": { displayName: "Bluetooth", description: "Bluetooth device list with connect/disconnect", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Bluetooth" }, - "calendar": { displayName: "Calendar", description: "Clock with month-grid popup", category: "Time", allowMultiple: false, settingsForm: "calendarSettings" }, + "daytime": { displayName: "Daytime", description: "Day/time label with date tooltip", category: "Time", allowMultiple: false, settingsForm: "daytimeSettings" }, "notificationCenter": { displayName: "Notification center", description: "Recent notifications + DND", category: "Status", allowMultiple: false }, "systemStats": { displayName: "System stats", description: "CPU icon — hover for graphs, click to open btop", category: "System", allowMultiple: false }, "weather": { displayName: "Weather", description: "Weather pill with detail popup", category: "Info", allowMultiple: false, settingsForm: "weatherSettings" }, diff --git a/shell/plugins/bar/README.md b/shell/plugins/bar/README.md index 2366249c..1261056d 100644 --- a/shell/plugins/bar/README.md +++ b/shell/plugins/bar/README.md @@ -16,7 +16,7 @@ the shell for its whole session. The bar config lives under the `bar:` key of [`~/.config/omarchy/shell.json`](../../README.md#shelljson-shape). Out of the box the shell uses [`shell-defaults.json`](../../shell-defaults.json). Once you customize anything via `omarchy launch bar settings` or by editing shell.json directly, your file is canonical — there is no deep-merge. -Launch the visual editor with `omarchy launch bar settings` (or run `omarchy-launch-bar-settings`) to reorder widgets, add/remove them, and tweak per-widget options without editing JSON by hand. You can also right-click empty space to the left or right of the centered clock to open it; double-left-click the same empty space to toggle bar transparency. +Launch the visual editor with `omarchy launch bar settings` (or run `omarchy-launch-bar-settings`) to reorder widgets, add/remove them, and tweak per-widget options without editing JSON by hand. You can also right-click empty space to the left or right of the centered daytime module to open it; double-left-click the same empty space to toggle bar transparency. Example `shell.json` (bar subtree only shown): @@ -26,7 +26,7 @@ Example `shell.json` (bar subtree only shown): "bar": { "position": "top", "transparent": false, - "centerAnchor": "calendar", + "centerAnchor": "daytime", "layout": { "left": [ { "id": "omarchy" }, @@ -35,7 +35,7 @@ Example `shell.json` (bar subtree only shown): ], "center": [ { "id": "media" }, - { "id": "calendar", "format": "HH:mm" } + { "id": "daytime", "format": "HH:mm" } ], "right": [ { "id": "audioPanel" }, @@ -55,7 +55,7 @@ Example `shell.json` (bar subtree only shown): | Name | What it does | Interactions | |---|---|---| | `media` | MPRIS now-playing — scrolling track + artist, cover-art popup | left = play/pause · middle = next · scroll = prev/next · right = popup | -| `calendar` | Clock + popup with month-grid calendar | left = popup · right = tz selector | +| `daytime` | Day/time label with date tooltip | hover = date | | `notificationCenter` | Bell with badge + popup with recent notifications, DND toggle | left = popup · right = toggle DND | | `systemStats` | Inline CPU + memory sparklines, popup with detail | left = popup · right = terminal | | `weather` | Weather icon + popup with forecast | left = popup · right = full notification | diff --git a/shell/plugins/bar/widgets/calendar.qml b/shell/plugins/bar/widgets/calendar.qml deleted file mode 100644 index 067893ae..00000000 --- a/shell/plugins/bar/widgets/calendar.qml +++ /dev/null @@ -1,198 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import Quickshell -import qs.Ui -import qs.Commons - -BarWidget { - id: root - moduleName: "calendar" - - - property date now: new Date() - property date viewMonth: new Date() - property bool popupOpen: false - - function close() { popupOpen = false } - - function setting(name, fallback) { - var value = settings ? settings[name] : undefined - return value === undefined || value === null ? fallback : value - } - - function formatLabel() { - if (!bar) return "" - var fmt = bar.vertical - ? String(setting("verticalFormat", "HH\n—\nmm")) - : String(setting("format", "dddd HH:mm")) - return Qt.formatDateTime(now, fmt) - } - - function shiftMonth(delta) { - var date = new Date(viewMonth) - date.setDate(1) - date.setMonth(date.getMonth() + delta) - viewMonth = 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 tooltipLabel() { - return Qt.formatDateTime(root.now, "dd MMMM yyyy") + " Week " + root.isoWeek(root.now) - } - - implicitWidth: button.implicitWidth - implicitHeight: button.implicitHeight - - SystemClock { - id: clockTimer - precision: SystemClock.Minutes - onDateChanged: root.now = clockTimer.date - } - - WidgetButton { - id: button - anchors.fill: parent - bar: root.bar - text: root.formatLabel() - horizontalMargin: 8.75 - verticalPadding: 8.75 - tooltipText: root.tooltipLabel() - - onPressed: function(b) { - if (b === Qt.RightButton) { - root.bar.run("omarchy-launch-floating-terminal-with-presentation omarchy-tz-select") - } else { - root.viewMonth = new Date(root.now) - root.popupOpen = !root.popupOpen - } - } - } - - PopupCard { - id: popup - anchorItem: button - bar: root.bar - owner: root - open: root.popupOpen - contentWidth: popup.fittedContentWidth(Style.space(300)) - contentHeight: popup.fittedContentHeight(header.implicitHeight + grid.implicitHeight + Style.spacing.rowGap) - - Column { - anchors.fill: parent - spacing: Style.space(8) - - Item { - id: header - width: parent.width - implicitHeight: 28 - - Button { - id: prevButton - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - iconText: "󰅁" - foreground: root.bar.foreground - horizontalPadding: Style.spacing.controlGap - verticalPadding: Style.spacing.labelGap - onClicked: root.shiftMonth(-1) - } - - Text { - anchors.centerIn: parent - text: Qt.formatDate(root.viewMonth, "MMMM yyyy") - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: Style.font.title - font.bold: true - } - - Button { - id: nextButton - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - iconText: "󰅂" - foreground: root.bar.foreground - horizontalPadding: Style.spacing.controlGap - verticalPadding: Style.spacing.labelGap - onClicked: root.shiftMonth(1) - } - } - - Grid { - id: grid - columns: 7 - rowSpacing: Style.space(4) - columnSpacing: Style.space(4) - width: parent.width - - Repeater { - model: ["S", "M", "T", "W", "T", "F", "S"] - - Item { - required property string modelData - width: (grid.width - grid.columnSpacing * 6) / 7 - height: Math.max(Style.space(18), Style.font.caption + Style.spacing.xxs) - - Text { - anchors.centerIn: parent - text: modelData - color: Qt.darker(root.bar.foreground, 1.6) - font.family: root.bar.fontFamily - font.pixelSize: Style.font.bodySmall - font.bold: true - } - } - } - - Repeater { - model: 42 - - Rectangle { - required property int index - - readonly property var startOfMonth: { - var d = new Date(root.viewMonth) - d.setDate(1) - return d - } - readonly property int firstDayOffset: startOfMonth.getDay() - readonly property var dayDate: { - var d = new Date(startOfMonth) - d.setDate(d.getDate() + index - firstDayOffset) - return d - } - readonly property bool inMonth: dayDate.getMonth() === root.viewMonth.getMonth() - readonly property bool isToday: { - var n = root.now - return dayDate.getDate() === n.getDate() && dayDate.getMonth() === n.getMonth() && dayDate.getFullYear() === n.getFullYear() - } - - width: (grid.width - grid.columnSpacing * 6) / 7 - height: Math.max(Style.space(28), Style.font.body + Style.spacing.sm) - radius: 4 - color: isToday ? root.bar.foreground : "transparent" - border.color: isToday ? root.bar.foreground : "transparent" - border.width: isToday ? 1 : 0 - - Text { - anchors.centerIn: parent - text: dayDate.getDate() - color: isToday ? root.bar.background : (inMonth ? root.bar.foreground : Qt.darker(root.bar.foreground, 2.2)) - font.family: root.bar.fontFamily - font.pixelSize: Style.font.body - font.bold: isToday - } - } - } - } - } - } - -} diff --git a/shell/plugins/bar/widgets/daytime.qml b/shell/plugins/bar/widgets/daytime.qml new file mode 100644 index 00000000..e85b0ff3 --- /dev/null +++ b/shell/plugins/bar/widgets/daytime.qml @@ -0,0 +1,48 @@ +import QtQuick +import Quickshell +import qs.Commons +import qs.Ui + +BarWidget { + id: root + moduleName: "daytime" + + property date now: new Date() + + function setting(name, fallback) { + var value = settings ? settings[name] : undefined + return value === undefined || value === null ? fallback : value + } + + function formatLabel() { + if (!bar) return "" + var fmt = bar.vertical + ? String(setting("verticalFormat", "HH\n—\nmm")) + : String(setting("format", "dddd HH:mm")) + return Qt.formatDateTime(now, fmt) + } + + function tooltipLabel() { + return Qt.formatDateTime(root.now, String(setting("formatAlt", "dd MMMM yyyy"))) + } + + implicitWidth: button.implicitWidth + implicitHeight: button.implicitHeight + + SystemClock { + id: clockTimer + precision: SystemClock.Minutes + onDateChanged: root.now = clockTimer.date + } + + WidgetButton { + id: button + anchors.fill: parent + bar: root.bar + text: root.formatLabel() + horizontalMargin: 8.75 + verticalPadding: 8.75 + tooltipText: root.tooltipLabel() + pressable: false + } +} diff --git a/shell/plugins/dev-gallery/GalleryPanel.qml b/shell/plugins/dev-gallery/GalleryPanel.qml index 6e380059..c7ddb4cc 100644 --- a/shell/plugins/dev-gallery/GalleryPanel.qml +++ b/shell/plugins/dev-gallery/GalleryPanel.qml @@ -105,7 +105,7 @@ Item { property string choiceDemoValue: "top" property bool toggleDemoOn: true property bool toggleSquareOn: false - property string dropdownDemoValue: "calendar" + property string dropdownDemoValue: "daytime" property string searchableDemoValue: "" property int numberDemoValue: 15 @@ -1523,7 +1523,7 @@ Item { width: Style.spacing.dropdownWidth label: "Center anchor" fontFamily: root.fontFamily - options: ["calendar", "weather", "clock", "powerPanel"] + options: ["daytime", "weather", "clock", "powerPanel"] value: root.dropdownDemoValue hasCursor: root.focusSection === "dropdown" && root.selectedIndex === 0 onHovered: function(h) { @@ -1593,7 +1593,7 @@ Item { { value: "network", label: "Network", description: "Wi-Fi + ethernet status" }, { value: "bluetooth", label: "Bluetooth", description: "Paired and nearby devices" }, { value: "monitor", label: "Monitor", description: "Brightness + scale" }, - { value: "calendar", label: "Calendar", description: "Month grid flyout" }, + { value: "daytime", label: "Daytime", description: "Day/time label" }, { value: "media", label: "Media", description: "Now-playing + transport" }, { value: "workspaces", label: "Workspaces", description: "Hyprland workspace pills" }, { value: "system-tray", label: "System tray", description: "StatusNotifierItem icons" }, diff --git a/shell/plugins/settings/SettingsPanel.qml b/shell/plugins/settings/SettingsPanel.qml index d832cc08..d20b5cc6 100644 --- a/shell/plugins/settings/SettingsPanel.qml +++ b/shell/plugins/settings/SettingsPanel.qml @@ -128,11 +128,11 @@ Item { bar: { position: "top", transparent: false, - centerAnchor: "calendar", + centerAnchor: "daytime", layout: { left: [{ id: "omarchy" }, { id: "workspaces" }], center: [ - { id: "calendar", format: "dddd HH:mm", formatAlt: "dd MMMM 'W'ww yyyy", verticalFormat: "HH\n\u2014\nmm" }, + { id: "daytime", format: "dddd HH:mm", formatAlt: "dd MMMM yyyy", verticalFormat: "HH\n\u2014\nmm" }, { id: "weather" }, { id: "indicators", items: [ "dnd", "nightlight", "stayAwake", "screenrecording", "dictation" ] }, { id: "update" } ], right: [ @@ -145,7 +145,7 @@ Item { }) property var defaultConfig: builtinShellConfig - property var draft: ({ version: 1, bar: { position: "top", transparent: false, centerAnchor: "calendar", layout: { left: [], center: [], right: [] } }, plugins: [] }) + property var draft: ({ version: 1, bar: { position: "top", transparent: false, centerAnchor: "daytime", layout: { left: [], center: [], right: [] } }, plugins: [] }) property int draftRevision: 0 property bool suppressReload: false @@ -1135,7 +1135,7 @@ Item { if (meta && meta.settingsForm) { switch (meta.settingsForm) { case "spacerSettings": return spacerSettingsComponent - case "calendarSettings": return calendarSettingsComponent + case "daytimeSettings": return daytimeSettingsComponent case "weatherSettings": return weatherSettingsComponent } } @@ -1177,7 +1177,7 @@ Item { } Component { - id: calendarSettingsComponent + id: daytimeSettingsComponent Column { id: calForm @@ -1187,7 +1187,7 @@ Item { spacing: Style.spacing.rowGap width: parent ? parent.width : 0 - component CalendarField: TextField { + component DaytimeField: TextField { property string fieldKey: "" width: parent.width foreground: root.foreground @@ -1203,20 +1203,20 @@ Item { font.family: root.fontFamily font.pixelSize: Style.font.bodySmall } - CalendarField { + DaytimeField { fieldKey: "format" text: calForm.entry.format || "dddd HH:mm" } Text { - text: "Alternate format (click to swap)" + text: "Tooltip format" color: Qt.darker(root.foreground, 1.4) font.family: root.fontFamily font.pixelSize: Style.font.bodySmall } - CalendarField { + DaytimeField { fieldKey: "formatAlt" - text: calForm.entry.formatAlt || "dd MMMM 'W'ww yyyy" + text: calForm.entry.formatAlt || "dd MMMM yyyy" } Text { @@ -1225,7 +1225,7 @@ Item { font.family: root.fontFamily font.pixelSize: Style.font.bodySmall } - CalendarField { + DaytimeField { fieldKey: "verticalFormat" text: calForm.entry.verticalFormat || "HH\n—\nmm" } diff --git a/shell/shell-defaults.json b/shell/shell-defaults.json index 6b0b9546..e90f39da 100644 --- a/shell/shell-defaults.json +++ b/shell/shell-defaults.json @@ -7,7 +7,7 @@ "bar": { "position": "top", "transparent": false, - "centerAnchor": "calendar", + "centerAnchor": "daytime", "layout": { "left": [ { @@ -19,9 +19,9 @@ ], "center": [ { - "id": "calendar", + "id": "daytime", "format": "dddd HH:mm", - "formatAlt": "dd MMMM 'W'ww yyyy", + "formatAlt": "dd MMMM yyyy", "verticalFormat": "HH\n\u2014\nmm" }, { diff --git a/shell/shell.qml b/shell/shell.qml index a6ca3cc0..b71be97b 100644 --- a/shell/shell.qml +++ b/shell/shell.qml @@ -41,10 +41,10 @@ ShellRoot { bar: { position: "top", transparent: false, - centerAnchor: "calendar", + centerAnchor: "daytime", layout: { left: [{ id: "omarchy" }, { id: "workspaces" }], - center: [{ id: "calendar", format: "dddd HH:mm" }], + center: [{ id: "daytime", format: "dddd HH:mm" }], right: [{ id: "audioPanel" }] } }, @@ -488,7 +488,7 @@ ShellRoot { // Mirror plugin registry state into BarWidgetRegistry whenever it changes. // Each enabled plugin with kind "bar-widget" gets a Component created from // its manifest entry point and registered under its plain manifest id. - // First-party widget ids (calendar, weather, etc.) are short and don't + // First-party widget ids (daytime, weather, etc.) are short and don't // collide with namespaced plugin ids, so we don't need a separate // "plugin:" namespace anymore. Connections {