mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-02 04:37:49 +02:00
1572 lines
48 KiB
QML
1572 lines
48 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 so bar slots can resolve enabled widgets.
|
|
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
|
|
// Manifest for the active bar option. Present for custom bars and useful for
|
|
// diagnostics; the built-in bar does not otherwise need it.
|
|
property var manifest: 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 requestedTransparent: false
|
|
property bool useTransparentForeground: false
|
|
property bool transparent: false
|
|
property bool centerSectionHovered: false
|
|
property bool centerSectionRevealHeld: false
|
|
property bool centerHoverRevealSuppressed: 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 themeForeground: Color.bar.text
|
|
property color themeContrastForeground: Color.background
|
|
property color transparentForeground: Color.bar.text
|
|
property color foreground: themeForeground
|
|
property color barForeground: useTransparentForeground ? transparentForeground : themeForeground
|
|
property bool foregroundAnimationEnabled: true
|
|
property color background: Color.bar.background
|
|
property color urgent: Color.bar.active
|
|
|
|
Behavior on barForeground { enabled: root.foregroundAnimationEnabled; 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 barDragSource: null
|
|
property var barDragTarget: null
|
|
property bool barDragAfter: false
|
|
property var barDragWindow: null
|
|
property var barDragScreen: null
|
|
property url barDragImageUrl: ""
|
|
property real barDragSceneX: 0
|
|
property real barDragSceneY: 0
|
|
property real barDragScreenX: 0
|
|
property real barDragScreenY: 0
|
|
property real barDragOffsetX: 0
|
|
property real barDragOffsetY: 0
|
|
property var configControls: []
|
|
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 registerConfigControl(control) {
|
|
if (!control || configControls.indexOf(control) !== -1) return
|
|
var next = configControls.slice()
|
|
next.push(control)
|
|
configControls = next
|
|
}
|
|
|
|
function unregisterConfigControl(control) {
|
|
var next = configControls.filter(function(item) { return item !== control })
|
|
configControls = 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 slotWindow(slot) {
|
|
if (!slot) return null
|
|
return targetWindow(slot.activeItem) || targetWindow(slot)
|
|
}
|
|
|
|
function sameWindow(left, right) {
|
|
if (!left || !right) return false
|
|
if (left === right) return true
|
|
return !!left.screen && !!right.screen && !!left.screen.name && !!right.screen.name && left.screen.name === right.screen.name
|
|
}
|
|
|
|
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 clearBarDrag() {
|
|
barDragSource = null
|
|
barDragWindow = null
|
|
barDragScreen = null
|
|
barDragImageUrl = ""
|
|
barDragTarget = null
|
|
barDragAfter = false
|
|
barDragSceneX = 0
|
|
barDragSceneY = 0
|
|
barDragScreenX = 0
|
|
barDragScreenY = 0
|
|
barDragOffsetX = 0
|
|
barDragOffsetY = 0
|
|
}
|
|
|
|
function barDragScreenPoint(scenePoint) {
|
|
var x = scenePoint ? scenePoint.x : 0
|
|
var y = scenePoint ? scenePoint.y : 0
|
|
var window = barDragWindow
|
|
if (!window || !window.screen) return { x: x, y: y }
|
|
|
|
if (root.position === "bottom")
|
|
y += Math.max(0, window.screen.height - window.height)
|
|
else if (root.position === "right")
|
|
x += Math.max(0, window.screen.width - window.width)
|
|
|
|
return { x: x, y: y }
|
|
}
|
|
|
|
function captureBarDragGhost(slot) {
|
|
var item = slot && slot.activeItem ? slot.activeItem : null
|
|
barDragImageUrl = ""
|
|
if (!item || typeof item.grabToImage !== "function") return
|
|
|
|
var grabWidth = Math.max(1, Math.ceil(item.width || item.implicitWidth || slot.width || 1))
|
|
var grabHeight = Math.max(1, Math.ceil(item.height || item.implicitHeight || slot.height || 1))
|
|
item.grabToImage(function(result) {
|
|
if (root.barDragSource !== slot || !result || !result.url) return
|
|
root.barDragImageUrl = result.url
|
|
}, Qt.size(grabWidth, grabHeight))
|
|
}
|
|
|
|
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 scriptable config helpers 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)
|
|
setRequestedTransparency(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 setCenterSectionHovered(hovered) {
|
|
centerSectionHovered = hovered
|
|
if (hovered) {
|
|
centerSectionRevealTimer.stop()
|
|
centerSectionRevealHeld = true
|
|
} else {
|
|
centerSectionRevealTimer.restart()
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: centerSectionRevealTimer
|
|
interval: 120
|
|
onTriggered: root.centerSectionRevealHeld = root.centerSectionHovered
|
|
}
|
|
|
|
function run(command) {
|
|
if (!command) return
|
|
|
|
launcher.command = Util.hyprExecCommand(command)
|
|
launcher.startDetached()
|
|
}
|
|
|
|
function openConfigPanel() {
|
|
for (var i = 0; i < configControls.length; i++) {
|
|
var control = configControls[i]
|
|
if (!control || control.visible !== true || typeof control.openPanel !== "function") continue
|
|
control.openPanel()
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
function toggleTransparency() {
|
|
var nextTransparent = !(root.requestedTransparent === 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.setRequestedTransparency(nextTransparent)
|
|
}
|
|
}
|
|
|
|
function rawLayoutSection(config, region) {
|
|
if (!Util.isPlainObject(config.bar)) config.bar = {}
|
|
if (!Util.isPlainObject(config.bar.layout)) config.bar.layout = {}
|
|
if (!Array.isArray(config.bar.layout[region])) config.bar.layout[region] = []
|
|
|
|
return config.bar.layout[region]
|
|
}
|
|
|
|
function rawEntryIndex(entries, name) {
|
|
for (var i = 0; i < entries.length; i++) {
|
|
if (root.entryId(entries[i]) === name) return i
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
function moveModuleInConfig(config, fromRegion, fromName, toRegion, beforeName) {
|
|
var fromEntries = rawLayoutSection(config, fromRegion)
|
|
var toEntries = rawLayoutSection(config, toRegion)
|
|
var fromIndex = rawEntryIndex(fromEntries, fromName)
|
|
if (fromIndex < 0) return false
|
|
|
|
var toIndex = beforeName ? rawEntryIndex(toEntries, beforeName) : toEntries.length
|
|
if (toIndex < 0) toIndex = toEntries.length
|
|
|
|
if (fromRegion === toRegion && fromIndex === toIndex) return false
|
|
|
|
var movedEntry = fromEntries[fromIndex]
|
|
fromEntries.splice(fromIndex, 1)
|
|
|
|
if (fromRegion === toRegion && fromIndex < toIndex) toIndex -= 1
|
|
if (toIndex < 0) toIndex = 0
|
|
if (toIndex > toEntries.length) toIndex = toEntries.length
|
|
if (fromRegion === toRegion && fromIndex === toIndex) {
|
|
fromEntries.splice(fromIndex, 0, movedEntry)
|
|
return false
|
|
}
|
|
|
|
toEntries.splice(toIndex, 0, movedEntry)
|
|
return true
|
|
}
|
|
|
|
function dropBarModule(source, toRegion, beforeName) {
|
|
if (!source || !source.region || !source.moduleName || !toRegion) return false
|
|
if (source.region === toRegion && source.moduleName === beforeName) return false
|
|
if (!root.shell || typeof root.shell.mutateShellConfig !== "function") return false
|
|
|
|
var changed = false
|
|
root.shell.mutateShellConfig(function(config) {
|
|
changed = moveModuleInConfig(config, source.region, source.moduleName, toRegion, beforeName)
|
|
})
|
|
return changed
|
|
}
|
|
|
|
function moduleDropAtScene(scenePoint, sourceSlot) {
|
|
var sourceWindow = root.slotWindow(sourceSlot) || root.barDragWindow
|
|
for (var i = 0; i < debugModuleSlots.length; i++) {
|
|
var slot = debugModuleSlots[i]
|
|
if (!slot || slot === sourceSlot || !slot.visible || slot.width <= 0 || slot.height <= 0) continue
|
|
if (sourceWindow && !root.sameWindow(root.slotWindow(slot), sourceWindow)) continue
|
|
|
|
var slotPoint = { x: slot.x, y: slot.y }
|
|
try {
|
|
slotPoint = slot.mapToItem(null, 0, 0)
|
|
} catch (e) {
|
|
}
|
|
|
|
if (scenePoint.x >= slotPoint.x && scenePoint.x <= slotPoint.x + slot.width &&
|
|
scenePoint.y >= slotPoint.y && scenePoint.y <= slotPoint.y + slot.height) {
|
|
return {
|
|
slot: slot,
|
|
after: root.vertical ? scenePoint.y > slotPoint.y + slot.height / 2 : scenePoint.x > slotPoint.x + slot.width / 2
|
|
}
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
function visibleModuleSlot(region, name, sourceSlot) {
|
|
var sourceWindow = root.slotWindow(sourceSlot) || root.barDragWindow
|
|
for (var i = 0; i < debugModuleSlots.length; i++) {
|
|
var slot = debugModuleSlots[i]
|
|
if (!slot || slot === sourceSlot || slot.region !== region || slot.moduleName !== name ||
|
|
!slot.visible || slot.width <= 0 || slot.height <= 0) continue
|
|
if (sourceWindow && !root.sameWindow(root.slotWindow(slot), sourceWindow)) continue
|
|
return slot
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
function nextVisibleModuleName(region, afterName, sourceSlot) {
|
|
var entries = layoutEntries(region)
|
|
var found = false
|
|
for (var i = 0; i < entries.length; i++) {
|
|
var name = entryId(entries[i])
|
|
if (!found) {
|
|
found = name === afterName
|
|
continue
|
|
}
|
|
|
|
if (visibleModuleSlot(region, name, sourceSlot)) return name
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
function dropBarModuleAtTarget(sourceSlot, targetSlot, afterTarget) {
|
|
if (!sourceSlot || !targetSlot) return false
|
|
|
|
var beforeName = afterTarget ? nextVisibleModuleName(targetSlot.region, targetSlot.moduleName, sourceSlot) : targetSlot.moduleName
|
|
return dropBarModule(sourceSlot, targetSlot.region, beforeName)
|
|
}
|
|
|
|
function moduleTargetClickable(target) {
|
|
return target
|
|
&& target.visible !== false
|
|
&& target.opacity !== 0
|
|
&& target.interactive !== false
|
|
&& target.pressable !== false
|
|
&& target.concealed !== true
|
|
&& typeof target.triggerPress === "function"
|
|
}
|
|
|
|
function moduleClickTargetAt(slot, localX, localY) {
|
|
for (var i = clickTargets.length - 1; i >= 0; i--) {
|
|
var target = clickTargets[i]
|
|
if (!moduleTargetClickable(target)) continue
|
|
|
|
var targetPoint = { x: localX, y: localY }
|
|
try {
|
|
targetPoint = slot.mapToItem(target, localX, localY)
|
|
} catch (e) {
|
|
continue
|
|
}
|
|
|
|
if (targetPoint.x >= 0 && targetPoint.x <= target.width &&
|
|
targetPoint.y >= 0 && targetPoint.y <= target.height) {
|
|
return target
|
|
}
|
|
}
|
|
|
|
if (moduleTargetClickable(slot.activeItem)) return slot.activeItem
|
|
return null
|
|
}
|
|
|
|
function pressModuleClickTarget(slot, button, localX, localY) {
|
|
var target = moduleClickTargetAt(slot, localX, localY)
|
|
if (!target) return false
|
|
|
|
target.triggerPress(button)
|
|
return true
|
|
}
|
|
|
|
function colorHex(colorValue) {
|
|
var c = colorValue
|
|
if (typeof c === "string") c = Qt.color(c)
|
|
function hexChannel(value) {
|
|
var s = Math.round(Util.clamp(value, 0, 1) * 255).toString(16)
|
|
return s.length < 2 ? "0" + s : s
|
|
}
|
|
return "#" + hexChannel(c.r) + hexChannel(c.g) + hexChannel(c.b)
|
|
}
|
|
|
|
function setRequestedTransparency(value) {
|
|
var nextTransparent = value === true
|
|
requestedTransparent = nextTransparent
|
|
if (!nextTransparent) {
|
|
foregroundAnimationEnabled = false
|
|
useTransparentForeground = false
|
|
transparent = false
|
|
transparentForeground = themeForeground
|
|
restoreForegroundAnimation()
|
|
return
|
|
}
|
|
scheduleTransparentForegroundRefresh()
|
|
}
|
|
|
|
function restoreForegroundAnimation() {
|
|
Qt.callLater(function() {
|
|
Qt.callLater(function() { root.foregroundAnimationEnabled = true })
|
|
})
|
|
}
|
|
|
|
function scheduleTransparentForegroundRefresh() {
|
|
if (!requestedTransparent) {
|
|
transparentForeground = themeForeground
|
|
return
|
|
}
|
|
transparentForegroundTimer.restart()
|
|
}
|
|
|
|
function refreshTransparentForeground() {
|
|
if (!requestedTransparent || transparentForegroundProc.running) return
|
|
|
|
transparentForegroundProc.command = [
|
|
"omarchy-shell-bar-text-color",
|
|
root.position,
|
|
String(root.barSize),
|
|
colorHex(root.themeForeground),
|
|
colorHex(root.themeContrastForeground)
|
|
]
|
|
transparentForegroundProc.running = true
|
|
}
|
|
|
|
onRequestedTransparentChanged: scheduleTransparentForegroundRefresh()
|
|
onPositionChanged: scheduleTransparentForegroundRefresh()
|
|
onThemeForegroundChanged: scheduleTransparentForegroundRefresh()
|
|
onThemeContrastForegroundChanged: scheduleTransparentForegroundRefresh()
|
|
|
|
Timer {
|
|
id: transparentForegroundTimer
|
|
interval: 120
|
|
repeat: false
|
|
onTriggered: root.refreshTransparentForeground()
|
|
}
|
|
|
|
Process {
|
|
id: transparentForegroundProc
|
|
stdout: SplitParser {
|
|
onRead: function(line) {
|
|
var value = String(line || "").trim()
|
|
if (!/^#[0-9A-Fa-f]{6}$/.test(value)) return
|
|
|
|
root.foregroundAnimationEnabled = false
|
|
root.transparentForeground = value
|
|
if (root.requestedTransparent) {
|
|
root.useTransparentForeground = true
|
|
root.transparent = true
|
|
}
|
|
root.restoreForegroundAnimation()
|
|
}
|
|
}
|
|
}
|
|
|
|
FileView {
|
|
path: root.home + "/.config/omarchy/current"
|
|
watchChanges: true
|
|
printErrors: false
|
|
onFileChanged: root.scheduleTransparentForegroundRefresh()
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
Variants {
|
|
model: Quickshell.screens
|
|
|
|
delegate: Component {
|
|
DragGhostPanel {
|
|
required property var modelData
|
|
|
|
screen: modelData
|
|
ghostScreen: 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)
|
|
}
|
|
}
|
|
|
|
BorderSurface {
|
|
id: tooltipBubble
|
|
implicitWidth: tooltipLabel.implicitWidth + 20
|
|
implicitHeight: tooltipLabel.implicitHeight + 14
|
|
color: Color.tooltip.background
|
|
borderSpec: Border.surfaceSpec("tooltip", "border", Color.tooltip.border, 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 } }
|
|
|
|
component DragGhostPanel: PanelWindow {
|
|
id: ghostWindow
|
|
|
|
required property var ghostScreen
|
|
readonly property bool screenMatches: root.barDragScreen === ghostScreen ||
|
|
(root.barDragScreen && ghostScreen && root.barDragScreen.name && ghostScreen.name && root.barDragScreen.name === ghostScreen.name)
|
|
readonly property bool active: root.barDragSource && root.barDragScreen && screenMatches
|
|
readonly property var sourceItem: root.barDragSource ? root.barDragSource.activeItem : null
|
|
readonly property int ghostPadding: Style.space(1)
|
|
readonly property int ghostWidth: sourceItem ? Math.max(1, Math.ceil(sourceItem.width)) : 1
|
|
readonly property int ghostHeight: sourceItem ? Math.max(1, Math.ceil(sourceItem.height)) : 1
|
|
|
|
visible: active && sourceItem !== null
|
|
color: "transparent"
|
|
exclusionMode: ExclusionMode.Ignore
|
|
WlrLayershell.namespace: "omarchy-bar-drag-ghost"
|
|
WlrLayershell.layer: WlrLayer.Overlay
|
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
|
|
anchors {
|
|
top: true
|
|
bottom: true
|
|
left: true
|
|
right: true
|
|
}
|
|
|
|
// Visual-only drag feedback. Keep the input region empty so the ghost can
|
|
// sit under the cursor without stealing the MouseArea's active pointer grab.
|
|
mask: Region {}
|
|
|
|
Item {
|
|
visible: ghostWindow.visible
|
|
x: Math.round(root.barDragScreenX - root.barDragOffsetX - ghostWindow.ghostPadding)
|
|
y: Math.round(root.barDragScreenY - root.barDragOffsetY - ghostWindow.ghostPadding)
|
|
width: ghostWindow.ghostWidth + ghostWindow.ghostPadding * 2
|
|
height: ghostWindow.ghostHeight + ghostWindow.ghostPadding * 2
|
|
|
|
BorderSurface {
|
|
anchors.fill: parent
|
|
color: root.transparent ? "transparent" : root.background
|
|
borderSpec: Border.flat(root.barForeground, 1)
|
|
radius: Math.min(Style.cornerRadius, height / 2)
|
|
opacity: root.transparent ? 0.45 : 0.94
|
|
}
|
|
|
|
Image {
|
|
anchors.fill: parent
|
|
anchors.margins: ghostWindow.ghostPadding
|
|
source: root.barDragImageUrl
|
|
fillMode: Image.Stretch
|
|
smooth: true
|
|
opacity: 0.84
|
|
}
|
|
}
|
|
}
|
|
|
|
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 }
|
|
|
|
HoverHandler {
|
|
onHoveredChanged: root.setCenterSectionHovered(hovered)
|
|
}
|
|
|
|
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: centerConfigControl.visible ? centerConfigControl.left : centerAnchorModule.left
|
|
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
|
}
|
|
|
|
ModuleSlot {
|
|
id: centerAnchorModule
|
|
visible: centerRoot.hasAnchor
|
|
entry: centerRoot.anchorEntry
|
|
region: "center"
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
BarConfigControl {
|
|
id: centerConfigControl
|
|
|
|
visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock"
|
|
clockHovered: centerAnchorModule.hovered
|
|
centerHovered: root.centerSectionRevealHeld && !root.centerHoverRevealSuppressed
|
|
anchors.right: centerAnchorModule.left
|
|
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
|
}
|
|
|
|
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 }
|
|
|
|
HoverHandler {
|
|
onHoveredChanged: root.setCenterSectionHovered(hovered)
|
|
}
|
|
|
|
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: centerConfigControl.visible ? centerConfigControl.top : centerAnchorModule.top
|
|
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
|
}
|
|
|
|
ModuleSlot {
|
|
id: centerAnchorModule
|
|
visible: centerRoot.hasAnchor
|
|
entry: centerRoot.anchorEntry
|
|
region: "center"
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
BarConfigControl {
|
|
id: centerConfigControl
|
|
|
|
visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock"
|
|
clockHovered: centerAnchorModule.hovered
|
|
centerHovered: root.centerSectionRevealHeld && !root.centerHoverRevealSuppressed
|
|
anchors.bottom: centerAnchorModule.top
|
|
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
|
}
|
|
|
|
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
|
|
|
|
onDoubleClicked: function(mouse) {
|
|
if (mouse.button === Qt.LeftButton) {
|
|
root.toggleTransparency()
|
|
mouse.accepted = true
|
|
}
|
|
}
|
|
}
|
|
|
|
component BarConfigControl: Item {
|
|
id: configControl
|
|
|
|
property bool clockHovered: false
|
|
property bool centerHovered: false
|
|
property bool openWhenReady: false
|
|
|
|
readonly property var panelItem: configPanelLoader.item
|
|
readonly property bool panelOpen: panelItem ? panelItem.opened === true : false
|
|
readonly property bool revealed: visible && (clockHovered || centerHovered || controlHover.hovered || panelOpen)
|
|
|
|
implicitWidth: button.implicitWidth
|
|
implicitHeight: button.implicitHeight
|
|
width: implicitWidth
|
|
height: implicitHeight
|
|
z: 500
|
|
|
|
HoverHandler { id: controlHover }
|
|
|
|
Component.onCompleted: root.registerConfigControl(configControl)
|
|
Component.onDestruction: root.unregisterConfigControl(configControl)
|
|
|
|
function configurePanel(panel) {
|
|
if (!panel) return
|
|
panel.bar = root
|
|
panel.anchorItem = button
|
|
}
|
|
|
|
function openPanel() {
|
|
if (!panelItem) {
|
|
openWhenReady = true
|
|
return
|
|
}
|
|
panelItem.open()
|
|
}
|
|
|
|
function togglePanel() {
|
|
if (!panelItem) {
|
|
openPanel()
|
|
return
|
|
}
|
|
panelItem.toggle()
|
|
}
|
|
|
|
WidgetButton {
|
|
id: button
|
|
|
|
anchors.fill: parent
|
|
bar: root
|
|
text: ""
|
|
keepSpace: true
|
|
concealed: !configControl.revealed
|
|
dimmed: configControl.revealed && !controlHover.hovered && !configControl.panelOpen
|
|
interactive: configControl.revealed
|
|
horizontalMargin: 6.5
|
|
verticalPadding: 6
|
|
tooltipText: "Bar config"
|
|
onPressed: function(b) {
|
|
if (b === Qt.LeftButton) configControl.togglePanel()
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: configPanelLoader
|
|
|
|
active: true
|
|
source: Qt.resolvedUrl("BarConfigPanel.qml")
|
|
onLoaded: {
|
|
configControl.configurePanel(item)
|
|
if (configControl.openWhenReady) {
|
|
configControl.openWhenReady = false
|
|
item.open()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
readonly property bool hovered: moduleHover.hovered
|
|
readonly property bool dragSource: root.barDragSource === slot
|
|
readonly property bool panelOpen: root.activePopout === slot.activeItem
|
|
readonly property real openIndicatorInlineOffset: {
|
|
var item = slot.activeItem
|
|
if (!item || !("openIndicatorInlineOffset" in item)) return 0
|
|
var offset = Number(item.openIndicatorInlineOffset)
|
|
return isFinite(offset) ? offset : 0
|
|
}
|
|
|
|
implicitWidth: activeItem && activeItem.visible ? (root.vertical ? root.barSize : activeItem.implicitWidth) : 0
|
|
implicitHeight: activeItem && activeItem.visible ? activeItem.implicitHeight : 0
|
|
width: implicitWidth
|
|
height: implicitHeight
|
|
z: modulePointer.dragging ? 100 : 0
|
|
|
|
Component.onCompleted: root.registerDebugModuleSlot(slot)
|
|
Component.onDestruction: {
|
|
if (root.barDragSource === slot) root.clearBarDrag()
|
|
root.unregisterDebugModuleSlot(slot)
|
|
}
|
|
|
|
HoverHandler { id: moduleHover }
|
|
|
|
BorderSurface {
|
|
visible: slot.dragSource
|
|
anchors.fill: parent
|
|
anchors.margins: Style.space(1)
|
|
color: root.transparent ? "transparent" : root.background
|
|
borderSpec: Border.flat(root.barForeground, 1)
|
|
radius: Math.min(Style.cornerRadius, height / 2)
|
|
opacity: root.transparent ? 0.22 : 0.32
|
|
}
|
|
|
|
Loader {
|
|
id: componentLoader
|
|
active: !slot.qmlCustom && !slot.registered
|
|
sourceComponent: slot.commandCustom ? customCommandModuleComponent : emptyModuleComponent
|
|
anchors.fill: parent
|
|
opacity: slot.dragSource ? 0.22 : 1.0
|
|
onLoaded: {
|
|
slot.injectProps()
|
|
Qt.callLater(slot.injectProps)
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: registryLoader
|
|
active: slot.registered
|
|
sourceComponent: slot.registered ? slot.registryComponent : null
|
|
anchors.fill: parent
|
|
opacity: slot.dragSource ? 0.22 : 1.0
|
|
onLoaded: {
|
|
slot.injectProps()
|
|
Qt.callLater(slot.injectProps)
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: qmlLoader
|
|
active: slot.qmlCustom
|
|
source: slot.qmlCustom ? root.customModuleSource(slot.entry) : ""
|
|
anchors.fill: parent
|
|
opacity: slot.dragSource ? 0.22 : 1.0
|
|
onLoaded: {
|
|
slot.injectProps()
|
|
Qt.callLater(slot.injectProps)
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: openPanelIndicator
|
|
|
|
readonly property int inset: Style.space(2)
|
|
|
|
visible: opacity > 0
|
|
opacity: slot.panelOpen && !slot.dragSource ? 0.9 : 0
|
|
color: Color.accent
|
|
radius: Math.min(width, height) / 2
|
|
width: root.vertical ? Style.space(2) : Math.max(Style.space(10), Math.round(parent.width * 0.55))
|
|
height: root.vertical ? Math.max(Style.space(10), Math.round(parent.height * 0.55)) : Style.space(2)
|
|
x: root.vertical
|
|
? (root.position === "left" ? parent.width - width - inset : inset)
|
|
: (slot.openIndicatorInlineOffset === 0 ? Math.round((parent.width - width) / 2) : (parent.width - width) / 2 + slot.openIndicatorInlineOffset)
|
|
y: root.vertical
|
|
? (slot.openIndicatorInlineOffset === 0 ? Math.round((parent.height - height) / 2) : (parent.height - height) / 2 + slot.openIndicatorInlineOffset)
|
|
: (root.position === "top" ? parent.height - height - inset : inset)
|
|
z: 50
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation { duration: 120; easing.type: Easing.OutCubic }
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: !root.vertical && root.barDragTarget === slot && !root.barDragAfter
|
|
anchors {
|
|
left: parent.left
|
|
top: parent.top
|
|
bottom: parent.bottom
|
|
}
|
|
width: 2
|
|
color: root.barForeground
|
|
opacity: 0.9
|
|
}
|
|
|
|
Rectangle {
|
|
visible: !root.vertical && root.barDragTarget === slot && root.barDragAfter
|
|
anchors {
|
|
right: parent.right
|
|
top: parent.top
|
|
bottom: parent.bottom
|
|
}
|
|
width: 2
|
|
color: root.barForeground
|
|
opacity: 0.9
|
|
}
|
|
|
|
Rectangle {
|
|
visible: root.vertical && root.barDragTarget === slot && !root.barDragAfter
|
|
anchors {
|
|
left: parent.left
|
|
right: parent.right
|
|
top: parent.top
|
|
}
|
|
height: 2
|
|
color: root.barForeground
|
|
opacity: 0.9
|
|
}
|
|
|
|
Rectangle {
|
|
visible: root.vertical && root.barDragTarget === slot && root.barDragAfter
|
|
anchors {
|
|
left: parent.left
|
|
right: parent.right
|
|
bottom: parent.bottom
|
|
}
|
|
height: 2
|
|
color: root.barForeground
|
|
opacity: 0.9
|
|
}
|
|
|
|
MouseArea {
|
|
id: modulePointer
|
|
|
|
property bool dragging: false
|
|
property bool suppressClick: false
|
|
property real pressedX: 0
|
|
property real pressedY: 0
|
|
readonly property bool canReorder: root.shell && typeof root.shell.mutateShellConfig === "function"
|
|
readonly property real dragThreshold: Style.space(4)
|
|
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton
|
|
enabled: slot.visible && slot.width > 0 && slot.height > 0
|
|
propagateComposedEvents: true
|
|
cursorShape: root.moduleClickTargetAt(slot, mouseX, mouseY) ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
// Do not assign drag.target here: ModuleSlot is owned by Row/Column
|
|
// positioners, and mutating slot.x/slot.y can leave stale offsets that
|
|
// make neighboring modules overlap after a small aborted drag.
|
|
|
|
onPressed: function(mouse) {
|
|
dragging = false
|
|
suppressClick = false
|
|
pressedX = mouse.x
|
|
pressedY = mouse.y
|
|
root.clearBarDrag()
|
|
}
|
|
|
|
onPositionChanged: function(mouse) {
|
|
if (!canReorder || !(mouse.buttons & Qt.LeftButton)) return
|
|
|
|
var distance = Math.abs(mouse.x - pressedX) + Math.abs(mouse.y - pressedY)
|
|
if (distance >= dragThreshold) {
|
|
if (!dragging) {
|
|
root.barDragWindow = root.targetWindow(slot.activeItem) || root.targetWindow(slot)
|
|
root.barDragScreen = root.barDragWindow ? root.barDragWindow.screen : null
|
|
root.barDragOffsetX = pressedX
|
|
root.barDragOffsetY = pressedY
|
|
root.captureBarDragGhost(slot)
|
|
root.barDragSource = slot
|
|
}
|
|
dragging = true
|
|
root.hideTooltip(slot.activeItem)
|
|
}
|
|
|
|
if (dragging) {
|
|
var scenePoint = slot.mapToItem(null, mouse.x, mouse.y)
|
|
var screenPoint = root.barDragScreenPoint(scenePoint)
|
|
root.barDragSceneX = scenePoint.x
|
|
root.barDragSceneY = scenePoint.y
|
|
root.barDragScreenX = screenPoint.x
|
|
root.barDragScreenY = screenPoint.y
|
|
|
|
var drop = root.moduleDropAtScene(scenePoint, slot)
|
|
root.barDragTarget = drop ? drop.slot : null
|
|
root.barDragAfter = drop ? drop.after : false
|
|
}
|
|
}
|
|
|
|
onReleased: function(mouse) {
|
|
var wasDragging = dragging
|
|
var targetSlot = root.barDragTarget
|
|
var afterTarget = root.barDragAfter
|
|
|
|
if (wasDragging) suppressClick = true
|
|
|
|
dragging = false
|
|
root.clearBarDrag()
|
|
|
|
if (wasDragging && targetSlot) {
|
|
root.dropBarModuleAtTarget(slot, targetSlot, afterTarget)
|
|
mouse.accepted = true
|
|
} else if (!wasDragging) {
|
|
mouse.accepted = false
|
|
}
|
|
}
|
|
|
|
onCanceled: {
|
|
dragging = false
|
|
suppressClick = false
|
|
root.clearBarDrag()
|
|
}
|
|
|
|
onClicked: function(mouse) {
|
|
if (suppressClick) {
|
|
suppressClick = false
|
|
mouse.accepted = true
|
|
return
|
|
}
|
|
|
|
if (!root.pressModuleClickTarget(slot, mouse.button, mouse.x, mouse.y)) mouse.accepted = false
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|