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>
176 lines
5.8 KiB
QML
176 lines
5.8 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.Commons
|
|
import qs.Ui
|
|
import "Model.js" as Model
|
|
|
|
// Date/time label for the bar, and the host for the calendar popup.
|
|
//
|
|
// Left click reveals the calendar — asking "what is the date?" is what a
|
|
// click on a clock means — right click walks the common label formats, and
|
|
// middle click opens the timezone picker.
|
|
BarWidget {
|
|
id: root
|
|
moduleName: "omarchy.clock"
|
|
|
|
property date displayDate: clock.date
|
|
|
|
readonly property string configuredFormat: vertical
|
|
? setting("verticalFormat", "HH\n—\nmm")
|
|
: setting("format", "dddd HH:mm")
|
|
readonly property string configuredAltFormat: vertical
|
|
? setting("verticalFormatAlt", "dd\nMMM\n'W'ww\n''yy")
|
|
: setting("formatAlt", "d MMMM 'W'ww yyyy")
|
|
|
|
readonly property var formatRing: Model.clockFormatRing(configuredFormat, configuredAltFormat, Model.clockFormats(vertical))
|
|
|
|
// What the bar shows is what shell.json stores, so a cycled format is the
|
|
// format from then on rather than something that reverts on restart.
|
|
readonly property string activeFormat: configuredFormat
|
|
readonly property string displayText: formatted(displayDate)
|
|
readonly property var verticalLines: displayText.split("\n")
|
|
|
|
function refresh() {
|
|
displayDate = new Date()
|
|
if (panelLoader.item && panelLoader.item.refresh) panelLoader.item.refresh()
|
|
}
|
|
|
|
function cycleFormat() {
|
|
var current = String(configuredFormat)
|
|
var next = Model.nextClockFormat(formatRing, current)
|
|
if (next === "" || next === current) return
|
|
|
|
var entry = { id: root.moduleName }
|
|
for (var key in root.settings) if (key !== "id") entry[key] = root.settings[key]
|
|
entry[vertical ? "verticalFormat" : "format"] = next
|
|
|
|
// Applied locally first so the label changes on the click itself; the
|
|
// shell.json write comes back through the bar as the same value.
|
|
root.settings = entry
|
|
if (root.bar && root.bar.shell && typeof root.bar.shell.updateEntryInline === "function")
|
|
root.bar.shell.updateEntryInline(root.moduleName, entry)
|
|
}
|
|
|
|
function formatted(date) {
|
|
return Qt.formatDateTime(date, activeFormat.replace(/ww/g, Model.isoWeekLiteral(date.getFullYear(), date.getMonth(), date.getDate())))
|
|
}
|
|
|
|
// ---- Calendar popup. Shape contract for shell.summon/hide/toggle
|
|
// routing: Bar.findPanelWidget requires open/close/opened on the
|
|
// bar-widget root.
|
|
readonly property bool opened: panelLoader.item ? panelLoader.item.opened === true : false
|
|
|
|
function open() {
|
|
if (panelLoader.item) panelLoader.item.open()
|
|
}
|
|
|
|
function close() {
|
|
if (panelLoader.item) panelLoader.item.close()
|
|
}
|
|
|
|
function togglePanel() {
|
|
if (panelLoader.item) panelLoader.item.toggle()
|
|
}
|
|
|
|
// The clock fills more slot than it paints a mark for, at both
|
|
// orientations: horizontally it is a text label in a padded slot, so the
|
|
// dot takes the label width; vertically it is a stack of icon-sized lines,
|
|
// so the dot takes one line — the same mark every icon widget gets, rather
|
|
// than a rule running the height of the whole stack.
|
|
readonly property real openPanelIndicatorWidth: button.labelWidth
|
|
readonly property real openPanelIndicatorHeight: Math.max(Style.space(10), Math.round(Style.bar.iconSlot * 0.55))
|
|
|
|
// Forwarded so this widget can stand in for the panel as the bar's popout
|
|
// identity: Bar.requestPopout prefers closeForPopoutSwitch over close, and
|
|
// KeyboardPanel reads popoutSwitchClosing back off its owner.
|
|
readonly property bool popoutSwitchClosing: panelLoader.item ? panelLoader.item.popoutSwitchClosing === true : false
|
|
|
|
function closeForPopoutSwitch() {
|
|
if (panelLoader.item) panelLoader.item.closeForPopoutSwitch()
|
|
}
|
|
|
|
function injectPanel() {
|
|
var target = panelLoader.item
|
|
if (!target) return
|
|
if ("bar" in target) target.bar = root.bar
|
|
if ("settings" in target) target.settings = root.settings
|
|
if ("anchorItem" in target) target.anchorItem = button
|
|
if ("hostWidget" in target) target.hostWidget = root
|
|
}
|
|
|
|
implicitWidth: button.implicitWidth
|
|
implicitHeight: button.implicitHeight
|
|
|
|
onBarChanged: injectPanel()
|
|
onSettingsChanged: injectPanel()
|
|
|
|
SystemClock {
|
|
id: clock
|
|
precision: SystemClock.Minutes
|
|
onDateChanged: root.displayDate = date
|
|
}
|
|
|
|
Loader {
|
|
id: panelLoader
|
|
active: true
|
|
source: Qt.resolvedUrl("Panel.qml")
|
|
visible: false
|
|
onLoaded: {
|
|
root.injectPanel()
|
|
Qt.callLater(root.injectPanel)
|
|
}
|
|
}
|
|
|
|
IpcHandler {
|
|
target: "omarchy.clock"
|
|
|
|
function refresh(): void { root.broadcast("refresh") }
|
|
function cycleFormat(): void { root.cycleFormat() }
|
|
function open(): void { root.open() }
|
|
function close(): void { root.close() }
|
|
function show(): void { root.open() }
|
|
function hide(): void { root.close() }
|
|
function toggle(): void { root.togglePanel() }
|
|
}
|
|
|
|
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(b) {
|
|
if (b === Qt.RightButton) root.cycleFormat()
|
|
else if (b === Qt.MiddleButton) { if (root.bar) root.bar.run("omarchy-menu-timezone") }
|
|
else root.togglePanel()
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|