Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/TrayPanel.qml

Raw
pragma 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
                }
            }
        }
    }
}