repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/TrayPanel.qml
Rawpragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import "Util.js" as Util
Flickable {
id: root
required property Theme theme
required property Tray trayService
readonly property var panelState: trayService ? Util.parseJson(trayService.stateJson(), {}) : ({})
readonly property var items: panelState.items || []
readonly property bool hasItems: items.length > 0
function primary(id, window, x, y) {
if (trayService)
trayService.primaryActivate(id, window, x, y);
}
function menu(id, window, x, y) {
if (trayService)
trayService.displayMenu(id, window, x, y);
}
function secondary(id) {
if (trayService)
trayService.secondaryActivate(id);
}
width: parent ? parent.width : 0
contentWidth: width
contentHeight: trayCol.height
clip: true
interactive: contentHeight > height
boundsBehavior: Flickable.StopAtBounds
flickableDirection: Flickable.VerticalFlick
StableListModel {
id: itemModel
rows: root.items
}
Column {
id: trayCol
objectName: "trayItems"
width: root.width
spacing: 6
Repeater {
model: itemModel
delegate: PopupCard {
id: itemCell
required property var rowData
readonly property bool needsAttention: rowData.status === "NeedsAttention"
readonly property string title: rowData.title || rowData.tooltipTitle || rowData.id
readonly property string tooltipTitle: rowData.tooltipTitle || ""
readonly property string description: rowData.tooltipDescription || ""
readonly property string statusText: String(rowData.status || "passive").replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase()
readonly property string categoryText: String(rowData.category || "application status").replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase()
readonly property string menuText: rowData.onlyMenu ? "menu only" : (rowData.hasMenu ? "menu available" : "")
readonly property string metadata: [tooltipTitle !== title ? tooltipTitle : "", description, categoryText, statusText, menuText].filter(value => value.length > 0).join("\n")
function openMenu(x, y) {
if (nativeMenu.menu) {
nativeMenu.anchor.rect = Qt.rect(Math.round(x), Math.round(y), 1, 1);
nativeMenu.open();
} else {
const point = itemCell.mapToItem(null, x, y);
root.menu(rowData.id, itemCell.QsWindow.window, point.x, point.y);
}
}
function activate() {
if (rowData.onlyMenu) {
openMenu(width / 2, height / 2);
} else {
const point = itemCell.mapToItem(null, width / 2, height / 2);
root.primary(rowData.id, itemCell.QsWindow.window, point.x, point.y);
}
}
theme: root.theme
padding: 0
attention: needsAttention
interactive: true
accessibleName: `${rowData.onlyMenu ? "open menu for" : "activate"} ${title}`
Accessible.description: metadata
tooltip: `${title}\n${metadata}`
width: root.width
height: 60
onControlFocusedChanged: if (controlFocused)
Util.revealIn(this, root)
onClicked: activate()
QsMenuAnchor {
id: nativeMenu
menu: root.trayService ? root.trayService.menuFor(itemCell.rowData.id) : null
anchor.item: itemCell
}
Image {
id: itemIcon
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
width: 26
height: 26
sourceSize.width: 26
sourceSize.height: 26
fillMode: Image.PreserveAspectFit
source: itemCell.rowData.icon || ""
}
Column {
anchors.left: itemIcon.right
anchors.leftMargin: 12
anchors.right: actionRail.left
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
spacing: 3
NuguText {
width: parent.width
theme: root.theme
text: itemCell.title
elide: Text.ElideRight
}
NuguText {
width: parent.width
theme: root.theme
variant: "meta"
selected: itemCell.needsAttention
text: itemCell.needsAttention ? `needs attention${itemCell.description ? " · " + itemCell.description : ""}` : (itemCell.description || itemCell.statusText)
elide: Text.ElideRight
}
}
Row {
id: actionRail
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
spacing: 6
PopupButton {
theme: root.theme
width: 34
icon: "\uF0E7"
accessibleName: `secondary action for ${itemCell.title}`
tooltip: `secondary action for ${itemCell.title} (middle click)`
onControlFocusedChanged: if (controlFocused)
Util.revealIn(this, root)
onClicked: root.secondary(itemCell.rowData.id)
}
PopupButton {
theme: root.theme
width: 34
icon: "\uF142"
enabled: itemCell.rowData.hasMenu
accessibleName: `menu for ${itemCell.title}`
tooltip: enabled ? `menu for ${itemCell.title} (right click)` : "no menu available"
onControlFocusedChanged: if (controlFocused)
Util.revealIn(this, root)
onClicked: itemCell.openMenu(itemCell.width - 12, itemCell.height)
}
}
// Primary pointer and keyboard activation share PopupCard.
// Keep only the documented additional mouse buttons here.
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.MiddleButton | Qt.RightButton
onClicked: event => {
if (event.button === Qt.RightButton)
itemCell.openMenu(event.x, event.y);
else
root.secondary(itemCell.rowData.id);
}
}
}
}
Item {
objectName: "trayEmptyState"
visible: !root.hasItems
width: parent.width
height: 56
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
font.family: "Symbols Nerd Font Mono"
font.pixelSize: 26
color: root.theme.ui.minor
text: "\uF01C"
Accessible.ignored: true
}
Column {
anchors.left: parent.left
anchors.leftMargin: 40
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 3
NuguText {
theme: root.theme
variant: "title"
text: "no tray items"
}
NuguText {
width: parent.width
theme: root.theme
variant: "meta"
text: "apps with tray controls appear here"
color: root.theme.ui.minor
wrapMode: Text.WordWrap
}
}
}
}
}