mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-02 04:37:49 +02:00
862 lines
25 KiB
QML
862 lines
25 KiB
QML
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Wayland
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
import qs.Commons
|
|
import qs.Ui
|
|
import "BarModel.js" as BarModel
|
|
|
|
Item {
|
|
id: root
|
|
|
|
// The omarchy-shell host injects omarchyPath from OMARCHY_PATH.
|
|
required property string omarchyPath
|
|
// Injected by the host shell. Shared with the bar settings panel so both
|
|
// see the same widget catalogue.
|
|
required property var barWidgetRegistry
|
|
// Injected by the host shell every time shell.json is reloaded. Holds the
|
|
// `bar:` subtree: position, centerAnchor, layout. The host owns file IO;
|
|
// the bar just renders whatever it's handed. The bar font follows the
|
|
// OS-level fontconfig monospace binding — it is not stored in shell.json.
|
|
required property var barConfig
|
|
// Injected by the host shell. Used for shell-wide actions such as opening
|
|
// settings and persisting inline widget state.
|
|
property var shell: null
|
|
// Mirrors the on-disk `bar-off` flag so the user can hide the bar without
|
|
// killing the entire shell. Wired to BarPanel.visible below; updated by the
|
|
// FileView watcher further down.
|
|
property bool barHidden: false
|
|
property string home: Quickshell.env("HOME")
|
|
property string omarchyConfigDir: home + "/.config/omarchy"
|
|
property var fallbackBarConfig: ({
|
|
position: "top",
|
|
transparent: false,
|
|
centerAnchor: "omarchy.clock",
|
|
layout: { left: [], center: [], right: [] }
|
|
})
|
|
property var layoutConfig: fallbackBarConfig.layout
|
|
property string centerAnchor: ""
|
|
property bool transparent: false
|
|
property int barConfigSerial: 0
|
|
property string position: "top"
|
|
// Resolves through fontconfig at paint time (Style.font.family defaults
|
|
// to "monospace"), so changing the system font (via `omarchy-font-set`)
|
|
// updates the bar without a reload.
|
|
property string fontFamily: Style.font.family
|
|
// Bound to the central Color singleton so the bar tracks shell.toml's
|
|
// [bar] section. Property names kept for the rest of this file's bindings.
|
|
property color foreground: Color.bar.text
|
|
property color background: Color.bar.background
|
|
property color urgent: Color.bar.active
|
|
|
|
Behavior on foreground { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
|
Behavior on background { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
|
Behavior on urgent { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
|
property var tooltipTarget: null
|
|
property var pendingTooltipTarget: null
|
|
property string tooltipText: ""
|
|
property string pendingTooltipText: ""
|
|
property bool tooltipShown: false
|
|
property int tooltipRequest: 0
|
|
property var activePopout: null
|
|
property var clickTargets: []
|
|
property var debugModuleSlots: []
|
|
|
|
function registerClickTarget(target) {
|
|
if (!target || clickTargets.indexOf(target) !== -1) return
|
|
var next = clickTargets.slice()
|
|
next.push(target)
|
|
clickTargets = next
|
|
}
|
|
|
|
function unregisterClickTarget(target) {
|
|
var next = clickTargets.filter(function(item) { return item !== target })
|
|
clickTargets = next
|
|
}
|
|
|
|
function registerDebugModuleSlot(slot) {
|
|
if (!slot || debugModuleSlots.indexOf(slot) !== -1) return
|
|
var next = debugModuleSlots.slice()
|
|
next.push(slot)
|
|
debugModuleSlots = next
|
|
}
|
|
|
|
function unregisterDebugModuleSlot(slot) {
|
|
var next = debugModuleSlots.filter(function(item) { return item !== slot })
|
|
debugModuleSlots = next
|
|
}
|
|
|
|
function debugBarGeometry() {
|
|
var out = []
|
|
for (var i = 0; i < debugModuleSlots.length; i++) {
|
|
var slot = debugModuleSlots[i]
|
|
if (!slot || !slot.activeItem) continue
|
|
var point = { x: slot.x, y: slot.y }
|
|
try {
|
|
point = slot.mapToItem(null, 0, 0)
|
|
} catch (e) {
|
|
}
|
|
out.push({
|
|
id: slot.moduleName,
|
|
section: slot.region,
|
|
x: Math.round(point.x),
|
|
y: Math.round(point.y),
|
|
width: Math.round(slot.width),
|
|
height: Math.round(slot.height),
|
|
visible: slot.visible === true && slot.width > 0 && slot.height > 0,
|
|
itemVisible: slot.activeItem.visible === true,
|
|
itemWidth: Math.round(slot.activeItem.implicitWidth || 0),
|
|
itemHeight: Math.round(slot.activeItem.implicitHeight || 0)
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
function targetWindow(target) {
|
|
return target && target.QsWindow ? target.QsWindow.window : null
|
|
}
|
|
|
|
function targetBelongsToWindow(target, window) {
|
|
return !!target && !!window && targetWindow(target) === window
|
|
}
|
|
|
|
function targetTooltipHovered(target) {
|
|
return !!target && target.visible !== false && target.opacity !== 0 && target.tooltipHovered === true
|
|
}
|
|
|
|
function clearTooltip() {
|
|
tooltipTimer.stop()
|
|
pendingTooltipTarget = null
|
|
pendingTooltipText = ""
|
|
tooltipTarget = null
|
|
tooltipText = ""
|
|
tooltipShown = false
|
|
}
|
|
|
|
function requestPopout(owner) {
|
|
if (activePopout === owner) return
|
|
if (activePopout) {
|
|
if ("closeForPopoutSwitch" in activePopout) activePopout.closeForPopoutSwitch()
|
|
else if ("close" in activePopout) activePopout.close()
|
|
}
|
|
activePopout = owner
|
|
}
|
|
|
|
function releasePopout(owner) {
|
|
if (activePopout === owner) activePopout = null
|
|
}
|
|
|
|
readonly property bool vertical: position === "left" || position === "right"
|
|
readonly property int barSize: vertical ? Style.bar.sizeVertical : Style.bar.sizeHorizontal
|
|
|
|
function normalizePosition(value) {
|
|
return BarModel.normalizePosition(value)
|
|
}
|
|
|
|
// Apply tray-pinning on top of the shared layout normalization so the
|
|
// bar host and the bar settings panel can't drift on entry shape.
|
|
function normalizeLayout(layout) {
|
|
var normalized = Util.normalizeLayout(Util.isPlainObject(layout) ? layout : fallbackBarConfig.layout)
|
|
return {
|
|
left: pinTrayToInner(normalized.left, "left"),
|
|
center: pinTrayToInner(normalized.center, "center"),
|
|
right: pinTrayToInner(normalized.right, "right")
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
// not stranded mid-section.
|
|
function pinTrayToInner(entries, section) {
|
|
return BarModel.pinTrayToInner(entries, section)
|
|
}
|
|
|
|
function applyBarConfig() {
|
|
var config = Util.isPlainObject(barConfig) ? barConfig : fallbackBarConfig
|
|
|
|
position = normalizePosition(config.position)
|
|
transparent = config.transparent === true
|
|
centerAnchor = Util.canonicalWidgetId(config.centerAnchor || "")
|
|
layoutConfig = normalizeLayout(config.layout)
|
|
barConfigSerial++
|
|
}
|
|
|
|
onBarConfigChanged: applyBarConfig()
|
|
|
|
function layoutEntries(region) {
|
|
var serial = barConfigSerial
|
|
var entries = layoutConfig ? layoutConfig[region] : null
|
|
return Array.isArray(entries) ? entries : []
|
|
}
|
|
|
|
function panelNavigationSlots(region) {
|
|
var entries = layoutEntries(region)
|
|
var slots = []
|
|
for (var i = 0; i < entries.length; i++) {
|
|
var id = entryId(entries[i])
|
|
for (var j = 0; j < debugModuleSlots.length; j++) {
|
|
var slot = debugModuleSlots[j]
|
|
if (!slot || slot.region !== region || slot.moduleName !== id) continue
|
|
var item = slot.activeItem
|
|
if (!item || item.visible !== true || slot.visible !== true || slot.width <= 0 || slot.height <= 0) continue
|
|
if (typeof item.open !== "function" || typeof item.close !== "function" || item.opened === undefined) continue
|
|
slots.push(slot)
|
|
break
|
|
}
|
|
}
|
|
return slots
|
|
}
|
|
|
|
function switchPanelFrom(owner, direction) {
|
|
if (!owner) return false
|
|
|
|
var currentSlot = null
|
|
for (var i = 0; i < debugModuleSlots.length; i++) {
|
|
var slot = debugModuleSlots[i]
|
|
if (slot && slot.activeItem === owner) {
|
|
currentSlot = slot
|
|
break
|
|
}
|
|
}
|
|
if (!currentSlot) return false
|
|
|
|
var slots = panelNavigationSlots(currentSlot.region)
|
|
if (slots.length < 2) return false
|
|
|
|
var currentIndex = -1
|
|
for (var j = 0; j < slots.length; j++) {
|
|
if (slots[j] === currentSlot) {
|
|
currentIndex = j
|
|
break
|
|
}
|
|
}
|
|
if (currentIndex < 0) return false
|
|
|
|
var step = direction < 0 ? -1 : 1
|
|
var nextSlot = slots[(currentIndex + step + slots.length) % slots.length]
|
|
if (!nextSlot || !nextSlot.activeItem || nextSlot.activeItem === owner) return false
|
|
|
|
nextSlot.activeItem.open()
|
|
return true
|
|
}
|
|
|
|
function entrySettings(entry) {
|
|
return BarModel.entrySettings(entry)
|
|
}
|
|
|
|
function entryId(entry) {
|
|
return BarModel.entryId(entry)
|
|
}
|
|
|
|
function moduleString(entry, key, fallback) {
|
|
return BarModel.moduleString(entry, key, fallback)
|
|
}
|
|
|
|
function entryIndex(entries, name) {
|
|
return BarModel.entryIndex(entries, name)
|
|
}
|
|
|
|
function entriesBefore(entries, name) {
|
|
return BarModel.entriesBefore(entries, name)
|
|
}
|
|
|
|
function entriesAfter(entries, name) {
|
|
return BarModel.entriesAfter(entries, name)
|
|
}
|
|
|
|
function canonicalWidgetId(name) {
|
|
return Util.canonicalWidgetId(name)
|
|
}
|
|
|
|
function expandPath(path) {
|
|
return BarModel.expandPath(path, home)
|
|
}
|
|
|
|
function customModuleSafeName(name) {
|
|
return BarModel.customModuleSafeName(name)
|
|
}
|
|
|
|
function customModuleType(entry) {
|
|
return BarModel.customModuleType(entry)
|
|
}
|
|
|
|
function customModuleSource(entry) {
|
|
var source = BarModel.customModulePath(entry, home, omarchyConfigDir)
|
|
return source ? Util.fileUrl(source) : ""
|
|
}
|
|
|
|
Component.onCompleted: applyBarConfig()
|
|
|
|
function run(command) {
|
|
if (!command) return
|
|
|
|
launcher.command = Util.hyprExecCommand(command)
|
|
launcher.startDetached()
|
|
}
|
|
|
|
function openBarSettings() {
|
|
if (root.shell && typeof root.shell.summon === "function") {
|
|
root.shell.summon("omarchy.settings", "{}")
|
|
} else {
|
|
root.run("omarchy-launch-bar-settings")
|
|
}
|
|
}
|
|
|
|
function toggleTransparency() {
|
|
var nextTransparent = !(root.transparent === true)
|
|
if (root.shell && typeof root.shell.mutateShellConfig === "function") {
|
|
root.shell.mutateShellConfig(function(config) {
|
|
if (!Util.isPlainObject(config.bar)) config.bar = {}
|
|
config.bar.transparent = nextTransparent
|
|
})
|
|
} else {
|
|
root.transparent = nextTransparent
|
|
}
|
|
}
|
|
|
|
function runProcess(process) {
|
|
if (!process.running)
|
|
process.running = true
|
|
}
|
|
|
|
function showTooltip(target, text) {
|
|
clearTooltip()
|
|
|
|
if (!targetTooltipHovered(target) || !text) {
|
|
tooltipRequest += 1
|
|
return
|
|
}
|
|
|
|
var request = tooltipRequest + 1
|
|
tooltipRequest = request
|
|
pendingTooltipTarget = target
|
|
pendingTooltipText = text
|
|
|
|
Qt.callLater(function() {
|
|
if (request !== tooltipRequest) return
|
|
if (!targetTooltipHovered(pendingTooltipTarget)) {
|
|
clearTooltip()
|
|
return
|
|
}
|
|
tooltipTarget = pendingTooltipTarget
|
|
tooltipText = pendingTooltipText
|
|
pendingTooltipTarget = null
|
|
pendingTooltipText = ""
|
|
tooltipTimer.restart()
|
|
})
|
|
}
|
|
|
|
function hideTooltip(target) {
|
|
if (tooltipTarget !== target && pendingTooltipTarget !== target) return
|
|
|
|
tooltipRequest += 1
|
|
clearTooltip()
|
|
}
|
|
|
|
Process { id: launcher }
|
|
|
|
Timer {
|
|
id: tooltipTimer
|
|
interval: 400
|
|
onTriggered: {
|
|
if (root.targetTooltipHovered(root.tooltipTarget)) root.tooltipShown = true
|
|
else root.clearTooltip()
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
interval: 100
|
|
running: root.tooltipShown
|
|
repeat: true
|
|
onTriggered: if (!root.targetTooltipHovered(root.tooltipTarget)) root.hideTooltip(root.tooltipTarget)
|
|
}
|
|
|
|
// 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`.
|
|
Process {
|
|
id: barHiddenProbe
|
|
running: true
|
|
command: ["bash", "-lc", "[[ -f $HOME/.local/state/omarchy/toggles/bar-off ]] && echo yes || echo no"]
|
|
stdout: SplitParser { onRead: function(line) { root.barHidden = String(line).trim() === "yes" } }
|
|
}
|
|
FileView {
|
|
path: root.home + "/.local/state/omarchy/toggles"
|
|
watchChanges: true
|
|
printErrors: false
|
|
onFileChanged: barHiddenProbe.running = true
|
|
}
|
|
|
|
Variants {
|
|
model: Quickshell.screens
|
|
|
|
delegate: Component {
|
|
BarPanel {
|
|
required property var modelData
|
|
|
|
screen: modelData
|
|
}
|
|
}
|
|
}
|
|
|
|
component BarPanel: PanelWindow {
|
|
id: barWindow
|
|
|
|
visible: !root.barHidden
|
|
|
|
anchors {
|
|
top: root.position === "top" || root.vertical
|
|
bottom: root.position === "bottom" || root.vertical
|
|
left: root.position === "left" || !root.vertical
|
|
right: root.position === "right" || !root.vertical
|
|
}
|
|
|
|
implicitWidth: root.vertical ? root.barSize : 0
|
|
implicitHeight: root.vertical ? 0 : root.barSize
|
|
color: root.transparent ? "transparent" : root.background
|
|
WlrLayershell.namespace: "omarchy-bar"
|
|
WlrLayershell.layer: WlrLayer.Top
|
|
|
|
Loader {
|
|
anchors.fill: parent
|
|
sourceComponent: root.vertical ? verticalBar : horizontalBar
|
|
}
|
|
|
|
PopupWindow {
|
|
id: tooltipWindow
|
|
|
|
visible: root.tooltipShown && root.tooltipTarget !== null && root.tooltipText !== "" && root.targetBelongsToWindow(root.tooltipTarget, barWindow)
|
|
color: "transparent"
|
|
implicitWidth: Math.ceil(tooltipBubble.implicitWidth)
|
|
implicitHeight: Math.ceil(tooltipBubble.implicitHeight)
|
|
|
|
anchor {
|
|
id: tooltipAnchor
|
|
window: barWindow
|
|
adjustment: PopupAdjustment.Slide
|
|
edges: Edges.Top | Edges.Left
|
|
gravity: Edges.Bottom | Edges.Right
|
|
rect.width: 1
|
|
rect.height: 1
|
|
|
|
onAnchoring: {
|
|
var target = root.tooltipTarget
|
|
if (!root.targetBelongsToWindow(target, barWindow)) return
|
|
|
|
var popupWidth = tooltipWindow.implicitWidth
|
|
var popupHeight = tooltipWindow.implicitHeight
|
|
var localX = target.width / 2 - popupWidth / 2
|
|
var localY = target.height + 6
|
|
|
|
if (root.position === "bottom") {
|
|
localY = -popupHeight - 6
|
|
} else if (root.position === "left") {
|
|
localX = target.width + 6
|
|
localY = target.height / 2 - popupHeight / 2
|
|
} else if (root.position === "right") {
|
|
localX = -popupWidth - 6
|
|
localY = target.height / 2 - popupHeight / 2
|
|
}
|
|
|
|
var point = barWindow.contentItem.mapFromItem(target, localX, localY)
|
|
tooltipAnchor.rect.x = Math.round(point.x)
|
|
tooltipAnchor.rect.y = Math.round(point.y)
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: tooltipBubble
|
|
implicitWidth: tooltipLabel.implicitWidth + 20
|
|
implicitHeight: tooltipLabel.implicitHeight + 14
|
|
color: Color.tooltip.background
|
|
border.color: Color.tooltip.border
|
|
border.width: 1
|
|
radius: Style.cornerRadius
|
|
|
|
Text {
|
|
id: tooltipLabel
|
|
anchors.centerIn: parent
|
|
text: root.tooltipText
|
|
color: Color.tooltip.text
|
|
font.family: root.fontFamily
|
|
font.pixelSize: Style.font.body
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: horizontalBar
|
|
|
|
Item {
|
|
anchors.fill: parent
|
|
|
|
CenterModules { anchors.fill: parent }
|
|
|
|
LeftModules {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Style.space(8)
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
RightModules {
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Style.space(8)
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: verticalBar
|
|
|
|
Item {
|
|
anchors.fill: parent
|
|
|
|
CenterModules { anchors.fill: parent }
|
|
|
|
LeftModules {
|
|
anchors.top: parent.top
|
|
anchors.topMargin: Style.space(8)
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
}
|
|
|
|
RightModules {
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: Style.space(8)
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component { id: emptyModuleComponent; Item { implicitWidth: 0; implicitHeight: 0; visible: false } }
|
|
|
|
function findCenterAnchorEntry() {
|
|
var entries = root.layoutEntries("center")
|
|
var idx = root.entryIndex(entries, root.centerAnchor)
|
|
return idx === -1 ? null : entries[idx]
|
|
}
|
|
|
|
component LeftModules: ModuleList {
|
|
entries: root.layoutEntries("left")
|
|
region: "left"
|
|
}
|
|
|
|
component RightModules: ModuleList {
|
|
entries: root.layoutEntries("right")
|
|
region: "right"
|
|
}
|
|
|
|
component CenterModules: Item {
|
|
id: centerRoot
|
|
|
|
property var entries: root.layoutEntries("center")
|
|
readonly property bool hasAnchor: root.entryIndex(entries, root.centerAnchor) !== -1
|
|
readonly property var anchorEntry: root.findCenterAnchorEntry()
|
|
|
|
Loader {
|
|
anchors.fill: parent
|
|
sourceComponent: root.vertical ? verticalCenterModules : horizontalCenterModules
|
|
}
|
|
|
|
Component {
|
|
id: horizontalCenterModules
|
|
|
|
Item {
|
|
anchors.fill: parent
|
|
|
|
CenterGestureArea { anchors.fill: parent }
|
|
|
|
ModuleList {
|
|
visible: !centerRoot.hasAnchor
|
|
entries: centerRoot.entries
|
|
region: "center"
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
ModuleList {
|
|
visible: centerRoot.hasAnchor
|
|
entries: root.entriesBefore(centerRoot.entries, root.centerAnchor)
|
|
region: "center"
|
|
anchors.right: centerAnchorModule.left
|
|
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
|
}
|
|
|
|
ModuleSlot {
|
|
id: centerAnchorModule
|
|
visible: centerRoot.hasAnchor
|
|
entry: centerRoot.anchorEntry
|
|
region: "center"
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
ModuleList {
|
|
visible: centerRoot.hasAnchor
|
|
entries: root.entriesAfter(centerRoot.entries, root.centerAnchor)
|
|
region: "center"
|
|
anchors.left: centerAnchorModule.right
|
|
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: verticalCenterModules
|
|
|
|
Item {
|
|
anchors.fill: parent
|
|
|
|
CenterGestureArea { anchors.fill: parent }
|
|
|
|
ModuleList {
|
|
visible: !centerRoot.hasAnchor
|
|
entries: centerRoot.entries
|
|
region: "center"
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
ModuleList {
|
|
visible: centerRoot.hasAnchor
|
|
entries: root.entriesBefore(centerRoot.entries, root.centerAnchor)
|
|
region: "center"
|
|
anchors.bottom: centerAnchorModule.top
|
|
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
|
}
|
|
|
|
ModuleSlot {
|
|
id: centerAnchorModule
|
|
visible: centerRoot.hasAnchor
|
|
entry: centerRoot.anchorEntry
|
|
region: "center"
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
ModuleList {
|
|
visible: centerRoot.hasAnchor
|
|
entries: root.entriesAfter(centerRoot.entries, root.centerAnchor)
|
|
region: "center"
|
|
anchors.top: centerAnchorModule.bottom
|
|
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
component CenterGestureArea: MouseArea {
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
|
|
onClicked: function(mouse) {
|
|
if (mouse.button === Qt.RightButton) {
|
|
root.openBarSettings()
|
|
mouse.accepted = true
|
|
}
|
|
}
|
|
|
|
onDoubleClicked: function(mouse) {
|
|
if (mouse.button !== Qt.RightButton) {
|
|
root.toggleTransparency()
|
|
mouse.accepted = true
|
|
}
|
|
}
|
|
}
|
|
|
|
component ModuleList: Loader {
|
|
id: moduleListRoot
|
|
|
|
property var entries: []
|
|
property string region: ""
|
|
|
|
visible: entries.length > 0
|
|
sourceComponent: root.vertical ? verticalModuleList : horizontalModuleList
|
|
width: item ? item.implicitWidth : 0
|
|
height: item ? item.implicitHeight : 0
|
|
|
|
Component {
|
|
id: horizontalModuleList
|
|
|
|
Row {
|
|
spacing: 0
|
|
|
|
Repeater {
|
|
model: moduleListRoot.entries
|
|
|
|
ModuleSlot {
|
|
required property var modelData
|
|
entry: modelData
|
|
region: moduleListRoot.region
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: verticalModuleList
|
|
|
|
Column {
|
|
spacing: 0
|
|
|
|
Repeater {
|
|
model: moduleListRoot.entries
|
|
|
|
ModuleSlot {
|
|
required property var modelData
|
|
entry: modelData
|
|
region: moduleListRoot.region
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
component ModuleSlot: Item {
|
|
id: slot
|
|
|
|
required property var entry
|
|
property string region: ""
|
|
readonly property string moduleName: root.entryId(entry)
|
|
readonly property var moduleSettings: root.entrySettings(entry)
|
|
readonly property string customType: root.customModuleType(entry)
|
|
// Re-evaluate when the registry mutates (Component reference changes,
|
|
// plugin enabled/disabled, etc.). Reading the `widgets` property creates
|
|
// the binding dependency — the wrapped function call alone wouldn't.
|
|
readonly property var registryComponent: {
|
|
var w = root.barWidgetRegistry.widgets
|
|
if (customType) return null
|
|
var registryName = root.canonicalWidgetId(moduleName)
|
|
return w[registryName] ? w[registryName].component : null
|
|
}
|
|
readonly property bool qmlCustom: customType === "qml"
|
|
readonly property bool commandCustom: customType === "command"
|
|
readonly property bool registered: registryComponent !== null
|
|
readonly property var activeItem: {
|
|
if (registered) return registryLoader.item
|
|
if (qmlCustom) return qmlLoader.item
|
|
return componentLoader.item
|
|
}
|
|
|
|
implicitWidth: activeItem && activeItem.visible ? (root.vertical ? root.barSize : activeItem.implicitWidth) : 0
|
|
implicitHeight: activeItem && activeItem.visible ? activeItem.implicitHeight : 0
|
|
width: implicitWidth
|
|
height: implicitHeight
|
|
|
|
Component.onCompleted: root.registerDebugModuleSlot(slot)
|
|
Component.onDestruction: root.unregisterDebugModuleSlot(slot)
|
|
|
|
Loader {
|
|
id: componentLoader
|
|
active: !slot.qmlCustom && !slot.registered
|
|
sourceComponent: slot.commandCustom ? customCommandModuleComponent : emptyModuleComponent
|
|
anchors.fill: parent
|
|
onLoaded: {
|
|
slot.injectProps()
|
|
Qt.callLater(slot.injectProps)
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: registryLoader
|
|
active: slot.registered
|
|
sourceComponent: slot.registered ? slot.registryComponent : null
|
|
anchors.fill: parent
|
|
onLoaded: {
|
|
slot.injectProps()
|
|
Qt.callLater(slot.injectProps)
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: qmlLoader
|
|
active: slot.qmlCustom
|
|
source: slot.qmlCustom ? root.customModuleSource(slot.entry) : ""
|
|
anchors.fill: parent
|
|
onLoaded: {
|
|
slot.injectProps()
|
|
Qt.callLater(slot.injectProps)
|
|
}
|
|
}
|
|
|
|
onActiveItemChanged: Qt.callLater(injectProps)
|
|
onModuleSettingsChanged: injectProps()
|
|
|
|
function injectProps() {
|
|
var target = activeItem
|
|
if (!target) return
|
|
if ("bar" in target) target.bar = root
|
|
if ("moduleName" in target) target.moduleName = moduleName
|
|
if ("settings" in target) target.settings = moduleSettings
|
|
}
|
|
|
|
Component {
|
|
id: customCommandModuleComponent
|
|
CustomCommandModule { entry: slot.entry }
|
|
}
|
|
}
|
|
|
|
component CustomCommandModule: WidgetButton {
|
|
id: customRoot
|
|
|
|
required property var entry
|
|
readonly property string moduleName: root.entryId(entry)
|
|
readonly property var settings: root.entrySettings(entry)
|
|
property string outputText: ""
|
|
property string outputTooltip: ""
|
|
property bool outputActive: false
|
|
|
|
function setting(name, fallback) {
|
|
var value = settings ? settings[name] : undefined
|
|
return value === undefined || value === null ? fallback : value
|
|
}
|
|
|
|
function update(raw) {
|
|
var data = Util.parseModuleJson(raw)
|
|
var klass = data.class || data.alt || ""
|
|
|
|
outputText = data.text || String(raw || "").trim()
|
|
outputTooltip = data.tooltip || String(setting("tooltip", ""))
|
|
outputActive = klass === "active" || (Array.isArray(klass) && klass.indexOf("active") !== -1)
|
|
}
|
|
|
|
bar: root
|
|
text: outputText || String(setting("text", ""))
|
|
tooltipText: outputTooltip || String(setting("tooltip", ""))
|
|
active: outputActive
|
|
keepSpace: setting("keepSpace", false) === true
|
|
horizontalMargin: Number(setting("horizontalMargin", 7.5))
|
|
verticalPadding: Number(setting("verticalPadding", 6))
|
|
fontSize: Number(setting("fontSize", 12))
|
|
|
|
onPressed: function(button) {
|
|
var command = ""
|
|
if (button === Qt.RightButton)
|
|
command = String(setting("onRightClick", ""))
|
|
else if (button === Qt.MiddleButton)
|
|
command = String(setting("onMiddleClick", ""))
|
|
else
|
|
command = String(setting("onClick", ""))
|
|
|
|
if (command) root.run(command)
|
|
}
|
|
|
|
Process {
|
|
id: customProc
|
|
command: ["bash", "-lc", String(customRoot.setting("exec", ""))]
|
|
stdout: StdioCollector {
|
|
waitForEnd: true
|
|
onStreamFinished: customRoot.update(text)
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
interval: Math.max(1, Number(customRoot.setting("interval", 5))) * 1000
|
|
running: String(customRoot.setting("exec", "")) !== ""
|
|
repeat: true
|
|
triggeredOnStart: true
|
|
onTriggered: root.runProcess(customProc)
|
|
}
|
|
}
|
|
}
|