Extract indicators bar widget

This commit is contained in:
David Heinemeier Hansson
2026-05-20 18:32:00 +02:00
parent 1e63e86133
commit cf4729ca2b
16 changed files with 470 additions and 455 deletions
-434
View File
@@ -63,25 +63,6 @@ Item {
property int tooltipRequest: 0
property var activePopout: null
property var clickTargets: []
property bool indicatorAreaHovered: false
property bool indicatorItemHovered: false
readonly property bool revealInactiveIndicators: indicatorAreaHovered || indicatorItemHovered
function setIndicatorAreaHovered(hovered) {
indicatorAreaHovered = hovered
if (hovered) indicatorHideTimer.stop()
else indicatorHideTimer.restart()
}
function setIndicatorItemHovered(hovered) {
if (hovered) {
indicatorItemHovered = true
indicatorHideTimer.stop()
} else {
indicatorHideTimer.restart()
}
}
function registerClickTarget(target) {
if (!target || clickTargets.indexOf(target) !== -1) return
var next = clickTargets.slice()
@@ -128,8 +109,6 @@ Item {
readonly property bool vertical: position === "left" || position === "right"
readonly property int barSize: vertical ? Style.bar.sizeVertical : Style.bar.sizeHorizontal
signal indicatorsRefreshRequested()
function normalizePosition(value) {
var next = String(value || "").trim()
return /^(top|bottom|left|right)$/.test(next) ? next : "top"
@@ -146,26 +125,6 @@ Item {
}
}
function indicatorEntriesFromSettings(settings) {
var source = []
if (settings.items && typeof settings.items.length === "number") source = settings.items
else if (settings.indicators && typeof settings.indicators.length === "number") source = settings.indicators
var result = []
for (var i = 0; i < source.length; i++) {
var item = source[i]
if (typeof item !== "string" && item !== null && typeof item === "object") {
try {
item = JSON.parse(JSON.stringify(item))
} catch (error) {
}
}
var id = entryId(item)
if (id !== "") result.push(item)
}
return result
}
// The tray drawer reveals inward (away from the bar edge). Place it at the
// section's inner edge: start of the right section, end of the left/center
// sections. The drawer's reserved space then sits next to the bar center,
@@ -258,7 +217,6 @@ Item {
function builtinModuleComponent(name) {
switch (String(name)) {
case "clock": return clockModuleComponent
case "indicators": return indicatorsModuleComponent
case "tray": return trayModuleComponent
default: return null
}
@@ -364,10 +322,6 @@ Item {
clearTooltip()
}
function refreshIndicators() {
indicatorsRefreshRequested()
}
function trayIconSource(icon) {
var value = String(icon || "")
var marker = "?path="
@@ -428,15 +382,6 @@ Item {
onTriggered: if (!root.targetTooltipHovered(root.tooltipTarget)) root.hideTooltip(root.tooltipTarget)
}
Timer {
id: indicatorHideTimer
interval: 120
onTriggered: {
if (!root.indicatorAreaHovered)
root.indicatorItemHovered = false
}
}
// Presence of the `bar-off` flag = bar hidden. Watching the parent toggles
// directory because FileView can't observe a file that doesn't exist yet,
// and the flag is created/removed by `omarchy-toggle-bar`.
@@ -453,26 +398,6 @@ Item {
onFileChanged: barHiddenProbe.running = true
}
Timer {
interval: 2000
running: true
repeat: true
triggeredOnStart: true
onTriggered: root.refreshIndicators()
}
IpcHandler {
target: "bar"
function refreshScreenRecording(): void {
root.refreshIndicators()
}
function refreshIndicators(): void {
root.refreshIndicators()
}
}
Variants {
model: Quickshell.screens
@@ -619,7 +544,6 @@ Item {
Component { id: emptyModuleComponent; Item { implicitWidth: 0; implicitHeight: 0; visible: false } }
Component { id: clockModuleComponent; ClockModule {} }
Component { id: indicatorsModuleComponent; IndicatorsModule {} }
Component { id: trayModuleComponent; TrayModule {} }
function findCenterAnchorEntry() {
@@ -989,364 +913,6 @@ Item {
}
}
component IndicatorsModule: Item {
id: indicatorsRoot
property var bar: root
property string moduleName: "indicators"
property var settings: ({})
readonly property var indicatorEntries: root.indicatorEntriesFromSettings(settings)
property var activeIndicatorIds: []
property var indicatorActiveStates: ({})
ListModel { id: activeIndicatorModel }
function hasIndicatorId(id) {
for (var i = 0; i < indicatorEntries.length; i++) {
if (root.entryId(indicatorEntries[i]) === id) return true
}
return false
}
function entryForId(id) {
for (var i = 0; i < indicatorEntries.length; i++) {
var entry = indicatorEntries[i]
if (root.entryId(entry) === id) return entry
}
return { id: id }
}
function activeModelIndex(id) {
for (var i = 0; i < activeIndicatorModel.count; i++) {
if (activeIndicatorModel.get(i).activeId === id) return i
}
return -1
}
function copyActiveStates() {
var states = {}
for (var id in indicatorActiveStates) {
if (indicatorActiveStates[id] === true) states[id] = true
}
return states
}
function orderedActiveIds(states, preferredOrder) {
var ids = []
for (var i = 0; i < preferredOrder.length; i++) {
var id = preferredOrder[i]
if (ids.indexOf(id) === -1 && hasIndicatorId(id) && states[id] === true) ids.push(id)
}
return ids
}
function syncActiveIndicatorModel() {
for (var i = activeIndicatorModel.count - 1; i >= 0; i--) {
if (activeIndicatorIds.indexOf(activeIndicatorModel.get(i).activeId) === -1)
activeIndicatorModel.remove(i)
}
for (var j = 0; j < activeIndicatorIds.length; j++) {
var id = activeIndicatorIds[j]
var index = activeModelIndex(id)
if (index === -1) activeIndicatorModel.insert(j, { activeId: id })
else if (index !== j) activeIndicatorModel.move(index, j, 1)
}
}
function setIndicatorActive(entry, active) {
var id = root.entryId(entry)
if (id === "") return
var states = copyActiveStates()
if (active) states[id] = true
else delete states[id]
indicatorActiveStates = states
var ids = orderedActiveIds(states, activeIndicatorIds)
if (active && ids.indexOf(id) === -1 && hasIndicatorId(id)) ids.push(id)
activeIndicatorIds = ids
syncActiveIndicatorModel()
}
function syncActiveIndicatorOrder() {
activeIndicatorIds = orderedActiveIds(indicatorActiveStates, activeIndicatorIds)
syncActiveIndicatorModel()
}
onIndicatorEntriesChanged: syncActiveIndicatorOrder()
implicitWidth: root.vertical ? verticalIndicators.implicitWidth : horizontalIndicators.implicitWidth
implicitHeight: root.vertical ? verticalIndicators.implicitHeight : horizontalIndicators.implicitHeight
Row {
id: horizontalIndicators
visible: !root.vertical
spacing: 0
ActiveIndicatorBlock {
indicatorsModule: indicatorsRoot
indicatorModel: activeIndicatorModel
horizontal: true
reportActiveState: !root.vertical
}
Item {
id: inactiveHorizontalArea
implicitWidth: inactiveHorizontalBlock.implicitWidth
implicitHeight: inactiveHorizontalBlock.implicitHeight
width: implicitWidth
height: implicitHeight
IndicatorBlock {
id: inactiveHorizontalBlock
anchors.fill: parent
indicatorsModule: indicatorsRoot
indicatorEntries: indicatorsRoot.indicatorEntries
indicatorBlock: "inactive"
horizontal: true
reportActiveState: !root.vertical
}
HoverHandler {
onHoveredChanged: root.setIndicatorAreaHovered(hovered)
}
}
}
Column {
id: verticalIndicators
visible: root.vertical
spacing: 0
ActiveIndicatorBlock {
indicatorsModule: indicatorsRoot
indicatorModel: activeIndicatorModel
horizontal: false
reportActiveState: root.vertical
}
Item {
id: inactiveVerticalArea
implicitWidth: inactiveVerticalBlock.implicitWidth
implicitHeight: inactiveVerticalBlock.implicitHeight
width: implicitWidth
height: implicitHeight
IndicatorBlock {
id: inactiveVerticalBlock
anchors.fill: parent
indicatorsModule: indicatorsRoot
indicatorEntries: indicatorsRoot.indicatorEntries
indicatorBlock: "inactive"
horizontal: false
reportActiveState: root.vertical
}
HoverHandler {
onHoveredChanged: root.setIndicatorAreaHovered(hovered)
}
}
}
}
component ActiveIndicatorBlock: Item {
id: activeIndicatorBlockRoot
property var indicatorModel: null
property var indicatorsModule: null
property bool horizontal: true
property bool reportActiveState: false
implicitWidth: blockLoader.item ? blockLoader.item.childrenRect.width : 0
implicitHeight: blockLoader.item ? blockLoader.item.childrenRect.height : 0
width: implicitWidth
height: implicitHeight
Loader {
id: blockLoader
anchors.fill: parent
sourceComponent: activeIndicatorBlockRoot.horizontal ? horizontalActiveIndicatorBlock : verticalActiveIndicatorBlock
}
Component {
id: horizontalActiveIndicatorBlock
Row {
spacing: 0
Repeater {
model: activeIndicatorBlockRoot.indicatorModel
IndicatorLoader {
required property string activeId
indicatorsModule: activeIndicatorBlockRoot.indicatorsModule
entry: activeIndicatorBlockRoot.indicatorsModule.entryForId(activeId)
indicatorBlock: "active"
reportActiveState: activeIndicatorBlockRoot.reportActiveState
}
}
}
}
Component {
id: verticalActiveIndicatorBlock
Column {
spacing: 0
Repeater {
model: activeIndicatorBlockRoot.indicatorModel
IndicatorLoader {
required property string activeId
indicatorsModule: activeIndicatorBlockRoot.indicatorsModule
entry: activeIndicatorBlockRoot.indicatorsModule.entryForId(activeId)
indicatorBlock: "active"
reportActiveState: activeIndicatorBlockRoot.reportActiveState
}
}
}
}
}
component IndicatorBlock: Item {
id: indicatorBlockRoot
property var indicatorEntries: []
property var indicatorsModule: null
property string indicatorBlock: "active"
property bool horizontal: true
property bool reportActiveState: false
implicitWidth: blockLoader.item ? blockLoader.item.childrenRect.width : 0
implicitHeight: blockLoader.item ? blockLoader.item.childrenRect.height : 0
width: implicitWidth
height: implicitHeight
Loader {
id: blockLoader
anchors.fill: parent
sourceComponent: indicatorBlockRoot.horizontal ? horizontalIndicatorBlock : verticalIndicatorBlock
}
Component {
id: horizontalIndicatorBlock
Row {
spacing: 0
Repeater {
model: indicatorBlockRoot.indicatorEntries
IndicatorLoader {
required property var modelData
indicatorsModule: indicatorBlockRoot.indicatorsModule
entry: modelData
indicatorBlock: indicatorBlockRoot.indicatorBlock
reportActiveState: indicatorBlockRoot.reportActiveState
}
}
}
}
Component {
id: verticalIndicatorBlock
Column {
spacing: 0
Repeater {
model: indicatorBlockRoot.indicatorEntries
IndicatorLoader {
required property var modelData
indicatorsModule: indicatorBlockRoot.indicatorsModule
entry: modelData
indicatorBlock: indicatorBlockRoot.indicatorBlock
reportActiveState: indicatorBlockRoot.reportActiveState
}
}
}
}
}
component IndicatorLoader: Item {
id: indicatorSlot
required property var entry
property var indicatorsModule: null
required property string indicatorBlock
property bool reportActiveState: false
property bool activeStateObserved: false
readonly property string indicatorId: root.entryId(entry)
readonly property var indicatorSettings: root.entrySettings(entry)
implicitWidth: indicatorSource.item && indicatorSource.item.visible ? indicatorSource.item.implicitWidth : 0
implicitHeight: indicatorSource.item && indicatorSource.item.visible ? indicatorSource.item.implicitHeight : 0
width: implicitWidth
height: implicitHeight
onEntryChanged: {
activeStateObserved = false
injectProps()
syncActiveState()
}
onIndicatorBlockChanged: injectProps()
onIndicatorSettingsChanged: injectProps()
onIndicatorsModuleChanged: syncActiveState()
onReportActiveStateChanged: syncActiveState()
Loader {
id: indicatorSource
anchors.fill: parent
source: indicatorSlot.indicatorId ? Qt.resolvedUrl("indicators/" + indicatorSlot.indicatorId + ".qml") : ""
onLoaded: {
indicatorSlot.injectProps()
indicatorSlot.syncActiveState()
}
onStatusChanged: if (status === Loader.Error) console.warn("Indicator loader error", indicatorSlot.indicatorId, source)
}
Connections {
target: indicatorSource.item
ignoreUnknownSignals: true
function onActiveChanged() { indicatorSlot.syncActiveState() }
}
function injectProps() {
var target = indicatorSource.item
if (!target) return
if ("bar" in target) target.bar = root
if ("moduleName" in target) target.moduleName = indicatorId
if ("settings" in target) target.settings = indicatorSettings
if ("indicatorBlock" in target) target.indicatorBlock = indicatorBlock
if ("activeOverride" in target) target.activeOverride = indicatorBlock === "active" ? true : null
}
function syncActiveState() {
if (!reportActiveState || !indicatorsModule || !indicatorsModule.setIndicatorActive) return
var active = !!indicatorSource.item && indicatorSource.item.active === true
if (indicatorBlock === "active") {
if (active) activeStateObserved = true
else if (!activeStateObserved) return
}
indicatorsModule.setIndicatorActive(entry, active)
}
}
component ClockModule: ModuleButton {
text: root.clockText()
horizontalMargin: 8.75