Selected file

BottomBar.qml

.config/quickshell/BottomBar.qml

import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.Pipewire
import QtQuick
import "lib/Audio.js" as Audio

PanelWindow {
    id: bar

    property bool shown: true
    property bool recording: false
    property bool quickSettingsOpen: false
    property string clockText: ""
    property string activeSpecialWorkspace: ""
    property var specialWorkspaces: []
    readonly property int barHeight: 34
    readonly property int contentGap: 4

    signal recordingToggleRequested()
    signal quickSettingsToggleRequested()
    signal specialWorkspaceToggleRequested(string name)

    function workspaceFor(id) {
        const workspaces = Hyprland.workspaces.values;

        for (let i = 0; i < workspaces.length; i++) {
            if (workspaces[i].id === id) {
                return workspaces[i];
            }
        }

        return null;
    }

    function workspaceExists(id) {
        return bar.workspaceFor(id) !== null;
    }

    function workspaceActive(id) {
        return Hyprland.focusedWorkspace !== null && Hyprland.focusedWorkspace.id === id;
    }

    function workspaceUrgent(id) {
        const workspace = bar.workspaceFor(id);
        return workspace !== null && workspace.urgent;
    }

    function specialWorkspaceFor(name) {
        const workspaces = Hyprland.workspaces.values;
        const workspaceName = "special:" + name;

        for (let i = 0; i < workspaces.length; i++) {
            if (workspaces[i].name === workspaceName) {
                return workspaces[i];
            }
        }

        return null;
    }

    function specialWorkspaceExists(name) {
        return bar.specialWorkspaceFor(name) !== null;
    }

    function specialWorkspaceVisible(name) {
        return bar.activeSpecialWorkspace === "special:" + name;
    }

    anchors {
        bottom: true
        left: true
        right: true
    }

    // Collapse the surface so a hidden bar does not reserve screen space.
    implicitHeight: bar.shown ? bar.barHeight : 0
    // Keep a small visual separation above the bottom bar while it is visible.
    exclusiveZone: bar.implicitHeight + (bar.shown ? bar.contentGap : 0)
    color: "transparent"
    aboveWindows: true

    Behavior on implicitHeight {
        NumberAnimation {
            duration: 180
            easing.type: Easing.OutCubic
        }
    }

    // Clip the fixed-height content while it moves below the collapsing surface.
    Item {
        id: viewport

        anchors.fill: parent
        clip: true

        Item {
            id: content

            width: parent.width
            height: bar.barHeight
            y: bar.shown ? 0 : height

            Behavior on y {
                NumberAnimation {
                    duration: 180
                    easing.type: Easing.OutCubic
                }
            }
        }
    }

    Rectangle {
        parent: content
        z: -1
        anchors.fill: parent
        color: Colors.bar
    }

    Row {
        parent: content
        anchors {
            right: parent.right
            rightMargin: 8
            verticalCenter: parent.verticalCenter
        }

        height: parent.height
        spacing: 2

        Repeater {
            model: 9

            Rectangle {
                width: 36
                height: bar.barHeight
                color: "transparent"

                Text {
                    anchors.centerIn: parent
                    text: modelData + 1
                    color: Colors.foreground
                    opacity: bar.workspaceActive(modelData + 1) ? 1.0 : bar.workspaceExists(modelData + 1) ? 0.82 : 0.42
                    font.family: "Syne, MesloLGS Nerd Font, monospace"
                    font.pixelSize: 20
                    font.bold: bar.workspaceActive(modelData + 1)
                }

                Rectangle {
                    anchors {
                        left: parent.left
                        right: parent.right
                        bottom: parent.bottom
                    }

                    height: 2
                    color: bar.workspaceUrgent(modelData + 1) ? Colors.danger : bar.workspaceActive(modelData + 1) ? Colors.foreground : Colors.faint
                    visible: bar.workspaceActive(modelData + 1) || bar.workspaceExists(modelData + 1) || bar.workspaceUrgent(modelData + 1)
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: Hyprland.dispatch("workspace " + (modelData + 1))
                }
            }
        }

        Text {
            anchors.verticalCenter: parent.verticalCenter
            text: "●"
            color: bar.recording ? Colors.danger : "transparent"
            font.family: "Syne, MesloLGS Nerd Font, monospace"
            font.pixelSize: 17
            font.bold: true

            MouseArea {
                anchors.fill: parent
                onClicked: bar.recordingToggleRequested()
            }
        }
    }

    Column {
        parent: content
        anchors.centerIn: parent

        Text {
            anchors.horizontalCenter: parent.horizontalCenter
            text: bar.clockText
            color: Colors.foreground
            font.family: "Syne, MesloLGS Nerd Font, monospace"
            font.pixelSize: 15
            font.bold: true
        }
    }

    Row {
        parent: content
        anchors {
            left: parent.left
            leftMargin: 10
            verticalCenter: parent.verticalCenter
        }

        height: parent.height
        spacing: 14

        Repeater {
            model: bar.specialWorkspaces

            Text {
                anchors.verticalCenter: parent.verticalCenter
                text: modelData.label
                color: bar.specialWorkspaceVisible(modelData.name) ? modelData.accent : bar.specialWorkspaceExists(modelData.name) ? Colors.foreground : Colors.muted
                font.family: "Syne, MesloLGS Nerd Font, monospace"
                font.pixelSize: 23
                font.bold: true

                MouseArea {
                    anchors.fill: parent
                    onClicked: bar.specialWorkspaceToggleRequested(modelData.name)
                }
            }
        }

        Rectangle {
            anchors.verticalCenter: parent.verticalCenter
            width: 31
            height: bar.barHeight
            color: "transparent"

            Text {
                anchors.centerIn: parent
                text: "⚙"
                color: bar.quickSettingsOpen ? Colors.accent : Colors.foreground
                font.family: "Syne, MesloLGS Nerd Font, monospace"
                font.pixelSize: 19
                font.bold: true
            }

            Rectangle {
                anchors {
                    left: parent.left
                    right: parent.right
                    bottom: parent.bottom
                }

                height: 2
                color: Colors.accent
                visible: bar.quickSettingsOpen
            }

            MouseArea {
                anchors.fill: parent
                onClicked: bar.quickSettingsToggleRequested()
            }
        }

        Text {
            anchors.verticalCenter: parent.verticalCenter
            text: "VOL " + Audio.percent(Pipewire.defaultAudioSink)
            color: Audio.muted(Pipewire.defaultAudioSink) ? Colors.danger : Colors.foreground
            font.family: "Syne, MesloLGS Nerd Font, monospace"
            font.pixelSize: 18
        }
    }
}