Extract clock and tray bar widgets
This commit is contained in:
+2
-495
@@ -1,8 +1,6 @@
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Services.SystemTray
|
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import Quickshell.Widgets
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import qs.Commons
|
import qs.Commons
|
||||||
@@ -54,7 +52,6 @@ Item {
|
|||||||
Behavior on foreground { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
Behavior on foreground { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
||||||
Behavior on background { 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 } }
|
Behavior on urgent { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
||||||
property bool clockAlt: false
|
|
||||||
property var tooltipTarget: null
|
property var tooltipTarget: null
|
||||||
property var pendingTooltipTarget: null
|
property var pendingTooltipTarget: null
|
||||||
property string tooltipText: ""
|
property string tooltipText: ""
|
||||||
@@ -214,14 +211,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function builtinModuleComponent(name) {
|
|
||||||
switch (String(name)) {
|
|
||||||
case "clock": return clockModuleComponent
|
|
||||||
case "tray": return trayModuleComponent
|
|
||||||
default: return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function expandPath(path) {
|
function expandPath(path) {
|
||||||
var value = String(path || "")
|
var value = String(path || "")
|
||||||
if (value === "") return ""
|
if (value === "") return ""
|
||||||
@@ -322,48 +311,6 @@ Item {
|
|||||||
clearTooltip()
|
clearTooltip()
|
||||||
}
|
}
|
||||||
|
|
||||||
function trayIconSource(icon) {
|
|
||||||
var value = String(icon || "")
|
|
||||||
var marker = "?path="
|
|
||||||
var markerIndex = value.indexOf(marker)
|
|
||||||
if (markerIndex === -1) return value
|
|
||||||
|
|
||||||
var name = value.substring(0, markerIndex).split("/").pop()
|
|
||||||
var iconPath = value.substring(markerIndex + marker.length).split("&")[0]
|
|
||||||
return Util.fileUrl(iconPath + "/hicolor/16x16/status/" + name + ".png")
|
|
||||||
}
|
|
||||||
|
|
||||||
function trayTooltip(item) {
|
|
||||||
return item.tooltipTitle || item.title || item.id || ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function clockEntry() {
|
|
||||||
var serial = barConfigSerial
|
|
||||||
var sections = ["left", "center", "right"]
|
|
||||||
for (var i = 0; i < sections.length; i++) {
|
|
||||||
var entries = layoutEntries(sections[i])
|
|
||||||
var idx = entryIndex(entries, "clock")
|
|
||||||
if (idx !== -1) return entries[idx]
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function clockText() {
|
|
||||||
var entry = clockEntry()
|
|
||||||
if (clockAlt)
|
|
||||||
return Qt.formatDateTime(systemClock.date, moduleString(entry, "formatAlt", "dd MMMM 'W'ww yyyy"))
|
|
||||||
|
|
||||||
if (vertical)
|
|
||||||
return Qt.formatDateTime(systemClock.date, moduleString(entry, "verticalFormat", "HH\n—\nmm"))
|
|
||||||
|
|
||||||
return Qt.formatDateTime(systemClock.date, moduleString(entry, "format", "dddd HH:mm"))
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemClock {
|
|
||||||
id: systemClock
|
|
||||||
precision: SystemClock.Minutes
|
|
||||||
}
|
|
||||||
|
|
||||||
Process { id: launcher }
|
Process { id: launcher }
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -543,8 +490,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component { id: emptyModuleComponent; Item { implicitWidth: 0; implicitHeight: 0; visible: false } }
|
Component { id: emptyModuleComponent; Item { implicitWidth: 0; implicitHeight: 0; visible: false } }
|
||||||
Component { id: clockModuleComponent; ClockModule {} }
|
|
||||||
Component { id: trayModuleComponent; TrayModule {} }
|
|
||||||
|
|
||||||
function findCenterAnchorEntry() {
|
function findCenterAnchorEntry() {
|
||||||
var entries = root.layoutEntries("center")
|
var entries = root.layoutEntries("center")
|
||||||
@@ -717,13 +662,12 @@ Item {
|
|||||||
readonly property string moduleName: root.entryId(entry)
|
readonly property string moduleName: root.entryId(entry)
|
||||||
readonly property var moduleSettings: root.entrySettings(entry)
|
readonly property var moduleSettings: root.entrySettings(entry)
|
||||||
readonly property string customType: root.customModuleType(entry)
|
readonly property string customType: root.customModuleType(entry)
|
||||||
readonly property var builtinComponent: customType ? null : root.builtinModuleComponent(moduleName)
|
|
||||||
// Re-evaluate when the registry mutates (Component reference changes,
|
// Re-evaluate when the registry mutates (Component reference changes,
|
||||||
// plugin enabled/disabled, etc.). Reading the `widgets` property creates
|
// plugin enabled/disabled, etc.). Reading the `widgets` property creates
|
||||||
// the binding dependency — the wrapped function call alone wouldn't.
|
// the binding dependency — the wrapped function call alone wouldn't.
|
||||||
readonly property var registryComponent: {
|
readonly property var registryComponent: {
|
||||||
var w = root.barWidgetRegistry.widgets
|
var w = root.barWidgetRegistry.widgets
|
||||||
if (customType || builtinComponent) return null
|
if (customType) return null
|
||||||
var registryName = root.canonicalWidgetId(moduleName)
|
var registryName = root.canonicalWidgetId(moduleName)
|
||||||
return w[registryName] ? w[registryName].component : null
|
return w[registryName] ? w[registryName].component : null
|
||||||
}
|
}
|
||||||
@@ -744,7 +688,7 @@ Item {
|
|||||||
Loader {
|
Loader {
|
||||||
id: componentLoader
|
id: componentLoader
|
||||||
active: !slot.qmlCustom && !slot.registered
|
active: !slot.qmlCustom && !slot.registered
|
||||||
sourceComponent: slot.builtinComponent || (slot.commandCustom ? customCommandModuleComponent : emptyModuleComponent)
|
sourceComponent: slot.commandCustom ? customCommandModuleComponent : emptyModuleComponent
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
slot.injectProps()
|
slot.injectProps()
|
||||||
@@ -913,441 +857,4 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component ClockModule: ModuleButton {
|
|
||||||
text: root.clockText()
|
|
||||||
horizontalMargin: 8.75
|
|
||||||
verticalPadding: 8.75
|
|
||||||
onPressed: function(button) {
|
|
||||||
if (button === Qt.RightButton) root.run("omarchy-launch-floating-terminal-with-presentation omarchy-tz-select")
|
|
||||||
else root.clockAlt = !root.clockAlt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component TrayModule: Item {
|
|
||||||
id: trayRoot
|
|
||||||
|
|
||||||
property bool expanded: false
|
|
||||||
property bool managePopupOpen: false
|
|
||||||
function close() { managePopupOpen = false }
|
|
||||||
|
|
||||||
// Re-resolve the tray's own entry settings whenever the bar layout reloads.
|
|
||||||
readonly property var trayEntry: {
|
|
||||||
var serial = root.barConfigSerial
|
|
||||||
var sections = ["left", "center", "right"]
|
|
||||||
for (var s = 0; s < sections.length; s++) {
|
|
||||||
var arr = root.layoutEntries(sections[s])
|
|
||||||
for (var i = 0; i < arr.length; i++) {
|
|
||||||
if (root.entryId(arr[i]) === "tray") return arr[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ({})
|
|
||||||
}
|
|
||||||
readonly property var pinnedIds: Array.isArray(trayEntry.pinned) ? trayEntry.pinned : []
|
|
||||||
readonly property var hiddenIds: Array.isArray(trayEntry.hidden) ? trayEntry.hidden : []
|
|
||||||
|
|
||||||
function classifyItem(item) {
|
|
||||||
var iid = String(item.id || "")
|
|
||||||
if (hiddenIds.indexOf(iid) !== -1) return "hidden"
|
|
||||||
if (pinnedIds.indexOf(iid) !== -1) return "pinned"
|
|
||||||
return "drawer"
|
|
||||||
}
|
|
||||||
|
|
||||||
function bucket(category) {
|
|
||||||
var values = SystemTray.items.values
|
|
||||||
var result = []
|
|
||||||
for (var i = 0; i < values.length; i++) {
|
|
||||||
var item = values[i]
|
|
||||||
if (item.status === Status.Passive) continue
|
|
||||||
if (category === "all") { result.push(item); continue }
|
|
||||||
if (classifyItem(item) === category) result.push(item)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property var pinnedItems: bucket("pinned")
|
|
||||||
readonly property var drawerItems: bucket("drawer")
|
|
||||||
readonly property var allItems: bucket("all")
|
|
||||||
readonly property int drawerCount: drawerItems.length
|
|
||||||
readonly property int drawerExtent: drawerCount > 0 ? drawerCount * 16 + (drawerCount - 1) * 17 : 0
|
|
||||||
// Match Waybar's group/tray-expander drawer transition-duration.
|
|
||||||
readonly property int animationDuration: 600
|
|
||||||
property real revealProgress: expanded ? 1 : 0
|
|
||||||
readonly property real revealExtent: drawerExtent * revealProgress
|
|
||||||
|
|
||||||
Behavior on revealProgress {
|
|
||||||
NumberAnimation { duration: trayRoot.animationDuration; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
|
|
||||||
function persistTrayState(pinned, hidden) {
|
|
||||||
if (!root.shell || typeof root.shell.updateEntryInline !== "function") return
|
|
||||||
root.shell.updateEntryInline("tray", { id: "tray", pinned: pinned, hidden: hidden })
|
|
||||||
}
|
|
||||||
|
|
||||||
function togglePin(iid) {
|
|
||||||
var p = pinnedIds.slice(), h = hiddenIds.slice()
|
|
||||||
var idx = p.indexOf(iid)
|
|
||||||
if (idx !== -1) p.splice(idx, 1)
|
|
||||||
else {
|
|
||||||
p.push(iid)
|
|
||||||
var hi = h.indexOf(iid); if (hi !== -1) h.splice(hi, 1)
|
|
||||||
}
|
|
||||||
persistTrayState(p, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleHide(iid) {
|
|
||||||
var p = pinnedIds.slice(), h = hiddenIds.slice()
|
|
||||||
var idx = h.indexOf(iid)
|
|
||||||
if (idx !== -1) h.splice(idx, 1)
|
|
||||||
else {
|
|
||||||
h.push(iid)
|
|
||||||
var pi = p.indexOf(iid); if (pi !== -1) p.splice(pi, 1)
|
|
||||||
}
|
|
||||||
persistTrayState(p, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
visible: pinnedItems.length > 0 || drawerCount > 0
|
|
||||||
clip: false
|
|
||||||
implicitWidth: root.vertical ? root.barSize : trayContent.implicitWidth
|
|
||||||
implicitHeight: root.vertical ? trayContent.implicitHeight : root.barSize
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: trayContent
|
|
||||||
anchors.fill: parent
|
|
||||||
sourceComponent: root.vertical ? verticalTray : horizontalTray
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: horizontalTray
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: horizontalTrayRoot
|
|
||||||
|
|
||||||
readonly property int pinnedWidth: pinnedRow.implicitWidth
|
|
||||||
readonly property int drawerBlockWidth: trayRoot.allItems.length > 0 ? expandIcon.implicitWidth + trayRoot.drawerExtent : 0
|
|
||||||
|
|
||||||
implicitWidth: pinnedWidth + drawerBlockWidth
|
|
||||||
implicitHeight: root.barSize
|
|
||||||
|
|
||||||
// Mask out the empty area the collapsed drawer reserves for its slide-in,
|
|
||||||
// so hovering it doesn't trigger expand and clicks pass through.
|
|
||||||
containmentMask: QtObject {
|
|
||||||
function contains(point: point): bool {
|
|
||||||
if (point.y < 0 || point.y > horizontalTrayRoot.height) return false
|
|
||||||
// Drawer reveals leftward; chevron sits at the right end when collapsed
|
|
||||||
// and slides left as it opens. The visible region starts at the chevron.
|
|
||||||
var chevronX = trayRoot.drawerExtent - trayRoot.revealExtent
|
|
||||||
if (point.x >= chevronX && point.x <= horizontalTrayRoot.drawerBlockWidth) return true
|
|
||||||
// Pinned items, placed to the right of the drawer block.
|
|
||||||
var pinnedStart = horizontalTrayRoot.drawerBlockWidth
|
|
||||||
return point.x >= pinnedStart && point.x <= horizontalTrayRoot.implicitWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: drawerArea
|
|
||||||
x: 0
|
|
||||||
width: horizontalTrayRoot.drawerBlockWidth
|
|
||||||
height: root.barSize
|
|
||||||
visible: trayRoot.allItems.length > 0
|
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
onHoveredChanged: trayRoot.expanded = hovered
|
|
||||||
}
|
|
||||||
|
|
||||||
ModuleButton {
|
|
||||||
id: expandIcon
|
|
||||||
width: implicitWidth
|
|
||||||
height: implicitHeight
|
|
||||||
x: trayRoot.drawerExtent - trayRoot.revealExtent
|
|
||||||
text: ""
|
|
||||||
horizontalMargin: 9
|
|
||||||
verticalPadding: 6
|
|
||||||
onPressed: function(button) {
|
|
||||||
if (button === Qt.RightButton) trayRoot.managePopupOpen = !trayRoot.managePopupOpen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: trayClip
|
|
||||||
x: expandIcon.width
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: trayRoot.drawerExtent
|
|
||||||
height: root.barSize
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: trayIcons
|
|
||||||
x: trayRoot.drawerExtent - trayRoot.revealExtent
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Style.space(17)
|
|
||||||
layer.enabled: true
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: trayRoot.drawerItems
|
|
||||||
TrayItem {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: pinnedRow
|
|
||||||
x: drawerArea.x + horizontalTrayRoot.drawerBlockWidth
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Style.space(17)
|
|
||||||
leftPadding: trayRoot.pinnedItems.length > 0 && trayRoot.allItems.length > 0 ? Style.space(6) : 0
|
|
||||||
Repeater {
|
|
||||||
model: trayRoot.pinnedItems
|
|
||||||
TrayItem {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: verticalTray
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: verticalTrayRoot
|
|
||||||
|
|
||||||
readonly property int pinnedHeight: pinnedCol.implicitHeight
|
|
||||||
readonly property int drawerBlockHeight: trayRoot.allItems.length > 0 ? expandIcon.implicitHeight + trayRoot.drawerExtent : 0
|
|
||||||
|
|
||||||
implicitWidth: root.barSize
|
|
||||||
implicitHeight: pinnedHeight + drawerBlockHeight
|
|
||||||
|
|
||||||
containmentMask: QtObject {
|
|
||||||
function contains(point: point): bool {
|
|
||||||
if (point.x < 0 || point.x > verticalTrayRoot.width) return false
|
|
||||||
var chevronY = trayRoot.drawerExtent - trayRoot.revealExtent
|
|
||||||
if (point.y >= chevronY && point.y <= verticalTrayRoot.drawerBlockHeight) return true
|
|
||||||
var pinnedStart = verticalTrayRoot.drawerBlockHeight
|
|
||||||
return point.y >= pinnedStart && point.y <= verticalTrayRoot.implicitHeight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: drawerArea
|
|
||||||
y: 0
|
|
||||||
width: root.barSize
|
|
||||||
height: verticalTrayRoot.drawerBlockHeight
|
|
||||||
visible: trayRoot.allItems.length > 0
|
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
onHoveredChanged: trayRoot.expanded = hovered
|
|
||||||
}
|
|
||||||
|
|
||||||
ModuleButton {
|
|
||||||
id: expandIcon
|
|
||||||
width: implicitWidth
|
|
||||||
height: implicitHeight
|
|
||||||
y: trayRoot.drawerExtent - trayRoot.revealExtent
|
|
||||||
text: ""
|
|
||||||
textRotation: 90
|
|
||||||
horizontalMargin: 9
|
|
||||||
verticalPadding: 6
|
|
||||||
onPressed: function(button) {
|
|
||||||
if (button === Qt.RightButton) trayRoot.managePopupOpen = !trayRoot.managePopupOpen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: trayClip
|
|
||||||
y: expandIcon.height
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
width: root.barSize
|
|
||||||
height: trayRoot.drawerExtent
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: trayIcons
|
|
||||||
y: trayRoot.drawerExtent - trayRoot.revealExtent
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: Style.space(17)
|
|
||||||
layer.enabled: true
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: trayRoot.drawerItems
|
|
||||||
TrayItem {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: pinnedCol
|
|
||||||
y: drawerArea.y + verticalTrayRoot.drawerBlockHeight
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: Style.space(17)
|
|
||||||
topPadding: trayRoot.pinnedItems.length > 0 && trayRoot.allItems.length > 0 ? Style.space(6) : 0
|
|
||||||
Repeater {
|
|
||||||
model: trayRoot.pinnedItems
|
|
||||||
TrayItem {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PopupCard {
|
|
||||||
id: managePopup
|
|
||||||
anchorItem: trayRoot
|
|
||||||
owner: trayRoot
|
|
||||||
bar: root
|
|
||||||
open: trayRoot.managePopupOpen
|
|
||||||
contentWidth: managePopup.fittedContentWidth(Style.space(300))
|
|
||||||
contentHeight: managePopup.fittedContentHeight(manageColumn.implicitHeight)
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: manageColumn
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: Style.space(8)
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Tray icons"
|
|
||||||
color: root.foreground
|
|
||||||
font.family: root.fontFamily
|
|
||||||
font.pixelSize: Style.font.body
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Pinned icons stay visible. Hidden icons never show."
|
|
||||||
color: Qt.darker(root.foreground, 1.4)
|
|
||||||
font.family: root.fontFamily
|
|
||||||
font.pixelSize: Style.font.caption
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
visible: trayRoot.allItems.length === 0
|
|
||||||
text: "No tray items reporting."
|
|
||||||
color: Qt.darker(root.foreground, 1.5)
|
|
||||||
font.family: root.fontFamily
|
|
||||||
font.pixelSize: Style.font.bodySmall
|
|
||||||
font.italic: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: trayRoot.allItems
|
|
||||||
delegate: Item {
|
|
||||||
id: rowRoot
|
|
||||||
required property var modelData
|
|
||||||
required property int index
|
|
||||||
width: manageColumn.width
|
|
||||||
implicitHeight: 28
|
|
||||||
|
|
||||||
readonly property string itemId: String(modelData.id || "")
|
|
||||||
readonly property string displayName: {
|
|
||||||
var t = String(modelData.title || "").trim()
|
|
||||||
if (t) return t
|
|
||||||
var tt = String(modelData.tooltipTitle || "").trim()
|
|
||||||
if (tt) return tt
|
|
||||||
var id = String(modelData.id || "")
|
|
||||||
var slash = id.lastIndexOf("/")
|
|
||||||
return slash !== -1 ? id.substring(slash + 1) : (id || "Unknown")
|
|
||||||
}
|
|
||||||
readonly property bool isPinned: trayRoot.pinnedIds.indexOf(itemId) !== -1
|
|
||||||
readonly property bool isHidden: trayRoot.hiddenIds.indexOf(itemId) !== -1
|
|
||||||
|
|
||||||
IconImage {
|
|
||||||
id: rowIcon
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.left: parent.left
|
|
||||||
implicitSize: 16
|
|
||||||
width: 16
|
|
||||||
height: 16
|
|
||||||
source: root.trayIconSource(rowRoot.modelData.icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.left: rowIcon.right
|
|
||||||
anchors.leftMargin: Style.space(10)
|
|
||||||
anchors.right: rowHideBtn.left
|
|
||||||
anchors.rightMargin: Style.space(8)
|
|
||||||
text: rowRoot.displayName
|
|
||||||
color: root.foreground
|
|
||||||
font.family: root.fontFamily
|
|
||||||
font.pixelSize: Style.font.bodySmall
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: rowPinBtn
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.right: parent.right
|
|
||||||
iconText: ""
|
|
||||||
text: rowRoot.isPinned ? "Unpin" : "Pin"
|
|
||||||
foreground: root.foreground
|
|
||||||
horizontalPadding: 8
|
|
||||||
verticalPadding: 3
|
|
||||||
iconSize: Style.font.bodySmall
|
|
||||||
fontSize: Style.font.bodySmall
|
|
||||||
onClicked: trayRoot.togglePin(rowRoot.itemId)
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: rowHideBtn
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.right: rowPinBtn.left
|
|
||||||
anchors.rightMargin: Style.space(6)
|
|
||||||
iconText: ""
|
|
||||||
text: rowRoot.isHidden ? "Show" : "Hide"
|
|
||||||
foreground: root.foreground
|
|
||||||
horizontalPadding: 8
|
|
||||||
verticalPadding: 3
|
|
||||||
iconSize: Style.font.bodySmall
|
|
||||||
fontSize: Style.font.bodySmall
|
|
||||||
onClicked: trayRoot.toggleHide(rowRoot.itemId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component TrayItem: Item {
|
|
||||||
id: trayItemRoot
|
|
||||||
|
|
||||||
required property var modelData
|
|
||||||
|
|
||||||
visible: modelData.status !== Status.Passive
|
|
||||||
implicitWidth: visible ? 16 : 0
|
|
||||||
implicitHeight: visible ? 16 : 0
|
|
||||||
|
|
||||||
IconImage {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
implicitSize: 12
|
|
||||||
width: 12
|
|
||||||
height: 12
|
|
||||||
source: root.trayIconSource(trayItemRoot.modelData.icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onEntered: root.showTooltip(trayItemRoot, root.trayTooltip(modelData))
|
|
||||||
onExited: root.hideTooltip(trayItemRoot)
|
|
||||||
onClicked: function(mouse) {
|
|
||||||
if (mouse.button === Qt.RightButton && trayItemRoot.modelData.hasMenu) {
|
|
||||||
var point = trayItemRoot.QsWindow.contentItem.mapFromItem(trayItemRoot, mouse.x, mouse.y)
|
|
||||||
trayItemRoot.modelData.display(trayItemRoot.QsWindow.window, point.x, point.y)
|
|
||||||
} else if (mouse.button === Qt.MiddleButton) {
|
|
||||||
trayItemRoot.modelData.secondaryActivate()
|
|
||||||
} else {
|
|
||||||
trayItemRoot.modelData.activate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onWheel: function(wheel) {
|
|
||||||
trayItemRoot.modelData.scroll(wheel.angleDelta.y, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property bool tooltipHovered: visible && opacity > 0 && mouseArea.containsMouse
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ QtObject {
|
|||||||
"networkPanel": { displayName: "Network", description: "Wi-Fi list and connection state", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Network" },
|
"networkPanel": { displayName: "Network", description: "Wi-Fi list and connection state", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Network" },
|
||||||
"powerPanel": { displayName: "Power", description: "Battery, power profile, and system stats", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Power" },
|
"powerPanel": { displayName: "Power", description: "Battery, power profile, and system stats", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Power" },
|
||||||
"bluetoothPanel": { displayName: "Bluetooth", description: "Bluetooth device list with connect/disconnect", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Bluetooth" },
|
"bluetoothPanel": { displayName: "Bluetooth", description: "Bluetooth device list with connect/disconnect", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Bluetooth" },
|
||||||
|
"clock": { displayName: "Clock", description: "Date / time text", category: "Time", allowMultiple: false },
|
||||||
"daytime": { displayName: "Daytime", description: "Day/time label with date tooltip", category: "Time", allowMultiple: false, settingsForm: "daytimeSettings" },
|
"daytime": { displayName: "Daytime", description: "Day/time label with date tooltip", category: "Time", allowMultiple: false, settingsForm: "daytimeSettings" },
|
||||||
"indicators": { displayName: "Indicators", description: "Manual state indicators", category: "Status", allowMultiple: false },
|
"indicators": { displayName: "Indicators", description: "Manual state indicators", category: "Status", allowMultiple: false },
|
||||||
"notificationCenter": { displayName: "Notification center", description: "Recent notifications + DND", category: "Status", allowMultiple: false },
|
"notificationCenter": { displayName: "Notification center", description: "Recent notifications + DND", category: "Status", allowMultiple: false },
|
||||||
"systemUpdate": { displayName: "System update", description: "Indicates available system updates", category: "System", allowMultiple: false },
|
"systemUpdate": { displayName: "System update", description: "Indicates available system updates", category: "System", allowMultiple: false },
|
||||||
"systemStats": { displayName: "System stats", description: "CPU icon — hover for graphs, click to open btop", category: "System", allowMultiple: false },
|
"systemStats": { displayName: "System stats", description: "CPU icon — hover for graphs, click to open btop", category: "System", allowMultiple: false },
|
||||||
|
"tray": { displayName: "System tray", description: "Status notifier items", category: "Status", allowMultiple: false },
|
||||||
"weather": { displayName: "Weather", description: "Weather pill with detail popup", category: "Info", allowMultiple: false, settingsForm: "weatherSettings" },
|
"weather": { displayName: "Weather", description: "Weather pill with detail popup", category: "Info", allowMultiple: false, settingsForm: "weatherSettings" },
|
||||||
"microphone": { displayName: "Microphone", description: "Mic input state and mute toggle", category: "Audio", allowMultiple: false },
|
"microphone": { displayName: "Microphone", description: "Mic input state and mute toggle", category: "Audio", allowMultiple: false },
|
||||||
"activeWindow": { displayName: "Active window", description: "Title of the focused window", category: "Compositor", allowMultiple: false },
|
"activeWindow": { displayName: "Active window", description: "Title of the focused window", category: "Compositor", allowMultiple: false },
|
||||||
|
|||||||
@@ -56,12 +56,14 @@ Example `shell.json` (bar subtree only shown):
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `omarchy` | Omarchy menu launcher | left = menu · right = terminal |
|
| `omarchy` | Omarchy menu launcher | left = menu · right = terminal |
|
||||||
| `workspaces` | Hyprland workspace switcher | left = focus workspace |
|
| `workspaces` | Hyprland workspace switcher | left = focus workspace |
|
||||||
|
| `clock` | Date/time label | left = alternate format · right = timezone selector |
|
||||||
| `media` | MPRIS now-playing — scrolling track + artist, cover-art popup | left = play/pause · middle = next · scroll = prev/next · right = popup |
|
| `media` | MPRIS now-playing — scrolling track + artist, cover-art popup | left = play/pause · middle = next · scroll = prev/next · right = popup |
|
||||||
| `daytime` | Day/time label with date tooltip | hover = date |
|
| `daytime` | Day/time label with date tooltip | hover = date |
|
||||||
| `indicators` | Manual state indicators | left = indicator action |
|
| `indicators` | Manual state indicators | left = indicator action |
|
||||||
| `notificationCenter` | Bell with badge + popup with recent notifications, DND toggle | left = popup · right = toggle DND |
|
| `notificationCenter` | Bell with badge + popup with recent notifications, DND toggle | left = popup · right = toggle DND |
|
||||||
| `systemUpdate` | Available update indicator | left = update |
|
| `systemUpdate` | Available update indicator | left = update |
|
||||||
| `systemStats` | Inline CPU + memory sparklines, popup with detail | left = popup · right = terminal |
|
| `systemStats` | Inline CPU + memory sparklines, popup with detail | left = popup · right = terminal |
|
||||||
|
| `tray` | System tray | hover = reveal drawer · right on chevron = manage |
|
||||||
| `weather` | Weather icon + popup with forecast | left = popup · right = full notification |
|
| `weather` | Weather icon + popup with forecast | left = popup · right = full notification |
|
||||||
| `microphone` | Mic icon + scroll volume | left = mute toggle · middle = audio panel · scroll = source volume |
|
| `microphone` | Mic icon + scroll volume | left = mute toggle · middle = audio panel · scroll = source volume |
|
||||||
|
|
||||||
@@ -75,10 +77,6 @@ Example `shell.json` (bar subtree only shown):
|
|||||||
| `panels.bluetooth` | Bluetooth icon + popup with device list, connect/disconnect, battery | left = popup · right = toggle radio · middle = bluetoothctl TUI |
|
| `panels.bluetooth` | Bluetooth icon + popup with device list, connect/disconnect, battery | left = popup · right = toggle radio · middle = bluetoothctl TUI |
|
||||||
| `panels.monitor` | Brightness and laptop display controls | left = popup |
|
| `panels.monitor` | Brightness and laptop display controls | left = popup |
|
||||||
|
|
||||||
### Built-in base modules (in `Bar.qml`)
|
|
||||||
|
|
||||||
`clock`, `tray`.
|
|
||||||
|
|
||||||
The `indicators` widget loads individual bar indicators from `indicators/`, ordered by its `items` array in `shell.json`. Rich panels such as `powerPanel`, `networkPanel`, and `audioPanel` live in `../panels/` above.
|
The `indicators` widget loads individual bar indicators from `indicators/`, ordered by its `items` array in `shell.json`. Rich panels such as `powerPanel`, `networkPanel`, and `audioPanel` live in `../panels/` above.
|
||||||
|
|
||||||
## Orientation
|
## Orientation
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Commons
|
||||||
|
import qs.Ui
|
||||||
|
|
||||||
|
BarWidget {
|
||||||
|
id: root
|
||||||
|
moduleName: "clock"
|
||||||
|
|
||||||
|
property bool alt: false
|
||||||
|
|
||||||
|
function setting(name, fallback) {
|
||||||
|
var value = settings ? settings[name] : undefined
|
||||||
|
return value === undefined || value === null ? fallback : value
|
||||||
|
}
|
||||||
|
|
||||||
|
function label() {
|
||||||
|
if (alt) return Qt.formatDateTime(clock.date, String(setting("formatAlt", "dd MMMM 'W'ww yyyy")))
|
||||||
|
if (bar && bar.vertical) return Qt.formatDateTime(clock.date, String(setting("verticalFormat", "HH\n—\nmm")))
|
||||||
|
return Qt.formatDateTime(clock.date, String(setting("format", "dddd HH:mm")))
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: button.implicitWidth
|
||||||
|
implicitHeight: button.implicitHeight
|
||||||
|
|
||||||
|
SystemClock {
|
||||||
|
id: clock
|
||||||
|
precision: SystemClock.Minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetButton {
|
||||||
|
id: button
|
||||||
|
anchors.fill: parent
|
||||||
|
bar: root.bar
|
||||||
|
text: root.label()
|
||||||
|
horizontalMargin: 8.75
|
||||||
|
verticalPadding: 8.75
|
||||||
|
onPressed: function(button) {
|
||||||
|
if (!root.bar) return
|
||||||
|
if (button === Qt.RightButton) root.bar.run("omarchy-launch-floating-terminal-with-presentation omarchy-tz-select")
|
||||||
|
else root.alt = !root.alt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,448 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell.Services.SystemTray
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import qs.Commons
|
||||||
|
import qs.Ui
|
||||||
|
|
||||||
|
BarWidget {
|
||||||
|
id: root
|
||||||
|
moduleName: "tray"
|
||||||
|
|
||||||
|
property bool expanded: false
|
||||||
|
property bool managePopupOpen: false
|
||||||
|
readonly property bool vertical: bar ? bar.vertical : false
|
||||||
|
readonly property int barSize: bar ? bar.barSize : Style.bar.sizeHorizontal
|
||||||
|
readonly property color foreground: bar ? bar.foreground : Color.foreground
|
||||||
|
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
|
||||||
|
readonly property var pinnedIds: Array.isArray(settings.pinned) ? settings.pinned : []
|
||||||
|
readonly property var hiddenIds: Array.isArray(settings.hidden) ? settings.hidden : []
|
||||||
|
readonly property var pinnedItems: bucket("pinned")
|
||||||
|
readonly property var drawerItems: bucket("drawer")
|
||||||
|
readonly property var allItems: bucket("all")
|
||||||
|
readonly property int drawerCount: drawerItems.length
|
||||||
|
readonly property int drawerExtent: drawerCount > 0 ? drawerCount * 16 + (drawerCount - 1) * 17 : 0
|
||||||
|
// Match Waybar's group/tray-expander drawer transition-duration.
|
||||||
|
readonly property int animationDuration: 600
|
||||||
|
property real revealProgress: expanded ? 1 : 0
|
||||||
|
readonly property real revealExtent: drawerExtent * revealProgress
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
managePopupOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function trayIconSource(icon) {
|
||||||
|
var value = String(icon || "")
|
||||||
|
var marker = "?path="
|
||||||
|
var markerIndex = value.indexOf(marker)
|
||||||
|
if (markerIndex === -1) return value
|
||||||
|
|
||||||
|
var name = value.substring(0, markerIndex).split("/").pop()
|
||||||
|
var iconPath = value.substring(markerIndex + marker.length).split("&")[0]
|
||||||
|
return Util.fileUrl(iconPath + "/hicolor/16x16/status/" + name + ".png")
|
||||||
|
}
|
||||||
|
|
||||||
|
function trayTooltip(item) {
|
||||||
|
return item.tooltipTitle || item.title || item.id || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function classifyItem(item) {
|
||||||
|
var iid = String(item.id || "")
|
||||||
|
if (hiddenIds.indexOf(iid) !== -1) return "hidden"
|
||||||
|
if (pinnedIds.indexOf(iid) !== -1) return "pinned"
|
||||||
|
return "drawer"
|
||||||
|
}
|
||||||
|
|
||||||
|
function bucket(category) {
|
||||||
|
var values = SystemTray.items.values
|
||||||
|
var result = []
|
||||||
|
for (var i = 0; i < values.length; i++) {
|
||||||
|
var item = values[i]
|
||||||
|
if (item.status === Status.Passive) continue
|
||||||
|
if (category === "all") {
|
||||||
|
result.push(item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (classifyItem(item) === category) result.push(item)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistTrayState(pinned, hidden) {
|
||||||
|
if (!root.bar || !root.bar.shell || typeof root.bar.shell.updateEntryInline !== "function") return
|
||||||
|
root.bar.shell.updateEntryInline("tray", { id: "tray", pinned: pinned, hidden: hidden })
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePin(iid) {
|
||||||
|
var p = pinnedIds.slice(), h = hiddenIds.slice()
|
||||||
|
var idx = p.indexOf(iid)
|
||||||
|
if (idx !== -1) p.splice(idx, 1)
|
||||||
|
else {
|
||||||
|
p.push(iid)
|
||||||
|
var hi = h.indexOf(iid)
|
||||||
|
if (hi !== -1) h.splice(hi, 1)
|
||||||
|
}
|
||||||
|
persistTrayState(p, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleHide(iid) {
|
||||||
|
var p = pinnedIds.slice(), h = hiddenIds.slice()
|
||||||
|
var idx = h.indexOf(iid)
|
||||||
|
if (idx !== -1) h.splice(idx, 1)
|
||||||
|
else {
|
||||||
|
h.push(iid)
|
||||||
|
var pi = p.indexOf(iid)
|
||||||
|
if (pi !== -1) p.splice(pi, 1)
|
||||||
|
}
|
||||||
|
persistTrayState(p, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: pinnedItems.length > 0 || drawerCount > 0
|
||||||
|
clip: false
|
||||||
|
implicitWidth: root.vertical ? root.barSize : trayContent.implicitWidth
|
||||||
|
implicitHeight: root.vertical ? trayContent.implicitHeight : root.barSize
|
||||||
|
|
||||||
|
Behavior on revealProgress {
|
||||||
|
NumberAnimation { duration: root.animationDuration; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: trayContent
|
||||||
|
anchors.fill: parent
|
||||||
|
sourceComponent: root.vertical ? verticalTray : horizontalTray
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: horizontalTray
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: horizontalTrayRoot
|
||||||
|
|
||||||
|
readonly property int pinnedWidth: pinnedRow.implicitWidth
|
||||||
|
readonly property int drawerBlockWidth: root.allItems.length > 0 ? expandIcon.implicitWidth + root.drawerExtent : 0
|
||||||
|
|
||||||
|
implicitWidth: pinnedWidth + drawerBlockWidth
|
||||||
|
implicitHeight: root.barSize
|
||||||
|
|
||||||
|
// Mask out the empty area the collapsed drawer reserves for its slide-in,
|
||||||
|
// so hovering it doesn't trigger expand and clicks pass through.
|
||||||
|
containmentMask: QtObject {
|
||||||
|
function contains(point: point): bool {
|
||||||
|
if (point.y < 0 || point.y > horizontalTrayRoot.height) return false
|
||||||
|
// Drawer reveals leftward; chevron sits at the right end when collapsed
|
||||||
|
// and slides left as it opens. The visible region starts at the chevron.
|
||||||
|
var chevronX = root.drawerExtent - root.revealExtent
|
||||||
|
if (point.x >= chevronX && point.x <= horizontalTrayRoot.drawerBlockWidth) return true
|
||||||
|
// Pinned items, placed to the right of the drawer block.
|
||||||
|
var pinnedStart = horizontalTrayRoot.drawerBlockWidth
|
||||||
|
return point.x >= pinnedStart && point.x <= horizontalTrayRoot.implicitWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: drawerArea
|
||||||
|
x: 0
|
||||||
|
width: horizontalTrayRoot.drawerBlockWidth
|
||||||
|
height: root.barSize
|
||||||
|
visible: root.allItems.length > 0
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
onHoveredChanged: root.expanded = hovered
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetButton {
|
||||||
|
id: expandIcon
|
||||||
|
bar: root.bar
|
||||||
|
width: implicitWidth
|
||||||
|
height: implicitHeight
|
||||||
|
x: root.drawerExtent - root.revealExtent
|
||||||
|
text: "\uf053"
|
||||||
|
horizontalMargin: 9
|
||||||
|
verticalPadding: 6
|
||||||
|
onPressed: function(button) {
|
||||||
|
if (button === Qt.RightButton) root.managePopupOpen = !root.managePopupOpen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: trayClip
|
||||||
|
x: expandIcon.width
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: root.drawerExtent
|
||||||
|
height: root.barSize
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: trayIcons
|
||||||
|
x: root.drawerExtent - root.revealExtent
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Style.space(17)
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.drawerItems
|
||||||
|
TrayItem {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: pinnedRow
|
||||||
|
x: drawerArea.x + horizontalTrayRoot.drawerBlockWidth
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Style.space(17)
|
||||||
|
leftPadding: root.pinnedItems.length > 0 && root.allItems.length > 0 ? Style.space(6) : 0
|
||||||
|
Repeater {
|
||||||
|
model: root.pinnedItems
|
||||||
|
TrayItem {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: verticalTray
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: verticalTrayRoot
|
||||||
|
|
||||||
|
readonly property int pinnedHeight: pinnedCol.implicitHeight
|
||||||
|
readonly property int drawerBlockHeight: root.allItems.length > 0 ? expandIcon.implicitHeight + root.drawerExtent : 0
|
||||||
|
|
||||||
|
implicitWidth: root.barSize
|
||||||
|
implicitHeight: pinnedHeight + drawerBlockHeight
|
||||||
|
|
||||||
|
containmentMask: QtObject {
|
||||||
|
function contains(point: point): bool {
|
||||||
|
if (point.x < 0 || point.x > verticalTrayRoot.width) return false
|
||||||
|
var chevronY = root.drawerExtent - root.revealExtent
|
||||||
|
if (point.y >= chevronY && point.y <= verticalTrayRoot.drawerBlockHeight) return true
|
||||||
|
var pinnedStart = verticalTrayRoot.drawerBlockHeight
|
||||||
|
return point.y >= pinnedStart && point.y <= verticalTrayRoot.implicitHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: drawerArea
|
||||||
|
y: 0
|
||||||
|
width: root.barSize
|
||||||
|
height: verticalTrayRoot.drawerBlockHeight
|
||||||
|
visible: root.allItems.length > 0
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
onHoveredChanged: root.expanded = hovered
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetButton {
|
||||||
|
id: expandIcon
|
||||||
|
bar: root.bar
|
||||||
|
width: implicitWidth
|
||||||
|
height: implicitHeight
|
||||||
|
y: root.drawerExtent - root.revealExtent
|
||||||
|
text: "\uf053"
|
||||||
|
textRotation: 90
|
||||||
|
horizontalMargin: 9
|
||||||
|
verticalPadding: 6
|
||||||
|
onPressed: function(button) {
|
||||||
|
if (button === Qt.RightButton) root.managePopupOpen = !root.managePopupOpen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: trayClip
|
||||||
|
y: expandIcon.height
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
width: root.barSize
|
||||||
|
height: root.drawerExtent
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: trayIcons
|
||||||
|
y: root.drawerExtent - root.revealExtent
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Style.space(17)
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.drawerItems
|
||||||
|
TrayItem {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: pinnedCol
|
||||||
|
y: drawerArea.y + verticalTrayRoot.drawerBlockHeight
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Style.space(17)
|
||||||
|
topPadding: root.pinnedItems.length > 0 && root.allItems.length > 0 ? Style.space(6) : 0
|
||||||
|
Repeater {
|
||||||
|
model: root.pinnedItems
|
||||||
|
TrayItem {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupCard {
|
||||||
|
id: managePopup
|
||||||
|
anchorItem: root
|
||||||
|
owner: root
|
||||||
|
bar: root.bar
|
||||||
|
open: root.managePopupOpen
|
||||||
|
contentWidth: managePopup.fittedContentWidth(Style.space(300))
|
||||||
|
contentHeight: managePopup.fittedContentHeight(manageColumn.implicitHeight)
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: manageColumn
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Style.space(8)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Tray icons"
|
||||||
|
color: root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.body
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Pinned icons stay visible. Hidden icons never show."
|
||||||
|
color: Qt.darker(root.foreground, 1.4)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.caption
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
visible: root.allItems.length === 0
|
||||||
|
text: "No tray items reporting."
|
||||||
|
color: Qt.darker(root.foreground, 1.5)
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.bodySmall
|
||||||
|
font.italic: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.allItems
|
||||||
|
delegate: Item {
|
||||||
|
id: rowRoot
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
width: manageColumn.width
|
||||||
|
implicitHeight: 28
|
||||||
|
|
||||||
|
readonly property string itemId: String(modelData.id || "")
|
||||||
|
readonly property string displayName: {
|
||||||
|
var t = String(modelData.title || "").trim()
|
||||||
|
if (t) return t
|
||||||
|
var tt = String(modelData.tooltipTitle || "").trim()
|
||||||
|
if (tt) return tt
|
||||||
|
var id = String(modelData.id || "")
|
||||||
|
var slash = id.lastIndexOf("/")
|
||||||
|
return slash !== -1 ? id.substring(slash + 1) : (id || "Unknown")
|
||||||
|
}
|
||||||
|
readonly property bool isPinned: root.pinnedIds.indexOf(itemId) !== -1
|
||||||
|
readonly property bool isHidden: root.hiddenIds.indexOf(itemId) !== -1
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
id: rowIcon
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
implicitSize: 16
|
||||||
|
width: 16
|
||||||
|
height: 16
|
||||||
|
source: root.trayIconSource(rowRoot.modelData.icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: rowIcon.right
|
||||||
|
anchors.leftMargin: Style.space(10)
|
||||||
|
anchors.right: rowHideBtn.left
|
||||||
|
anchors.rightMargin: Style.space(8)
|
||||||
|
text: rowRoot.displayName
|
||||||
|
color: root.foreground
|
||||||
|
font.family: root.fontFamily
|
||||||
|
font.pixelSize: Style.font.bodySmall
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: rowPinBtn
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
iconText: "\uf08d"
|
||||||
|
text: rowRoot.isPinned ? "Unpin" : "Pin"
|
||||||
|
foreground: root.foreground
|
||||||
|
horizontalPadding: 8
|
||||||
|
verticalPadding: 3
|
||||||
|
iconSize: Style.font.bodySmall
|
||||||
|
fontSize: Style.font.bodySmall
|
||||||
|
onClicked: root.togglePin(rowRoot.itemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: rowHideBtn
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: rowPinBtn.left
|
||||||
|
anchors.rightMargin: Style.space(6)
|
||||||
|
iconText: "\uf06e"
|
||||||
|
text: rowRoot.isHidden ? "Show" : "Hide"
|
||||||
|
foreground: root.foreground
|
||||||
|
horizontalPadding: 8
|
||||||
|
verticalPadding: 3
|
||||||
|
iconSize: Style.font.bodySmall
|
||||||
|
fontSize: Style.font.bodySmall
|
||||||
|
onClicked: root.toggleHide(rowRoot.itemId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component TrayItem: Item {
|
||||||
|
id: trayItemRoot
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
visible: modelData.status !== Status.Passive
|
||||||
|
implicitWidth: visible ? 16 : 0
|
||||||
|
implicitHeight: visible ? 16 : 0
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
implicitSize: 12
|
||||||
|
width: 12
|
||||||
|
height: 12
|
||||||
|
source: root.trayIconSource(trayItemRoot.modelData.icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onEntered: if (root.bar) root.bar.showTooltip(trayItemRoot, root.trayTooltip(modelData))
|
||||||
|
onExited: if (root.bar) root.bar.hideTooltip(trayItemRoot)
|
||||||
|
onClicked: function(mouse) {
|
||||||
|
if (mouse.button === Qt.RightButton && trayItemRoot.modelData.hasMenu) {
|
||||||
|
var point = trayItemRoot.QsWindow.contentItem.mapFromItem(trayItemRoot, mouse.x, mouse.y)
|
||||||
|
trayItemRoot.modelData.display(trayItemRoot.QsWindow.window, point.x, point.y)
|
||||||
|
} else if (mouse.button === Qt.MiddleButton) {
|
||||||
|
trayItemRoot.modelData.secondaryActivate()
|
||||||
|
} else {
|
||||||
|
trayItemRoot.modelData.activate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onWheel: function(wheel) {
|
||||||
|
trayItemRoot.modelData.scroll(wheel.angleDelta.y, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool tooltipHovered: visible && opacity > 0 && mouseArea.containsMouse
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -290,11 +290,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- widget catalog -----------------------------------------
|
// ---------------- widget catalog -----------------------------------------
|
||||||
readonly property var builtinWidgetMeta: ({
|
|
||||||
"clock": { name: "Clock", description: "Date / time text", category: "Time" },
|
|
||||||
"tray": { name: "System tray", description: "Status notifier items", category: "Status" }
|
|
||||||
})
|
|
||||||
|
|
||||||
property int catalogRevision: 0
|
property int catalogRevision: 0
|
||||||
onBarWidgetRegistryChanged: {
|
onBarWidgetRegistryChanged: {
|
||||||
catalogRevision++
|
catalogRevision++
|
||||||
@@ -323,7 +318,6 @@ Item {
|
|||||||
var canonicalKey = canonicalWidgetId(key)
|
var canonicalKey = canonicalWidgetId(key)
|
||||||
if (root.barWidgetRegistry && root.barWidgetRegistry.has(canonicalKey))
|
if (root.barWidgetRegistry && root.barWidgetRegistry.has(canonicalKey))
|
||||||
return root.barWidgetRegistry.metadataFor(canonicalKey) || {}
|
return root.barWidgetRegistry.metadataFor(canonicalKey) || {}
|
||||||
if (builtinWidgetMeta[canonicalKey]) return builtinWidgetMeta[canonicalKey]
|
|
||||||
|
|
||||||
var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[key] : null
|
var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[key] : null
|
||||||
if (manifest) {
|
if (manifest) {
|
||||||
@@ -388,7 +382,6 @@ Item {
|
|||||||
ids[pid] = true
|
ids[pid] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var key in builtinWidgetMeta) ids[key] = true
|
|
||||||
return Object.keys(ids)
|
return Object.keys(ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +404,7 @@ Item {
|
|||||||
var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[id] : null
|
var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[id] : null
|
||||||
var manifestIsBarWidget = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar-widget") !== -1
|
var manifestIsBarWidget = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar-widget") !== -1
|
||||||
var isBarWidget = !!(meta && meta.source !== "plugin") || manifestIsBarWidget
|
var isBarWidget = !!(meta && meta.source !== "plugin") || manifestIsBarWidget
|
||||||
if (!isBarWidget && !builtinWidgetMeta[id]) continue
|
if (!isBarWidget) continue
|
||||||
|
|
||||||
var inSection = sectionArray(section)
|
var inSection = sectionArray(section)
|
||||||
var existsHere = false
|
var existsHere = false
|
||||||
|
|||||||
Reference in New Issue
Block a user