Files
omarchycn/shell/plugins/panels/clock/BarWidget.qml
T
David Heinemeier HanssonandClaude Fable 5 f54edbeba8 Expose the shell's remaining UI-only toggles over IPC
Every toggle you could only reach by mouse or panel hotkey is now
scriptable through omarchy-shell:

- omarchy.bluetooth toggleBluetooth — the radio (was right-click / B)
- omarchy.network toggleNetwork — Wi-Fi on/off (was W in the panel)
- omarchy.tailscale toggleTailscale — the connection as one call,
  matching the widget's right-click, instead of separate up/down
- omarchy.clock toggleWeekStart — calendar week start (was W)
- shell toggleBarTransparency — the bar background (was manage UI only)

Bluetooth and network follow the power/monitor pattern: manageIpc: false
so the panel owns the target's single IpcHandler and can extend it past
the inherited lifecycle methods.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 15:44:07 -05:00

181 lines
6.0 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()
}
function toggleWeekStart() {
if (panelLoader.item) panelLoader.item.toggleWeekStart()
}
// 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 toggleWeekStart(): void { root.toggleWeekStart() }
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
}
}
}
}
}