import QtQuick import QtQuick.Layouts import QtQuick.Controls import Quickshell import Quickshell.Wayland import Quickshell.Io import "../theme" import ".." PanelWindow { id: root anchors { top: true bottom: true left: true } margins { left: Theme.sidebarWidth top: Theme.topStripHeight } implicitWidth: Theme.hudWidth WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.namespace: "quickshell-systemhud" WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand visible: GlobalState.systemHudVisible color: "transparent" property real slideOffset: GlobalState.systemHudVisible ? 0 : -Theme.hudWidth Behavior on slideOffset { NumberAnimation { duration: Theme.animSmooth easing.type: Easing.OutQuint } } Shortcut { sequence: "Escape" onActivated: GlobalState.systemHudVisible = false } component SectionHeader: Item { property string sectionNum: "00" property string title: "Section" implicitWidth: parent.width implicitHeight: 24 RowLayout { anchors.fill: parent spacing: 8 Text { text: sectionNum + " //" font.family: Theme.fontMono font.pixelSize: 12 color: Theme.accentVolt } Text { text: title font.family: Theme.fontSerif font.italic: true font.pixelSize: 16 color: Theme.textPrimary Layout.fillWidth: true } } } component ToggleCard: Rectangle { property string title: "" property string icon: "" property string glyph: "" // Nerd Font glyph alternative to icon file property bool active: false signal clicked() Layout.preferredWidth: 160 Layout.preferredHeight: 56 color: Theme.bgPrimary border.color: active ? Qt.rgba(Theme.accentVolt.r, Theme.accentVolt.g, Theme.accentVolt.b, 0.3) : Theme.glassBorder border.width: 1 radius: 4 Behavior on border.color { ColorAnimation { duration: Theme.animFast } } MouseArea { anchors.fill: parent hoverEnabled: true onClicked: clicked() onEntered: if (!active) border.color = Theme.glassBorderHover onExited: if (!active) border.color = Theme.glassBorder } RowLayout { anchors.fill: parent anchors.margins: 12 spacing: 12 Text { visible: parent.parent.glyph !== "" text: parent.parent.glyph font.family: "JetBrainsMono Nerd Font" font.pixelSize: 20 color: parent.parent.active ? Theme.accentVolt : Theme.textMuted } Image { source: parent.parent.icon && parent.parent.glyph === "" ? Qt.resolvedUrl("../" + parent.parent.icon) : "" Layout.preferredWidth: 24 Layout.preferredHeight: 24 sourceSize: Qt.size(24, 24) visible: parent.parent.icon !== "" && parent.parent.glyph === "" } Text { text: parent.parent.title color: Theme.textPrimary font.family: Theme.fontSans font.pixelSize: 13 Layout.fillWidth: true elide: Text.ElideRight } } } component CustomSlider: Item { property real value: 0 signal valueChangedUser(real newVal) implicitWidth: parent.width implicitHeight: 24 Rectangle { id: track anchors.verticalCenter: parent.verticalCenter width: parent.width - 40 height: 6 color: Theme.bgSecondary radius: 3 Rectangle { id: fill width: track.width * (parent.parent.value / 100) height: track.height color: Theme.accentVolt radius: 3 } Rectangle { width: 14 height: 14 radius: 7 color: Theme.textPrimary anchors.verticalCenter: parent.verticalCenter x: Math.max(0, fill.width - 7) } MouseArea { anchors.fill: parent anchors.margins: -10 function updateValue(mouseX) { let p = Math.max(0, Math.min(1, mouseX / track.width)) parent.parent.valueChangedUser(p * 100) } onPressed: (mouse) => updateValue(mouse.x) onPositionChanged: (mouse) => updateValue(mouse.x) } } Text { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: Math.round(parent.value) + "%" color: Theme.textPrimary font.family: Theme.fontMono font.pixelSize: 12 } } component ProgressBarItem: ColumnLayout { property string label: "" property real percent: 0 property string valueText: "" property color barColor: Theme.accentVolt Layout.fillWidth: true spacing: 4 RowLayout { Layout.fillWidth: true Text { text: parent.parent.label color: Theme.textPrimary font.family: Theme.fontMono font.pixelSize: 12 } Item { Layout.fillWidth: true } Text { text: parent.parent.valueText color: Theme.textSecondary font.family: Theme.fontMono font.pixelSize: 12 } } Rectangle { Layout.fillWidth: true height: 8 color: Theme.bgSecondary radius: 4 Rectangle { width: parent.width * (parent.parent.percent / 100) height: parent.height color: parent.parent.barColor radius: 4 Behavior on width { NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } } Behavior on color { ColorAnimation { duration: Theme.animFast } } } } } Rectangle { id: panelContent anchors.fill: parent color: Theme.glassBg opacity: GlobalState.systemHudVisible ? 1 : 0 transform: Translate { x: slideOffset } Behavior on opacity { NumberAnimation { duration: Theme.animSmooth easing.type: Easing.OutQuint } } Rectangle { anchors.right: parent.right anchors.top: parent.top anchors.bottom: parent.bottom width: 1 color: Theme.glassBorder } Item { id: header anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right height: 60 Rectangle { anchors.bottom: parent.bottom width: parent.width height: 1 color: Theme.glassBorder } RowLayout { anchors.fill: parent anchors.leftMargin: 20 anchors.rightMargin: 20 Text { text: "System HUD" font.family: Theme.fontSerif font.italic: true font.pixelSize: 19 color: Theme.textPrimary Layout.fillWidth: true } Text { text: "✕" color: Theme.textMuted font.pixelSize: 16 MouseArea { anchors.fill: parent anchors.margins: -10 hoverEnabled: true onEntered: parent.color = Theme.textPrimary onExited: parent.color = Theme.textMuted onClicked: GlobalState.systemHudVisible = false } } } } ScrollView { id: scrollView anchors.top: header.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom contentWidth: availableWidth clip: true ColumnLayout { id: contentLayout width: parent.width - 40 x: 20 spacing: 32 Item { Layout.preferredHeight: 16 } // Top padding // Section 1: Toggles ColumnLayout { id: sectionToggles Layout.fillWidth: true spacing: 16 SectionHeader { sectionNum: "01"; title: "Toggles" } GridLayout { columns: 2 columnSpacing: 12 rowSpacing: 12 Layout.fillWidth: true ToggleCard { title: GlobalState.wifiSsid !== "" ? GlobalState.wifiSsid : "Wi-Fi" icon: "assets/wifi.svg" active: GlobalState.wifiConnected onClicked: { Quickshell.execDetached(["sh", "-c", "nm-connection-editor || foot -e nmtui"]) } } ToggleCard { title: "Bluetooth" icon: "assets/bluetooth.svg" active: false // Adjust if there's a bluetooth state onClicked: { let proc = Quickshell.createProcess("sh", ["-c", "foot -e bluetoothctl"]) proc.start() } } ToggleCard { property bool dndActive: false title: "Do Not Disturb" glyph: "\uF186" active: dndActive onClicked: dndActive = !dndActive } ToggleCard { title: GlobalState.volumeMuted ? "Muted" : "Sound" glyph: GlobalState.volumeMuted ? "\uF026" : "\uF028" active: !GlobalState.volumeMuted onClicked: GlobalState.toggleVolumeMute() } } } // Section 2: Volume ColumnLayout { id: sectionVolume Layout.fillWidth: true spacing: 16 SectionHeader { sectionNum: "02"; title: "Volume" } CustomSlider { Layout.fillWidth: true value: GlobalState.volume onValueChangedUser: (val) => GlobalState.volume = val // Adjust to actual volume setter if needed } RowLayout { Layout.fillWidth: true Text { text: GlobalState.volumeMuted ? "Unmute" : "Mute" color: Theme.textSecondary font.family: Theme.fontSans font.pixelSize: 13 MouseArea { anchors.fill: parent onClicked: GlobalState.toggleVolumeMute() } } Item { Layout.fillWidth: true } Text { text: "Sound Preferences..." color: Theme.textMuted font.family: Theme.fontSans font.pixelSize: 13 MouseArea { anchors.fill: parent hoverEnabled: true onEntered: parent.color = Theme.textPrimary onExited: parent.color = Theme.textMuted onClicked: { let proc = Quickshell.createProcess("pavucontrol", []) proc.start() } } } } } // Section 3: Brightness ColumnLayout { id: sectionBrightness Layout.fillWidth: true spacing: 16 SectionHeader { sectionNum: "03"; title: "Brightness" } CustomSlider { Layout.fillWidth: true value: GlobalState.brightness onValueChangedUser: (val) => GlobalState.setBrightness(val) } } // Section 4: Performance ColumnLayout { id: sectionPerformance Layout.fillWidth: true spacing: 16 SectionHeader { sectionNum: "04"; title: "Performance" } ProgressBarItem { label: "CPU" percent: GlobalState.cpuPercent valueText: GlobalState.cpuPercent + "%" barColor: GlobalState.cpuPercent > 80 ? Theme.accentCoral : Theme.accentVolt } ProgressBarItem { label: "RAM" percent: (GlobalState.memUsedGb / Math.max(1, GlobalState.memTotalGb)) * 100 valueText: GlobalState.memUsedGb + " / " + GlobalState.memTotalGb + " GB" } ProgressBarItem { label: "DISK" percent: GlobalState.diskPercent valueText: GlobalState.diskUsedGb + " / " + GlobalState.diskTotalGb + " GB" barColor: GlobalState.diskPercent > 90 ? Theme.accentCoral : Theme.accentVolt } ProgressBarItem { label: "VRAM" percent: (GlobalState.gpuVramUsed / Math.max(1, GlobalState.gpuVramTotal)) * 100 valueText: GlobalState.gpuVramUsed + " / " + GlobalState.gpuVramTotal + " GB" } } // Section 5: Network ColumnLayout { id: sectionNetwork Layout.fillWidth: true spacing: 16 SectionHeader { sectionNum: "05"; title: "Network" } RowLayout { spacing: 8 Rectangle { width: 8 height: 8 radius: 4 color: GlobalState.wifiConnected ? Theme.accentVolt : Theme.accentCoral } Text { text: { if (!GlobalState.wifiConnected) return "Disconnected"; if (GlobalState.connectionType === "wifi") return "Wi-Fi — " + (GlobalState.wifiSsid !== "" ? GlobalState.wifiSsid : "unknown"); if (GlobalState.connectionType === "eth") return "Ethernet"; return "Connected"; } color: Theme.textPrimary font.family: Theme.fontSans font.pixelSize: 14 } } Text { visible: GlobalState.wifiConnected text: "IP: " + (GlobalState.ipAddress !== "" ? GlobalState.ipAddress : "—") color: Theme.textSecondary font.family: Theme.fontMono font.pixelSize: 13 } Text { text: "Network Preferences..." color: Theme.textMuted font.family: Theme.fontSans font.pixelSize: 13 MouseArea { anchors.fill: parent hoverEnabled: true onEntered: parent.color = Theme.textPrimary onExited: parent.color = Theme.textMuted onClicked: { Quickshell.execDetached(["sh", "-c", "nm-connection-editor || foot -e nmtui"]) } } } } // Section 6: Battery ColumnLayout { id: sectionBattery Layout.fillWidth: true spacing: 16 visible: GlobalState.batteryPresent SectionHeader { sectionNum: "06"; title: "Battery" } RowLayout { spacing: 16 Text { text: GlobalState.batteryPercent + "%" font.family: Theme.fontMono font.pixelSize: 24 color: GlobalState.batteryStatus === "Charging" ? Theme.accentVolt : (GlobalState.batteryPercent < 20 ? Theme.accentCoral : Theme.textPrimary) } Text { text: GlobalState.batteryStatus color: Theme.textSecondary font.family: Theme.fontSans font.pixelSize: 14 } } } // Section 7: Now Playing ColumnLayout { id: sectionNowPlaying Layout.fillWidth: true spacing: 16 visible: GlobalState.mediaTitle !== "" SectionHeader { sectionNum: "07"; title: "Now Playing" } ColumnLayout { spacing: 4 Text { text: GlobalState.mediaTitle color: Theme.textPrimary font.family: Theme.fontSans font.bold: true font.pixelSize: 14 Layout.fillWidth: true elide: Text.ElideRight } Text { text: GlobalState.mediaArtist color: Theme.textSecondary font.family: Theme.fontSans font.pixelSize: 12 Layout.fillWidth: true elide: Text.ElideRight } } RowLayout { Layout.alignment: Qt.AlignHCenter spacing: 16 Text { text: "⏮" font.pixelSize: 20 color: Theme.textSecondary MouseArea { anchors.fill: parent anchors.margins: -10 hoverEnabled: true onEntered: parent.color = Theme.accentVolt onExited: parent.color = Theme.textSecondary onClicked: GlobalState.mediaPrevious() } } Text { text: GlobalState.mediaPlaying ? "⏸" : "▶" font.pixelSize: 24 color: Theme.textSecondary MouseArea { anchors.fill: parent anchors.margins: -10 hoverEnabled: true onEntered: parent.color = Theme.accentVolt onExited: parent.color = Theme.textSecondary onClicked: GlobalState.mediaPlayPause() } } Text { text: "⏭" font.pixelSize: 20 color: Theme.textSecondary MouseArea { anchors.fill: parent anchors.margins: -10 hoverEnabled: true onEntered: parent.color = Theme.accentVolt onExited: parent.color = Theme.textSecondary onClicked: GlobalState.mediaNext() } } } } // Section 8: Calendar ColumnLayout { id: sectionCalendar Layout.fillWidth: true spacing: 16 SectionHeader { sectionNum: "08"; title: "Calendar" } RowLayout { spacing: 12 Text { text: GlobalState.timeString font.family: Theme.fontMono font.pixelSize: 22 color: Theme.accentVolt } Text { text: GlobalState.fullDateString font.family: Theme.fontSans font.pixelSize: 13 color: Theme.textSecondary } } GridLayout { columns: 7 columnSpacing: 4 rowSpacing: 4 Layout.alignment: Qt.AlignHCenter Repeater { model: ["S", "M", "T", "W", "T", "F", "S"] Text { text: modelData font.family: Theme.fontMono font.pixelSize: 11 color: Theme.textMuted horizontalAlignment: Text.AlignHCenter Layout.preferredWidth: 32 } } Repeater { model: 31 Rectangle { property bool isToday: (index + 1) === new Date().getDate() Layout.preferredWidth: 32 Layout.preferredHeight: 32 color: isToday ? Theme.accentVolt : "transparent" radius: isToday ? 0 : 4 Text { anchors.centerIn: parent text: index + 1 color: parent.isToday ? "#ffffff" : Theme.textSecondary font.family: Theme.fontMono font.pixelSize: 12 } MouseArea { anchors.fill: parent hoverEnabled: true onEntered: if (!parent.isToday) parent.color = Qt.rgba(1, 1, 1, 0.04) onExited: if (!parent.isToday) parent.color = "transparent" } } } } } Item { Layout.preferredHeight: 20 } // Bottom padding } } } // Auto-scroll to active section when changed Connections { target: GlobalState function onSystemHudActiveSectionChanged() { if (!GlobalState.systemHudVisible) return; let targetY = 0; if (GlobalState.systemHudActiveSection === "volume") targetY = sectionVolume.y; else if (GlobalState.systemHudActiveSection === "brightness") targetY = sectionBrightness.y; else if (GlobalState.systemHudActiveSection === "performance") targetY = sectionPerformance.y; else if (GlobalState.systemHudActiveSection === "network") targetY = sectionNetwork.y; else if (GlobalState.systemHudActiveSection === "battery") targetY = sectionBattery.y; else if (GlobalState.systemHudActiveSection === "nowplaying") targetY = sectionNowPlaying.y; else if (GlobalState.systemHudActiveSection === "calendar") targetY = sectionCalendar.y; if (targetY > 0) { // Ensure targetY doesn't scroll past max possible scroll let maxY = Math.max(0, contentLayout.height - scrollView.height); scrollView.ScrollBar.vertical.position = Math.min(targetY, maxY) / contentLayout.height; } } } }