Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/Bar.qml

Raw
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland

PanelWindow {
    id: root

    required property Theme theme
    required property Niri niriService
    required property Time timeService
    required property Resources resourceService
    required property Notifications notificationService
    required property Tray trayService
    required property Media mediaService
    required property Clipboard clipboardService
    required property Vault vaultService
    required property Launcher launcherService
    required property Audio audioService
    required property Brightness brightnessService
    required property Network networkService
    required property Bluetooth bluetoothService
    required property Power powerService
    required property Privilege privilegeService
    required property Idle idleService
    required property Lock lockService
    required property Noko nokoService
    required property SurfaceManager surfaces
    required property Usage usageService
    required property CalDav caldavService
    required property Format formatService
    required property string orientation

    readonly property bool vertical: orientation === "vertical"

    function chipLabel(name) {
        switch (String(name)) {
        case "clock":
            return root.timeService ? root.timeService.compact : "--:--";
        case "launcher":
            return "launch";
        case "media":
            return "media";
        case "noko":
            return root.nokoService ? root.nokoService.label : "noko";
        case "system":
            return "system";
        case "clipboard":
            return "clip";
        case "vault":
            return "vault";
        case "audio":
            return root.audioService ? root.audioService.privacyLabel().toLowerCase() : "audio";
        case "brightness":
            return "bright";
        case "connectivity":
            return "network";
        case "power":
            return "power";
        case "notifications":
            return "notify";
        case "tray":
            return root.trayService && root.trayService.attentionCount > 0 ? `tray ${root.trayService.attentionCount}` : "tray";
        case "idle":
            return "idle";
        }
        return String(name);
    }

    // "something active inside" — drives a super subtle active dot on the chip
    function chipActive(name) {
        switch (String(name)) {
        case "media":
            return !!(root.mediaService && root.mediaService.playing);
        case "noko":
            return !!(root.nokoService && root.nokoService.active);
        case "notifications":
            return !!(root.notificationService && root.notificationService.unreadCount > 0);
        case "tray":
            return !!(root.trayService && root.trayService.attentionCount > 0);
        case "idle":
            return !!(root.idleService && root.idleService.inhibited);
        case "audio":
            return !!(root.audioService && root.audioService.privacyActive());
        case "vault":
            return !!(root.vaultService && root.vaultService.unlocked);
        }
        return false;
    }

    // idle (keep-awake) is important enough to pop louder than a subtle dot
    function chipActiveStrong(name) {
        const key = String(name);
        if (key === "noko")
            return !!(root.nokoService && root.nokoService.strong);
        return key === "idle" && root.chipActive(name);
    }

    function chipRole(name) {
        if (String(name) === "noko" && root.nokoService)
            return root.nokoService.role;
        return "ui.accent";
    }

    function itemRectInBar(item) {
        let x = 0;
        let y = 0;
        let cursor = item;
        while (cursor && cursor !== root.contentItem) {
            x += cursor.x;
            y += cursor.y;
            cursor = cursor.parent;
        }
        return {
            "x": x,
            "y": y,
            "width": item.width,
            "height": item.height
        };
    }

    function registerAnchor(name, item) {
        // Skip hidden items: the horizontal clock chip and the vertical clock
        // stack share the "clock" key, so only the visible one must register.
        if (!root.surfaces || !item || !item.visible || item.width <= 0 || item.height <= 0)
            return;

        const rect = itemRectInBar(item);
        root.surfaces.registerAnchor(name, `${Math.round(rect.x)},${Math.round(rect.y)},${Math.round(rect.width)},${Math.round(rect.height)}`);
    }

    function activatePopout(name) {
        if (!root.surfaces)
            return;

        const target = String(name);
        if (root.surfaces.popoutOpen && root.surfaces.popoutName === target) {
            root.surfaces.closePopout("bar-toggle");
        } else {
            if (root.usageService)
                root.usageService.record(target);
            if (target === "vault" && root.vaultService)
                root.vaultService.prepareOpen(root.niriService ? root.niriService.focusedAppId : "");

            root.surfaces.openRegisteredPopout(target);
        }
    }

    function refreshSurfaceGeometry() {
        if (root.surfaces) {
            const thickness = Math.round(root.vertical ? root.width : root.height);
            root.surfaces.setPopoutScreen(Math.round(root.screen ? root.screen.width : root.width), Math.round(root.screen ? root.screen.height : root.height), thickness, root.orientation);
        }
    }

    // horizontal: width spans (left+right anchored), implicitHeight sets bar
    // height. vertical: height spans (top+bottom anchored), implicitWidth sets
    // bar thickness from the widest chip content (never a magic constant).
    implicitHeight: 34
    implicitWidth: root.vertical ? (Math.max(34, widgetsRow.implicitWidth, clockStack.width, workspaceStrip.implicitWidth) + 12) : root.width
    color: "transparent"
    WlrLayershell.namespace: "nuguland-bar"
    WlrLayershell.layer: WlrLayer.Top
    WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
    Component.onCompleted: Qt.callLater(root.refreshSurfaceGeometry)
    onSurfacesChanged: Qt.callLater(root.refreshSurfaceGeometry)
    onWidthChanged: root.refreshSurfaceGeometry()
    onHeightChanged: root.refreshSurfaceGeometry()

    anchors {
        top: true
        left: true
        right: !root.vertical
        bottom: root.vertical
    }

    Rectangle {
        id: barContent

        anchors.fill: parent
        color: "transparent"

        // accent edge: bottom hairline (horizontal) / right hairline (vertical)
        Rectangle {
            x: root.vertical ? parent.width - 1 : 0
            y: root.vertical ? 0 : parent.height - 1
            width: root.vertical ? 1 : parent.width
            height: root.vertical ? parent.height : 1
            color: root.theme ? root.theme.withAlpha(root.theme.ui.accent, 0.55) : "transparent"
        }

        // Horizontal clock stays at the right edge; widgets sit immediately left.
        // The vertical clock retains its existing centred placement.
        NuguClock {
            id: clockChip

            stacked: false
            visible: !root.vertical
            anchors.right: parent.right
            anchors.rightMargin: 10
            anchors.verticalCenter: parent.verticalCenter
        }

        NuguClock {
            id: clockStack

            stacked: true
            visible: root.vertical
            anchors.horizontalCenter: parent.horizontalCenter
            y: Math.min(parent.height / 2 - height / 2, widgetsRow.y - height - 12)
        }

        // Workspaces fill the space before widgets horizontally, or above the
        // clock vertically. Each output keeps its own faint tray.
        GridLayout {
            id: workspaceStrip

            // horizontal base: single row from the left edge up to the widgets,
            // centred vertically. Vertical state swaps to a single column from
            // the top down to the clock, centred horizontally.
            columns: root.vertical ? 1 : 99
            rowSpacing: 8
            columnSpacing: 8
            clip: true
            anchors.left: parent.left
            anchors.leftMargin: root.vertical ? 0 : 10
            anchors.right: widgetsRow.left
            anchors.rightMargin: root.vertical ? 0 : 12
            anchors.verticalCenter: parent.verticalCenter
            anchors.topMargin: root.vertical ? 10 : 0
            anchors.bottomMargin: root.vertical ? 10 : 0

            states: State {
                when: root.vertical

                AnchorChanges {
                    target: workspaceStrip
                    anchors.left: undefined
                    anchors.right: undefined
                    anchors.verticalCenter: undefined
                    anchors.top: parent.top
                    anchors.bottom: clockStack.top
                    anchors.horizontalCenter: parent.horizontalCenter
                }
            }

            Repeater {
                model: root.niriService ? root.niriService.workspaceGroups() : []

                // Wrap the group in a plain Item: Qt6 won't inject an array
                // model's modelData into a required prop on a delegate root that
                // re-declares it for a nested Repeater. The wrapper takes the
                // injection and hands it to the tray/pills explicitly.
                delegate: Item {
                    id: groupItem

                    required property var modelData

                    implicitWidth: tray.width
                    implicitHeight: tray.height
                    Layout.preferredWidth: tray.width
                    Layout.preferredHeight: tray.height
                    // pinned to the strip's start edge: left in the horizontal
                    // row, top in the vertical column (no centred-float gap).
                    Layout.alignment: root.vertical ? (Qt.AlignHCenter | Qt.AlignTop) : (Qt.AlignLeft | Qt.AlignVCenter)

                    Rectangle {
                        id: tray

                        anchors.centerIn: parent
                        width: pills.implicitWidth + 12
                        height: pills.implicitHeight + 12
                        radius: 11
                        color: root.theme ? root.theme.withAlpha(root.theme.ui.minor, 0.07) : "transparent"

                        // pills: one output's workspaces. Flow follows the bar —
                        // a horizontal row, or a vertical stack in vertical mode.
                        GridLayout {
                            id: pills

                            anchors.centerIn: parent
                            columns: root.vertical ? 1 : 99
                            rowSpacing: 4
                            columnSpacing: 4

                            Repeater {
                                model: groupItem.modelData.workspaces

                                delegate: Item {
                                    id: pillWrap

                                    required property var modelData

                                    implicitWidth: workspaceChip.width
                                    implicitHeight: workspaceChip.height
                                    Layout.preferredWidth: workspaceChip.width
                                    Layout.preferredHeight: workspaceChip.height
                                    Layout.alignment: Qt.AlignCenter

                                    Rectangle {
                                        id: workspaceChip

                                        readonly property bool active: !!pillWrap.modelData.is_focused
                                        readonly property string idxStr: String(pillWrap.modelData.shortcut || pillWrap.modelData.idx || pillWrap.modelData.id || "?")
                                        readonly property string nameStr: pillWrap.modelData.name ? String(pillWrap.modelData.name).toLowerCase() : ""
                                        readonly property string abbrName: root.formatService && workspaceChip.nameStr ? root.formatService.abbreviate(workspaceChip.nameStr) : ""

                                        anchors.centerIn: parent
                                        width: Math.max(30, workspaceContent.implicitWidth + (root.vertical ? 12 : 16))
                                        height: root.vertical ? (workspaceContent.implicitHeight + 8) : 22
                                        radius: 8
                                        color: root.theme ? (active ? root.theme.withAlpha(root.theme.ui.focus, 0.28) : root.theme.withAlpha(root.theme.ui.minor, 0.1)) : "transparent"
                                        border.width: 0

                                        // idx (minor) leads the name (normal): inline in horizontal, stacked in vertical.
                                        GridLayout {
                                            id: workspaceContent

                                            anchors.centerIn: parent
                                            columns: root.vertical ? 1 : 2
                                            rowSpacing: 0
                                            columnSpacing: 4

                                            NuguText {
                                                Layout.alignment: Qt.AlignHCenter
                                                theme: root.theme
                                                variant: "meta"
                                                text: workspaceChip.idxStr
                                                color: !root.theme ? "transparent" : (workspaceChip.active ? root.theme.ui.accent : root.theme.ui.minor)
                                            }

                                            NuguText {
                                                Layout.alignment: Qt.AlignHCenter
                                                visible: text.length > 0
                                                theme: root.theme
                                                variant: "meta"
                                                font.pixelSize: root.vertical ? 10 : 13
                                                text: root.vertical ? workspaceChip.abbrName : workspaceChip.nameStr
                                                color: !root.theme ? "transparent" : (workspaceChip.active ? root.theme.ui.accent : root.theme.ui.normal)
                                            }
                                        }

                                        MouseArea {
                                            anchors.fill: parent
                                            hoverEnabled: true
                                            cursorShape: Qt.PointingHandCursor
                                            onClicked: {
                                                if (root.niriService)
                                                    root.niriService.focusWorkspace(pillWrap.modelData.name || pillWrap.modelData.idx || pillWrap.modelData.id);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        // Widgets keep their natural width before the clock horizontally,
        // or at the bottom edge vertically.
        GridLayout {
            id: widgetsRow

            // horizontal base: single row pinned before the clock, centred
            // vertically. The vertical state swaps to a single column pinned to
            // the bottom edge, centred horizontally. AnchorChanges is required
            // because clearing an anchor with `undefined` does not reset it.
            columns: root.vertical ? 1 : 99
            rowSpacing: 4
            columnSpacing: 4
            anchors.right: clockChip.left
            anchors.rightMargin: root.vertical ? 0 : 12
            anchors.verticalCenter: parent.verticalCenter
            anchors.bottomMargin: root.vertical ? 10 : 0

            states: State {
                when: root.vertical

                AnchorChanges {
                    target: widgetsRow
                    anchors.right: undefined
                    anchors.verticalCenter: undefined
                    anchors.bottom: parent.bottom
                    anchors.horizontalCenter: parent.horizontalCenter
                }
            }

            Repeater {
                model: root.usageService ? root.usageService.layoutOrder : []

                delegate: BarChip {
                    required property var modelData

                    popoutName: modelData
                    label: root.vertical ? root.formatService.abbreviate(root.chipLabel(modelData)) : root.chipLabel(modelData)
                }
            }
        }
    }

    PopoutHost {
        theme: root.theme
        surfaces: root.surfaces
        niriService: root.niriService
        timeService: root.timeService
        caldavService: root.caldavService
        resourceService: root.resourceService
        notificationService: root.notificationService
        trayService: root.trayService
        mediaService: root.mediaService
        clipboardService: root.clipboardService
        vaultService: root.vaultService
        launcherService: root.launcherService
        audioService: root.audioService
        brightnessService: root.brightnessService
        networkService: root.networkService
        bluetoothService: root.bluetoothService
        powerService: root.powerService
        privilegeService: root.privilegeService
        lockService: root.lockService
        nokoService: root.nokoService
        barWindow: root
    }

    component NuguText: Text {
        required property Theme theme
        property string variant: "body"
        property bool selected: false

        color: !theme ? "transparent" : (selected ? theme.content.backdrop : (variant === "title" ? theme.ui.important_global : (variant === "meta" ? theme.ui.normal : theme.content.normal)))
        font.family: variant === "mono" ? "IBM Plex Mono" : "IBM Plex Sans"
        font.pixelSize: variant === "title" ? 15 : (variant === "meta" ? 13 : 14)
        font.bold: variant === "title"
        elide: Text.ElideRight
    }

    component OdometerText: Item {
        id: odometer

        required property Theme theme
        property string text: "--:--"
        property bool selected: false
        property int digitWidth: 10
        property int digitHeight: 18
        property int separatorWidth: 5
        property string fontFamily: "IBM Plex Mono"
        property int pixelSize: 13
        readonly property string source: "Nuguland Clock.qml ClockNumber.qml"
        readonly property string motion: "vertical-digit-roll"
        readonly property bool noGlow: true
        readonly property bool settingsBacked: false
        readonly property var hardSeparators: [":"]
        readonly property var cells: makeCells(text)

        function makeCells(value) {
            const result = [];
            const chars = String(value).split("");
            for (let i = 0; i < chars.length; i++) {
                const ch = chars[i];
                const digit = ch >= "0" && ch <= "9";
                result.push(digit ? {
                    "kind": "digit",
                    "value": Number(ch)
                } : {
                    "kind": "separator",
                    "value": ch
                });
            }
            return result;
        }

        function colorForCell(cell) {
            if (!theme)
                return "white";

            if (odometer.selected)
                return theme.content.backdrop;

            return cell.kind === "separator" ? theme.ui.accent : theme.content.normal;
        }

        implicitWidth: row.implicitWidth
        implicitHeight: digitHeight
        clip: true

        Row {
            id: row

            anchors.centerIn: parent
            spacing: 0

            Repeater {
                model: odometer.cells

                delegate: Item {
                    id: cellRoot

                    required property var modelData

                    width: modelData.kind === "digit" ? odometer.digitWidth : odometer.separatorWidth
                    height: odometer.digitHeight
                    clip: true

                    Column {
                        id: digitColumn

                        visible: cellRoot.modelData.kind === "digit"
                        y: cellRoot.modelData.kind === "digit" ? -cellRoot.modelData.value * odometer.digitHeight : 0

                        Repeater {
                            model: 10

                            delegate: Text {
                                required property int index

                                width: odometer.digitWidth
                                height: odometer.digitHeight
                                horizontalAlignment: Text.AlignHCenter
                                verticalAlignment: Text.AlignVCenter
                                text: String(index)
                                font.family: odometer.fontFamily
                                font.pixelSize: odometer.pixelSize
                                color: odometer.colorForCell(cellRoot.modelData)
                            }
                        }

                        Behavior on y {
                            NumberAnimation {
                                duration: odometer.theme && odometer.theme.lowPowerMode ? 0 : 220
                                easing.type: Easing.OutCubic
                            }
                        }
                    }

                    Text {
                        visible: cellRoot.modelData.kind === "separator"
                        anchors.centerIn: parent
                        text: String(cellRoot.modelData.value)
                        font.family: odometer.fontFamily
                        font.pixelSize: odometer.pixelSize
                        color: odometer.colorForCell(cellRoot.modelData)
                    }
                }
            }
        }
    }

    component StateLayer: MouseArea {
        id: layer

        property Theme theme
        property bool checked: false
        property bool disabled: false
        property bool shapeMorph: true
        property real hoverOpacity: 0.08
        property real pressOpacity: 0.14
        property real disabledOpacity: 0.38
        property point pressOrigin: Qt.point(width / 2, height / 2)
        property real rippleRadius: 0
        property real cornerRadius: 0
        property real endRadius: Math.sqrt(width * width + height * height) * (shapeMorph ? 1.25 : 1)
        property string sourceMechanic: "Nuguland StateLayer.qml ButtonBase.qml"

        signal activated

        function stateLayerContract() {
            return {
                "clickablePrimitive": "NuguStateLayer",
                "source": sourceMechanic,
                "hoverOpacity": hoverOpacity,
                "pressOpacity": pressOpacity,
                "disabledOpacity": disabledOpacity,
                "rippleRadius": "radial-press-feedback",
                "pressOrigin": "pointer-local-origin",
                "shapeMorph": shapeMorph,
                "keyboardActivation": ["Return", "Space"],
                "roles": {
                    "checked": "ui.focus",
                    "hover": "ui.accent",
                    "disabled": "ui.minor"
                }
            };
        }

        function beginPress(x, y) {
            pressOrigin = Qt.point(x, y);
            rippleRadius = 0;
            rippleAnimation.restart();
        }

        function keyboardActivate() {
            beginPress(width / 2, height / 2);
            activated();
        }

        anchors.fill: parent
        clip: true
        enabled: !disabled
        hoverEnabled: true
        acceptedButtons: Qt.LeftButton
        cursorShape: disabled ? Qt.ArrowCursor : Qt.PointingHandCursor
        activeFocusOnTab: !disabled
        onPressed: event => {
            return beginPress(event.x, event.y);
        }
        onClicked: activated()
        Keys.onReturnPressed: event => {
            event.accepted = true;
            keyboardActivate();
        }
        Keys.onSpacePressed: event => {
            event.accepted = true;
            keyboardActivate();
        }

        Rectangle {
            anchors.fill: parent
            radius: layer.cornerRadius
            color: layer.theme ? layer.theme.ui.accent : "white"
            opacity: layer.disabled ? layer.disabledOpacity : layer.pressed ? layer.pressOpacity : layer.containsMouse || layer.checked ? layer.hoverOpacity : 0

            Behavior on opacity {
                NumberAnimation {
                    duration: layer.theme && layer.theme.lowPowerMode ? 0 : 120
                    easing.type: Easing.OutCubic
                }
            }
        }

        Rectangle {
            width: layer.rippleRadius * 2
            height: width
            x: layer.pressOrigin.x - width / 2
            y: layer.pressOrigin.y - height / 2
            radius: width / 2
            color: layer.theme ? layer.theme.ui.accent : "white"
            opacity: layer.pressed ? layer.pressOpacity : 0

            Behavior on opacity {
                NumberAnimation {
                    duration: layer.theme && layer.theme.lowPowerMode ? 0 : 160
                    easing.type: Easing.OutCubic
                }
            }
        }

        NumberAnimation {
            id: rippleAnimation

            target: layer
            property: "rippleRadius"
            to: layer.endRadius
            duration: layer.theme && layer.theme.lowPowerMode ? 0 : 220
            easing.type: Easing.OutCubic
        }
    }

    // Shared clock renderer. `stacked` lays HH / rule / MM in a column (vertical
    // bar); otherwise HH | rule | MM in a row (horizontal bar). The accent rule
    // is a Rectangle, so it never depends on a font glyph.
    component NuguClock: Rectangle {
        id: clk

        property bool stacked: false
        readonly property bool selected: root.surfaces && root.surfaces.popoutName === "clock"
        readonly property var clockParts: String(root.chipLabel("clock")).split(":")
        readonly property string hh: clk.clockParts[0] || "--"
        readonly property string mm: clk.clockParts[1] || "--"

        implicitWidth: clockContent.implicitWidth + (stacked ? 12 : 16)
        implicitHeight: clockContent.implicitHeight + (stacked ? 12 : 8)
        width: implicitWidth
        height: implicitHeight
        radius: 9
        color: !root.theme ? "transparent" : (selected ? root.theme.ui.focus : root.theme.withAlpha(root.theme.ui.minor, 0.1))
        clip: true
        Component.onCompleted: Qt.callLater(() => root.registerAnchor("clock", clk))
        onXChanged: root.registerAnchor("clock", clk)
        onYChanged: root.registerAnchor("clock", clk)
        onWidthChanged: root.registerAnchor("clock", clk)
        onHeightChanged: root.registerAnchor("clock", clk)

        GridLayout {
            id: clockContent

            anchors.centerIn: parent
            columns: clk.stacked ? 1 : 3
            rowSpacing: clk.stacked ? 3 : 0
            columnSpacing: 6

            NuguText {
                Layout.alignment: Qt.AlignCenter
                theme: root.theme
                variant: "meta"
                text: clk.hh
                color: !root.theme ? "transparent" : (clk.selected ? root.theme.content.backdrop : root.theme.content.normal)
            }

            Rectangle {
                Layout.alignment: Qt.AlignCenter
                Layout.preferredWidth: clk.stacked ? 16 : 2
                Layout.preferredHeight: clk.stacked ? 2 : 14
                radius: 1
                color: root.theme ? root.theme.ui.accent : "transparent"
            }

            NuguText {
                Layout.alignment: Qt.AlignCenter
                theme: root.theme
                variant: "meta"
                text: clk.mm
                color: !root.theme ? "transparent" : (clk.selected ? root.theme.content.backdrop : root.theme.content.normal)
            }
        }

        StateLayer {
            theme: root.theme
            cornerRadius: clk.radius
            checked: clk.selected
            onActivated: {
                if (root.surfaces)
                    root.activatePopout("clock");
            }
        }
    }

    component BarChip: Rectangle {
        id: chip

        required property string popoutName
        required property string label
        property int preferredWidth: Math.max(52, labelText.implicitWidth + 18)
        readonly property bool selected: root.surfaces && root.surfaces.popoutName === popoutName
        readonly property bool active: root.chipActive(popoutName)
        readonly property bool activeStrong: root.chipActiveStrong(popoutName)
        readonly property bool nokoCapture: popoutName === "noko" && root.nokoService && (root.nokoService.listening || root.nokoService.transcribing)
        readonly property color roleColor: !root.theme ? "transparent" : (root.chipRole(popoutName) === "warning" ? root.theme.warning : root.theme.ui.accent)

        Layout.preferredWidth: preferredWidth
        Layout.preferredHeight: 26
        Layout.alignment: Qt.AlignVCenter
        radius: 9
        color: !root.theme ? "transparent" : (selected ? root.theme.ui.focus : (activeStrong && !selected ? root.theme.withAlpha(chip.roleColor, 0.3) : root.theme.withAlpha(root.theme.ui.minor, 0.1)))
        clip: true
        border.width: chip.activeStrong && !chip.selected ? 1 : 0
        border.color: root.theme && chip.activeStrong ? chip.roleColor : "transparent"
        Component.onCompleted: Qt.callLater(() => {
            return root.registerAnchor(chip.popoutName, chip);
        })
        onXChanged: root.registerAnchor(chip.popoutName, chip)
        onYChanged: root.registerAnchor(chip.popoutName, chip)
        onWidthChanged: root.registerAnchor(chip.popoutName, chip)
        onHeightChanged: root.registerAnchor(chip.popoutName, chip)

        NuguText {
            id: labelText

            anchors.centerIn: parent
            width: Math.max(0, parent.width - 14)
            theme: root.theme
            variant: "meta"
            selected: chip.selected
            text: chip.label
            font.bold: chip.activeStrong && !chip.selected
            horizontalAlignment: Text.AlignHCenter
            elide: Text.ElideRight
            color: !root.theme ? "transparent" : (chip.selected ? root.theme.content.backdrop : (chip.activeStrong ? chip.roleColor : root.theme.content.normal))
            z: 2
        }

        Rectangle {
            id: activeDot

            visible: chip.active && !chip.activeStrong && !chip.selected && !chip.nokoCapture
            width: 5
            height: 5
            radius: 2.5
            anchors.top: parent.top
            anchors.right: parent.right
            anchors.topMargin: 4
            anchors.rightMargin: 5
            color: root.theme ? chip.roleColor : "white"
            opacity: 0.9
            z: 4

            Behavior on opacity {
                NumberAnimation {
                    duration: root.theme && root.theme.lowPowerMode ? 0 : 160
                    easing.type: Easing.OutCubic
                }
            }
        }

        Rectangle {
            visible: chip.nokoCapture
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 3
            width: Math.max(16, parent.width * 0.42)
            height: 2
            radius: 1
            color: chip.roleColor
            opacity: 0.65
            z: 4

            SequentialAnimation on opacity {
                running: chip.nokoCapture && !(root.theme && root.theme.lowPowerMode)
                loops: Animation.Infinite
                NumberAnimation {
                    to: 0.25
                    duration: 520
                    easing.type: Easing.InOutSine
                }
                NumberAnimation {
                    to: 0.78
                    duration: 520
                    easing.type: Easing.InOutSine
                }
            }
        }

        StateLayer {
            theme: root.theme
            cornerRadius: chip.radius
            checked: chip.selected
            z: 3
            onActivated: {
                if (chip.popoutName === "idle") {
                    if (root.usageService)
                        root.usageService.record("idle");

                    if (root.idleService)
                        root.idleService.toggle();

                    return;
                }
                if (root.surfaces)
                    root.activatePopout(chip.popoutName);
            }
        }
    }
}