diff --git a/fedora/quickshell/control-center/Bar.qml b/fedora/quickshell/control-center/Bar.qml index 0e60afd..158c349 100644 --- a/fedora/quickshell/control-center/Bar.qml +++ b/fedora/quickshell/control-center/Bar.qml @@ -14,7 +14,6 @@ Item { id: bar property date currentTime: new Date() - property var controlCenter property var settings property var notificationCenter @@ -34,6 +33,11 @@ Item { property var audioSink: Pipewire.defaultAudioSink property var mediaPlayer: Mpris.players.values.find(player => player.playbackState === MprisPlaybackState.Playing) || Mpris.players.values[0] + readonly property var defaultBarModules: [ + "weather", "mail", "media", "notifications", "gpu", "cpu", "memory", + "disk", "keyboard", "audio", "calendar", "settings", "tray" + ] + property var activeModules: settings ? settings.visibleBarModules() : defaultBarModules function start(process) { if (!process.running) process.running = true; @@ -52,13 +56,28 @@ Item { } } + function moduleComponent(name) { + switch (name) { + case "weather": return weatherModule; + case "mail": return mailModule; + case "media": return mediaModule; + case "notifications": return notificationsModule; + case "gpu": return gpuModule; + case "cpu": return cpuModule; + case "memory": return memoryModule; + case "disk": return diskModule; + case "keyboard": return keyboardModule; + case "audio": return audioModule; + case "calendar": return calendarModule; + case "settings": return settingsModule; + case "tray": return trayModule; + default: return null; + } + } + PanelWindow { id: barWindow - anchors { - top: true - left: true - right: true - } + anchors { top: true; left: true; right: true } implicitHeight: 40 exclusiveZone: 40 color: "transparent" @@ -71,7 +90,6 @@ Item { anchors.topMargin: 4 anchors.leftMargin: 4 anchors.rightMargin: 4 - anchors.bottomMargin: 0 radius: 14 color: "#1e1e2e" border.color: "#313244" @@ -84,9 +102,7 @@ Item { spacing: 6 RowLayout { - id: workspaceRow spacing: 3 - Repeater { model: bar.workspaces delegate: Rectangle { @@ -95,26 +111,20 @@ Item { implicitHeight: 26 radius: 10 color: modelData.is_focused ? "#89b4fa" : (modelData.is_active ? "#45475a" : "#313244") - Label { id: workspaceLabel anchors.centerIn: parent text: modelData.name || String(modelData.idx) - color: modelData.is_focused ? "#1e1e2e" : "#cdd6f4" + color: modelData.is_focused ? "#11111b" : "#cdd6f4" font.pixelSize: 15 font.bold: true } - - MouseArea { - anchors.fill: parent - onClicked: bar.focusWorkspace(modelData.idx) - } + MouseArea { anchors.fill: parent; onClicked: bar.focusWorkspace(modelData.idx) } } } } Item { Layout.fillWidth: true } - Label { text: bar.windowTitle color: "#cdd6f4" @@ -124,230 +134,27 @@ Item { Layout.maximumWidth: 420 Layout.fillWidth: true } - Item { Layout.fillWidth: true } RowLayout { spacing: 4 - - Label { - text: bar.weather - color: "#89dceb" - font.pixelSize: 15 - ToolTip.visible: weatherMouse.containsMouse - ToolTip.text: "Weather" - MouseArea { - id: weatherMouse - anchors.fill: parent - } - } - - Label { - visible: bar.mail.length > 0 - text: bar.mail - color: "#b4befe" - font.pixelSize: 15 - leftPadding: 19 - BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } - ToolTip.visible: mailMouse.containsMouse - ToolTip.text: "Unread mail" - MouseArea { - id: mailMouse - anchors.fill: parent - onClicked: Quickshell.execDetached(["thunderbird"]) - } - } - - Label { - visible: bar.mediaPlayer !== null - text: bar.mediaPlayer ? (bar.mediaPlayer.trackTitle || bar.mediaPlayer.identity) : "" - color: "#f5c2e7" - leftPadding: 19 - elide: Text.ElideRight - Layout.maximumWidth: 190 - BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } - ToolTip.visible: mediaMouse.containsMouse - ToolTip.text: "Open media controls" - MouseArea { - id: mediaMouse - anchors.fill: parent - onClicked: { - if (bar.toggleMedia) bar.toggleMedia(); - } - } - } - - Label { - visible: !!bar.notificationCenter && bar.notificationCenter.count > 0 - text: bar.notificationCenter ? String(bar.notificationCenter.count) : "" - color: "#f38ba8" - leftPadding: 19 - BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } - ToolTip.visible: notificationMouse.containsMouse - ToolTip.text: "Open notifications" - MouseArea { - id: notificationMouse - anchors.fill: parent - onClicked: { - if (bar.notificationCenter) bar.notificationCenter.open = !bar.notificationCenter.open; - } - } - } - - Label { - text: bar.gpu - color: "#a6e3a1" - font.pixelSize: 15 - leftPadding: 19 - BarIcon { name: "gpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } - ToolTip.visible: gpuMouse.containsMouse - ToolTip.text: "GPU usage" - MouseArea { - id: gpuMouse - anchors.fill: parent - } - } - - Label { - text: bar.cpuUsage + "%" - color: "#a6e3a1" - font.pixelSize: 15 - leftPadding: 19 - BarIcon { name: "cpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } - ToolTip.visible: cpuMouse.containsMouse - ToolTip.text: "CPU usage" - MouseArea { id: cpuMouse; anchors.fill: parent } - } - - Label { - text: bar.memoryUsage + "%" - color: "#cba6f7" - font.pixelSize: 15 - leftPadding: 19 - BarIcon { name: "memory"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } - ToolTip.visible: memoryMouse.containsMouse - ToolTip.text: "Memory usage" - MouseArea { id: memoryMouse; anchors.fill: parent } - } - - Label { - text: bar.diskUsage - color: "#f38ba8" - font.pixelSize: 15 - leftPadding: 19 - BarIcon { name: "disk"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } - ToolTip.visible: diskMouse.containsMouse - ToolTip.text: "Root filesystem" - MouseArea { id: diskMouse; anchors.fill: parent } - } - - Label { - text: bar.language - color: "#94e2d5" - font.pixelSize: 15 - leftPadding: 19 - BarIcon { name: "language"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } - ToolTip.visible: languageMouse.containsMouse - ToolTip.text: "Keyboard layout" - MouseArea { id: languageMouse; anchors.fill: parent } - } - - Label { - text: bar.audioSink && bar.audioSink.audio - ? Math.round(bar.audioSink.audio.volume * 100) + "%" - : "--" - color: "#f9e2af" - font.pixelSize: 15 - leftPadding: 19 - BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } - ToolTip.visible: audioMouse.containsMouse - ToolTip.text: "Audio output" - MouseArea { - id: audioMouse - anchors.fill: parent - onClicked: { - if (bar.audioSink && bar.audioSink.audio) bar.audioSink.audio.muted = !bar.audioSink.audio.muted; - } - } - } - - Label { - text: Qt.formatDateTime(bar.currentTime, "dd.MM HH:mm") - color: "#cdd6f4" - font.pixelSize: 15 - ToolTip.visible: clockMouse.containsMouse - ToolTip.text: "Open calendar" - MouseArea { - id: clockMouse - anchors.fill: parent - onClicked: Quickshell.execDetached(["sh", "-c", "alacritty --title khal-popup -e bash -c 'vdirsyncer sync 2>/dev/null; ikhal'"]) - } - } - - Label { - text: "" - color: "#89b4fa" - Layout.preferredWidth: 18 - Layout.minimumWidth: 18 - Layout.maximumWidth: 18 - BarIcon { name: "gear"; size: 18; anchors.centerIn: parent } - ToolTip.visible: settingsMouse.containsMouse - ToolTip.text: "Quick settings" - MouseArea { - id: settingsMouse - anchors.fill: parent - onClicked: { - if (bar.controlCenter) bar.controlCenter.togglePanel(); - } - } - } - Repeater { - model: SystemTray.items - delegate: Item { - required property var modelData - property bool hiddenTrayItem: !modelData - || /wayland to x11 video bridge|screen sharing|screen share/i.test(String(modelData.title || "")) - || /video-display|screen-share|screen-sharing/i.test(String(modelData.icon || "")) - visible: !hiddenTrayItem - implicitWidth: hiddenTrayItem ? 0 : 20 - implicitHeight: 22 - + model: bar.activeModules + delegate: Rectangle { + required property string modelData + property bool cardStyle: !bar.settings || bar.settings.panelStyle !== "indicators" + implicitWidth: moduleLoader.implicitWidth + (cardStyle ? 10 : 0) + implicitHeight: 28 + radius: 8 + color: cardStyle ? "#24243a" : "transparent" + border.color: cardStyle ? "#313244" : "transparent" + border.width: cardStyle ? 1 : 0 + Layout.preferredWidth: implicitWidth + Layout.preferredHeight: implicitHeight Loader { - id: trayIcon - anchors.fill: parent - active: !hiddenTrayItem && modelData - sourceComponent: Component { - IconImage { - anchors.fill: parent - implicitSize: 18 - source: modelData && modelData.icon && modelData.icon.startsWith("image://") - ? modelData.icon - : (modelData ? Quickshell.iconPath(modelData.icon) : "") - } - } - } - - BarIcon { - visible: !hiddenTrayItem && !trayIcon.active - anchors.fill: parent - enabled: !hiddenTrayItem - name: "video" - size: 15 - anchors.margins: 2 - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: function(mouse) { - if (!modelData) return; - if (mouse.button === Qt.RightButton && modelData.hasMenu) { - modelData.display(barWindow, mouse.x, mouse.y + 20); - } else { - modelData.activate(); - } - } + id: moduleLoader + anchors.centerIn: parent + sourceComponent: bar.moduleComponent(modelData) } } } @@ -356,10 +163,211 @@ Item { } } - PwObjectTracker { - objects: [bar.audioSink] + Component { + id: weatherModule + Label { + text: bar.weather + color: "#89dceb" + font.pixelSize: 15 + ToolTip.visible: mouse.containsMouse + ToolTip.text: "Weather" + MouseArea { id: mouse; anchors.fill: parent; hoverEnabled: true } + } } + Component { + id: mailModule + Label { + text: bar.mail + visible: bar.mail.length > 0 + color: "#b4befe" + font.pixelSize: 15 + leftPadding: 19 + BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } + MouseArea { anchors.fill: parent; onClicked: Quickshell.execDetached(["thunderbird"]) } + } + } + + Component { + id: mediaModule + Label { + text: bar.mediaPlayer ? (bar.mediaPlayer.trackTitle || bar.mediaPlayer.identity) : "" + visible: bar.mediaPlayer !== null + color: "#cba6f7" + font.pixelSize: 15 + leftPadding: 19 + elide: Text.ElideRight + Layout.maximumWidth: 190 + BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } + MouseArea { anchors.fill: parent; onClicked: if (bar.toggleMedia) bar.toggleMedia() } + } + } + + Component { + id: notificationsModule + Label { + text: bar.notificationCenter ? String(bar.notificationCenter.count) : "" + visible: !!bar.settings && bar.settings.notificationBadge && !!bar.notificationCenter && bar.notificationCenter.count > 0 + color: "#f38ba8" + font.pixelSize: 15 + leftPadding: 19 + BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } + MouseArea { + anchors.fill: parent + onClicked: if (bar.notificationCenter) bar.notificationCenter.open = !bar.notificationCenter.open + } + } + } + + Component { + id: gpuModule + Label { + text: bar.gpu + color: "#a6e3a1" + font.pixelSize: 15 + leftPadding: 19 + BarIcon { name: "gpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } + } + } + + Component { + id: cpuModule + Label { + text: bar.cpuUsage + "%" + color: "#a6e3a1" + font.pixelSize: 15 + leftPadding: 19 + BarIcon { name: "cpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } + } + } + + Component { + id: memoryModule + Label { + text: bar.memoryUsage + "%" + color: "#cba6f7" + font.pixelSize: 15 + leftPadding: 19 + BarIcon { name: "memory"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } + } + } + + Component { + id: diskModule + Label { + text: bar.diskUsage + color: "#f38ba8" + font.pixelSize: 15 + leftPadding: 19 + BarIcon { name: "disk"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } + } + } + + Component { + id: keyboardModule + Label { + text: bar.language + color: "#94e2d5" + font.pixelSize: 15 + leftPadding: 19 + BarIcon { name: "language"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } + } + } + + Component { + id: audioModule + Label { + text: bar.audioSink && bar.audioSink.audio ? Math.round(bar.audioSink.audio.volume * 100) + "%" : "--" + color: "#f9e2af" + font.pixelSize: 15 + leftPadding: 19 + BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } + MouseArea { + anchors.fill: parent + onClicked: if (bar.audioSink && bar.audioSink.audio) bar.audioSink.audio.muted = !bar.audioSink.audio.muted + } + } + } + + Component { + id: calendarModule + Label { + text: Qt.formatDateTime(bar.currentTime, "dd.MM HH:mm") + color: "#cdd6f4" + font.pixelSize: 15 + MouseArea { + anchors.fill: parent + onClicked: Quickshell.execDetached(["sh", "-c", "alacritty --title khal-popup -e bash -c 'vdirsyncer sync 2>/dev/null; ikhal'"]) + } + } + } + + Component { + id: settingsModule + Label { + text: "⚙" + color: "#89b4fa" + font.pixelSize: 17 + MouseArea { anchors.fill: parent; onClicked: if (bar.controlCenter) bar.controlCenter.togglePanel() } + } + } + + Component { + id: trayModule + Item { + implicitWidth: trayRow.implicitWidth + implicitHeight: 22 + Row { + id: trayRow + spacing: 4 + Repeater { + model: SystemTray.items + delegate: Item { + required property var modelData + property bool hiddenTrayItem: !modelData + || /wayland to x11 video bridge|screen sharing|screen share/i.test(String(modelData.title || "")) + || /video-display|screen-share|screen-sharing/i.test(String(modelData.icon || "")) + visible: !hiddenTrayItem + width: hiddenTrayItem ? 0 : 20 + height: 22 + Loader { + id: trayIcon + anchors.fill: parent + active: !hiddenTrayItem && modelData + sourceComponent: Component { + IconImage { + anchors.fill: parent + implicitSize: 18 + source: modelData && modelData.icon && modelData.icon.startsWith("image://") + ? modelData.icon : (modelData ? Quickshell.iconPath(modelData.icon) : "") + } + } + } + BarIcon { + visible: !hiddenTrayItem && !trayIcon.active + anchors.fill: parent + name: "video" + size: 15 + anchors.margins: 2 + } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: function(mouse) { + if (!modelData) return; + if (mouse.button === Qt.RightButton && modelData.hasMenu) + modelData.display(barWindow, mouse.x, mouse.y + 20); + else modelData.activate(); + } + } + } + } + } + } + } + + PwObjectTracker { objects: [bar.audioSink] } + Process { id: workspaceReader command: ["niri", "msg", "-j", "workspaces"] @@ -373,18 +381,11 @@ Item { } } } - Process { id: windowReader command: ["niri", "msg", "-j", "focused-window"] - stdout: StdioCollector { - onStreamFinished: { - const value = bar.parseJson(this, {}); - bar.windowTitle = value.title || "Desktop"; - } - } + stdout: StdioCollector { onStreamFinished: bar.windowTitle = bar.parseJson(this, {}).title || "Desktop" } } - Process { id: languageReader command: ["niri", "msg", "-j", "keyboard-layouts"] @@ -400,31 +401,26 @@ Item { } } } - Process { id: cpuReader command: ["sh", "-c", "awk '/^cpu / { idle=$5; total=0; for (i=2; i<=NF; i++) total += $i; print int((total-idle)*100/total) }' /proc/stat"] stdout: StdioCollector { onStreamFinished: bar.cpuUsage = parseInt(text.trim()) || 0 } } - Process { id: memoryReader command: ["sh", "-c", "free | awk '/^Mem:/ { print int($3*100/$2) }'"] stdout: StdioCollector { onStreamFinished: bar.memoryUsage = parseInt(text.trim()) || 0 } } - Process { id: diskReader command: ["sh", "-c", "df -P / | awk 'NR==2 { print $5 }'"] stdout: StdioCollector { onStreamFinished: bar.diskUsage = text.trim() || "--" } } - Process { id: temperatureReader command: ["sh", "-c", "max=0; for file in /sys/class/thermal/thermal_zone*/temp; do value=$(cat $file 2>/dev/null); [ $value -gt $max ] 2>/dev/null && max=$value; done; if [ $max -gt 0 ]; then echo $((max / 1000))C; else echo --; fi"] stdout: StdioCollector { onStreamFinished: bar.temperature = text.trim() || "--" } } - Process { id: gpuReader command: ["sh", "-c", "nvidia-smi --query-gpu=utilization.gpu,temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1"] @@ -438,27 +434,15 @@ Item { } } } - Process { id: weatherReader command: ["bash", Quickshell.shellPath("../scripts/weather.sh")] - stdout: StdioCollector { - onStreamFinished: { - const value = bar.parseJson(this, {}); - bar.weather = value.text || "N/A"; - } - } + stdout: StdioCollector { onStreamFinished: bar.weather = bar.parseJson(this, {}).text || "N/A" } } - Process { id: mailReader command: ["sh", "-c", "if [ -x $HOME/.local/bin/mail-counter ]; then $HOME/.local/bin/mail-counter; else printf '{}'; fi"] - stdout: StdioCollector { - onStreamFinished: { - const value = bar.parseJson(this, {}); - bar.mail = value.text || ""; - } - } + stdout: StdioCollector { onStreamFinished: bar.mail = bar.parseJson(this, {}).text || "" } } Timer { @@ -468,50 +452,17 @@ Item { repeat: true onTriggered: { bar.currentTime = new Date(); - bar.start(windowReader); - bar.start(cpuReader); - bar.start(memoryReader); - bar.start(diskReader); - bar.start(temperatureReader); - bar.start(gpuReader); - bar.start(mailReader); + bar.start(windowReader); bar.start(cpuReader); bar.start(memoryReader); bar.start(diskReader); + bar.start(temperatureReader); bar.start(gpuReader); bar.start(mailReader); } } - - Timer { - id: weatherTimer - interval: 900000 - running: true - repeat: true - onTriggered: bar.start(weatherReader) - } - - Timer { - id: languageTimer - interval: 200 - running: true - repeat: true - onTriggered: bar.start(languageReader) - } - - Timer { - id: workspaceTimer - interval: 100 - running: true - repeat: true - onTriggered: bar.start(workspaceReader) - } + Timer { id: weatherTimer; interval: 900000; running: true; repeat: true; onTriggered: bar.start(weatherReader) } + Timer { id: languageTimer; interval: 200; running: true; repeat: true; onTriggered: bar.start(languageReader) } + Timer { id: workspaceTimer; interval: 100; running: true; repeat: true; onTriggered: bar.start(workspaceReader) } Component.onCompleted: { - bar.start(workspaceReader); - bar.start(windowReader); - bar.start(languageReader); - bar.start(cpuReader); - bar.start(memoryReader); - bar.start(diskReader); - bar.start(temperatureReader); - bar.start(gpuReader); - bar.start(weatherReader); - bar.start(mailReader); + bar.start(workspaceReader); bar.start(windowReader); bar.start(languageReader); bar.start(cpuReader); + bar.start(memoryReader); bar.start(diskReader); bar.start(temperatureReader); bar.start(gpuReader); + bar.start(weatherReader); bar.start(mailReader); } } diff --git a/fedora/quickshell/control-center/BarPage.qml b/fedora/quickshell/control-center/BarPage.qml new file mode 100644 index 0000000..85a0b45 --- /dev/null +++ b/fedora/quickshell/control-center/BarPage.qml @@ -0,0 +1,97 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property var settings + property var theme + property var availableModules: [] + implicitHeight: pageColumn.implicitHeight + + function moveModule(module, delta) { + let modules = settings.barModules.slice(); + const index = modules.indexOf(module); + const next = index + delta; + if (index < 0 || next < 0 || next >= modules.length) return; + const swap = modules[next]; + modules[next] = module; + modules[index] = swap; + settings.setBarModules(modules); + } + + ColumnLayout { + id: pageColumn + width: parent.width + spacing: 10 + + SettingsCard { + theme: root.theme + title: "Bar modules" + description: "Weather stays first; workspace buttons remain on the left." + Layout.fillWidth: true + + Repeater { + model: root.settings && Array.isArray(root.settings.barModules) + ? root.settings.barModules : root.availableModules + delegate: SettingsRow { + required property string modelData + required property int index + theme: root.theme + label: modelData + description: index + 1 + " / " + (root.settings && Array.isArray(root.settings.barModules) + ? root.settings.barModules.length : root.availableModules.length) + CCSwitch { + theme: root.theme + checked: root.settings.visibleBarModules().indexOf(modelData) >= 0 + onToggled: root.settings.setModuleVisible(modelData, checked) + } + CCButton { + theme: root.theme + text: "↑" + enabled: index > 0 + onClicked: root.moveModule(modelData, -1) + } + CCButton { + theme: root.theme + text: "↓" + enabled: index < (root.settings && Array.isArray(root.settings.barModules) + ? root.settings.barModules.length : root.availableModules.length) - 1 + onClicked: root.moveModule(modelData, 1) + } + } + } + } + + SettingsCard { + theme: root.theme + title: "Indicator style" + description: "Keep the bar compact and consistent with the cards." + Layout.fillWidth: true + SettingsRow { + theme: root.theme + label: "Card style" + ComboBox { + palette.base: root.theme.surfaceVariant + palette.button: root.theme.surfaceVariant + palette.text: root.theme.text + palette.buttonText: root.theme.text + palette.highlight: root.theme.primary + model: ["Cards", "Indicators"] + currentIndex: root.settings.panelStyle === "indicators" ? 1 : 0 + onActivated: root.settings.panelStyle = currentIndex === 1 ? "indicators" : "cards" + } + } + SettingsRow { + theme: root.theme + label: "Notification badge" + CCSwitch { + theme: root.theme + checked: root.settings.notificationBadge + onToggled: root.settings.notificationBadge = checked + } + } + } + } +} diff --git a/fedora/quickshell/control-center/CCButton.qml b/fedora/quickshell/control-center/CCButton.qml new file mode 100644 index 0000000..206beef --- /dev/null +++ b/fedora/quickshell/control-center/CCButton.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls + +Button { + id: root + + property var theme + property bool accent: false + + implicitHeight: 32 + leftPadding: 11 + rightPadding: 11 + topPadding: 5 + bottomPadding: 5 + + contentItem: Label { + text: root.text + color: root.enabled + ? (root.accent && root.theme ? root.theme.background : (root.theme ? root.theme.text : "#cdd6f4")) + : (root.theme ? root.theme.dimText : "#6c7086") + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 12 + elide: Text.ElideRight + } + + background: Rectangle { + radius: root.theme ? root.theme.controlRadius : 9 + color: !root.enabled + ? (root.theme ? root.theme.surface : "#181825") + : (root.accent + ? (root.theme ? root.theme.primary : "#89b4fa") + : (root.hovered ? (root.theme ? root.theme.surfaceRaised : "#24243a") + : (root.theme ? root.theme.surfaceVariant : "#1e1e2e"))) + border.color: root.theme ? root.theme.border : "#313244" + border.width: 1 + } +} diff --git a/fedora/quickshell/control-center/CCSlider.qml b/fedora/quickshell/control-center/CCSlider.qml new file mode 100644 index 0000000..d25ee63 --- /dev/null +++ b/fedora/quickshell/control-center/CCSlider.qml @@ -0,0 +1,36 @@ +import QtQuick +import QtQuick.Controls + +Slider { + id: root + + property var theme + implicitHeight: 24 + + background: Rectangle { + x: root.leftPadding + y: root.topPadding + root.availableHeight / 2 - height / 2 + width: root.availableWidth + height: 4 + radius: 2 + color: root.theme ? root.theme.surfaceRaised : "#24243a" + + Rectangle { + width: root.visualPosition * parent.width + height: parent.height + radius: 2 + color: root.theme ? root.theme.primary : "#89b4fa" + } + } + + handle: Rectangle { + x: root.leftPadding + root.visualPosition * (root.availableWidth - width) + y: root.topPadding + root.availableHeight / 2 - height / 2 + width: 14 + height: 14 + radius: 7 + color: root.theme ? root.theme.primary : "#89b4fa" + border.color: root.theme ? root.theme.background : "#11111b" + border.width: 2 + } +} diff --git a/fedora/quickshell/control-center/CCSwitch.qml b/fedora/quickshell/control-center/CCSwitch.qml new file mode 100644 index 0000000..b015bd2 --- /dev/null +++ b/fedora/quickshell/control-center/CCSwitch.qml @@ -0,0 +1,32 @@ +import QtQuick +import QtQuick.Controls + +Switch { + id: root + + property var theme + implicitWidth: 42 + implicitHeight: 24 + + indicator: Rectangle { + x: root.leftPadding + y: (root.height - height) / 2 + width: 40 + height: 22 + radius: 11 + color: root.checked && root.enabled + ? (root.theme ? root.theme.primary : "#89b4fa") + : (root.theme ? root.theme.surfaceRaised : "#24243a") + border.color: root.theme ? root.theme.border : "#313244" + + Rectangle { + x: root.checked ? parent.width - width - 3 : 3 + anchors.verticalCenter: parent.verticalCenter + width: 16 + height: 16 + radius: 8 + color: root.checked ? (root.theme ? root.theme.background : "#11111b") : (root.theme ? root.theme.mutedText : "#9399b2") + Behavior on x { NumberAnimation { duration: 120 } } + } + } +} diff --git a/fedora/quickshell/control-center/MediaPopup.qml b/fedora/quickshell/control-center/MediaPopup.qml index fec45ca..13c72b7 100644 --- a/fedora/quickshell/control-center/MediaPopup.qml +++ b/fedora/quickshell/control-center/MediaPopup.qml @@ -9,6 +9,8 @@ import Quickshell.Wayland Item { id: root + UiTokens { id: theme } + property bool open: false property var selectedPlayer: null property var player: selectedPlayer @@ -51,8 +53,8 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 52 radius: 20 - color: "#f5f2ea" - border.color: "#d8d0c2" + color: "#11111b" + border.color: "#313244" border.width: 1 z: 1 @@ -70,30 +72,20 @@ Item { Layout.fillWidth: true Label { text: "Media" - color: "#252321" + color: "#cdd6f4" font.pixelSize: 20 font.bold: true Layout.fillWidth: true } Label { text: root.player ? root.player.identity : "No player" - color: "#7c756c" + color: "#9399b2" elide: Text.ElideRight } - Rectangle { - width: 26 - height: 26 - radius: 13 - color: "#e5e0d7" - Label { - anchors.centerIn: parent - text: "x" - color: "#6e675f" - } - MouseArea { - anchors.fill: parent - onClicked: root.dismissed() - } + CCButton { + theme: theme + text: "Close" + onClicked: root.dismissed() } } @@ -107,7 +99,7 @@ Item { Layout.preferredWidth: 132 Layout.preferredHeight: 132 radius: 14 - color: "#e5e0d7" + color: "#181825" clip: true Image { @@ -132,7 +124,7 @@ Item { Label { text: root.player ? (root.player.trackTitle || "Unknown track") : "" - color: "#252321" + color: "#cdd6f4" font.pixelSize: 17 font.bold: true elide: Text.ElideRight @@ -140,20 +132,21 @@ Item { } Label { text: root.player ? (root.player.trackArtist || root.player.trackAlbum || "Unknown artist") : "" - color: "#7c756c" + color: "#9399b2" elide: Text.ElideRight Layout.fillWidth: true } Item { Layout.fillHeight: true } Label { text: root.player ? root.formatTime(root.player.position) + " / " + root.formatTime(root.player.length) : "" - color: "#7c756c" + color: "#9399b2" font.pixelSize: 11 } } } - Slider { + CCSlider { + theme: theme visible: root.player && root.player.length > 0 Layout.fillWidth: true from: 0 @@ -169,17 +162,20 @@ Item { Layout.alignment: Qt.AlignHCenter spacing: 14 - Button { + CCButton { + theme: theme text: "Prev" enabled: !!(root.player && root.player.canGoPrevious) onClicked: root.player.previous() } - Button { + CCButton { + theme: theme text: root.player && root.player.playbackState === MprisPlaybackState.Playing ? "Pause" : "Play" enabled: !!(root.player && root.player.canTogglePlaying) onClicked: root.player.togglePlaying() } - Button { + CCButton { + theme: theme text: "Next" enabled: !!(root.player && root.player.canGoNext) onClicked: root.player.next() @@ -191,13 +187,14 @@ Item { Layout.fillWidth: true Label { text: "Players" - color: "#7c756c" + color: "#9399b2" Layout.fillWidth: true } Repeater { model: Mpris.players - delegate: Button { + delegate: CCButton { required property var modelData + theme: theme text: modelData.identity onClicked: root.selectedPlayer = modelData } diff --git a/fedora/quickshell/control-center/NavigationRail.qml b/fedora/quickshell/control-center/NavigationRail.qml new file mode 100644 index 0000000..71c97af --- /dev/null +++ b/fedora/quickshell/control-center/NavigationRail.qml @@ -0,0 +1,88 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property var theme + property int currentIndex: 0 + signal selected(int index) + + readonly property var names: ["Quick", "Bar", "Panel", "Notifications", "Status"] + implicitWidth: 78 + + Rectangle { + anchors.fill: parent + radius: root.theme ? root.theme.cardRadius : 14 + color: root.theme ? root.theme.surface : "#181825" + border.color: root.theme ? root.theme.border : "#313244" + border.width: 1 + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 7 + spacing: 5 + + Label { + text: "iN" + color: root.theme ? root.theme.primary : "#89b4fa" + font.pixelSize: 17 + font.bold: true + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + Layout.bottomMargin: 5 + } + + Repeater { + model: root.names + delegate: Rectangle { + required property int index + required property string modelData + Layout.fillWidth: true + Layout.preferredHeight: 48 + radius: 11 + color: index === root.currentIndex + ? (root.theme ? root.theme.primary : "#89b4fa") + : (navMouse.containsMouse ? (root.theme ? root.theme.surfaceRaised : "#24243a") : "transparent") + + Column { + anchors.centerIn: parent + spacing: 2 + Label { + text: ["⌂", "≡", "▣", "●", "⌁"][index] + color: index === root.currentIndex ? (root.theme ? root.theme.background : "#11111b") : (root.theme ? root.theme.mutedText : "#9399b2") + font.pixelSize: 17 + horizontalAlignment: Text.AlignHCenter + width: 58 + } + Label { + text: modelData + color: index === root.currentIndex ? (root.theme ? root.theme.background : "#11111b") : (root.theme ? root.theme.mutedText : "#9399b2") + font.pixelSize: 9 + horizontalAlignment: Text.AlignHCenter + width: 58 + } + } + + MouseArea { + id: navMouse + anchors.fill: parent + hoverEnabled: true + onClicked: root.selected(index) + } + } + } + + Item { Layout.fillHeight: true } + + Label { + text: "Niri" + color: root.theme ? root.theme.dimText : "#6c7086" + font.pixelSize: 9 + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + } + } +} diff --git a/fedora/quickshell/control-center/Notifications.qml b/fedora/quickshell/control-center/Notifications.qml index 116da32..4d69ae4 100644 --- a/fedora/quickshell/control-center/Notifications.qml +++ b/fedora/quickshell/control-center/Notifications.qml @@ -9,10 +9,13 @@ import Quickshell.Wayland Item { id: root + UiTokens { id: theme } + property bool open: false property bool dnd: false property bool toastOpen: false property var latest: null + property var settings readonly property int count: server.trackedNotifications ? server.trackedNotifications.values.length : 0 signal dismissed() @@ -32,7 +35,7 @@ Item { Timer { id: toastTimer - interval: 5000 + interval: root.settings ? root.settings.notificationPopupDuration : 5000 repeat: false onTriggered: root.toastOpen = false } @@ -48,13 +51,12 @@ Item { Rectangle { width: Math.min(390, parent.width - 32) - height: 100 + height: 112 anchors.top: parent.top - anchors.right: parent.right - anchors.margins: 16 + x: root.settings && root.settings.notificationPosition === "top-left" ? 16 : parent.width - width - 16 radius: 16 - color: "#f5f2ea" - border.color: "#d8d0c2" + color: "#181825" + border.color: "#313244" border.width: 1 MouseArea { @@ -69,16 +71,26 @@ Item { anchors.fill: parent anchors.margins: 14 spacing: 3 - Label { - text: root.latest ? root.latest.appName : "" - color: "#7c756c" - font.pixelSize: 11 + RowLayout { Layout.fillWidth: true - elide: Text.ElideRight + spacing: 8 + + Label { + text: root.latest ? root.latest.appName : "" + color: "#9399b2" + font.pixelSize: 11 + Layout.fillWidth: true + elide: Text.ElideRight + } + CCButton { + theme: theme + text: "Close" + onClicked: root.toastOpen = false + } } Label { text: root.latest ? root.latest.summary : "" - color: "#252321" + color: "#cdd6f4" font.pixelSize: 16 font.bold: true Layout.fillWidth: true @@ -86,7 +98,7 @@ Item { } Label { text: root.latest ? root.latest.body : "" - color: "#5f5952" + color: "#9399b2" Layout.fillWidth: true elide: Text.ElideRight } @@ -115,8 +127,8 @@ Item { anchors.right: parent.right anchors.margins: 16 radius: 20 - color: "#f5f2ea" - border.color: "#d8d0c2" + color: "#11111b" + border.color: "#313244" border.width: 1 z: 1 @@ -134,16 +146,18 @@ Item { Layout.fillWidth: true Label { text: "Notifications" - color: "#252321" + color: "#cdd6f4" font.pixelSize: 22 font.bold: true Layout.fillWidth: true } - Button { + CCButton { + theme: theme text: root.dnd ? "DND on" : "DND off" onClicked: root.dnd = !root.dnd } - Button { + CCButton { + theme: theme text: "Close" onClicked: root.dismissed() } @@ -152,7 +166,7 @@ Item { Label { visible: root.count === 0 text: "No notifications" - color: "#7c756c" + color: "#9399b2" Layout.alignment: Qt.AlignHCenter Layout.topMargin: 18 } @@ -173,7 +187,7 @@ Item { Layout.fillWidth: true implicitHeight: notificationColumn.implicitHeight + 22 radius: 14 - color: "#e8e3da" + color: "#181825" ColumnLayout { id: notificationColumn @@ -185,19 +199,20 @@ Item { Layout.fillWidth: true Label { text: modelData.appName - color: "#7c756c" + color: "#9399b2" font.pixelSize: 11 Layout.fillWidth: true elide: Text.ElideRight } - Button { + CCButton { + theme: theme text: "x" onClicked: modelData.dismiss() } } Label { text: modelData.summary - color: "#252321" + color: "#cdd6f4" font.pixelSize: 15 font.bold: true Layout.fillWidth: true @@ -205,7 +220,7 @@ Item { } Label { text: modelData.body - color: "#5f5952" + color: "#9399b2" Layout.fillWidth: true wrapMode: Text.Wrap } diff --git a/fedora/quickshell/control-center/NotificationsPage.qml b/fedora/quickshell/control-center/NotificationsPage.qml new file mode 100644 index 0000000..3982680 --- /dev/null +++ b/fedora/quickshell/control-center/NotificationsPage.qml @@ -0,0 +1,78 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property var settings + property var theme + property var notificationCenter + implicitHeight: pageColumn.implicitHeight + + ColumnLayout { + id: pageColumn + width: parent.width + spacing: 10 + + SettingsCard { + theme: root.theme + title: "Notifications" + description: root.notificationCenter ? root.notificationCenter.count + " tracked notifications" : "Notification service" + Layout.fillWidth: true + SettingsRow { + theme: root.theme + label: "Bar badge" + CCSwitch { + theme: root.theme + checked: root.settings.notificationBadge + onToggled: root.settings.notificationBadge = checked + } + } + SettingsRow { + theme: root.theme + label: "Popup position" + ComboBox { + palette.base: root.theme.surfaceVariant + palette.button: root.theme.surfaceVariant + palette.text: root.theme.text + palette.buttonText: root.theme.text + palette.highlight: root.theme.primary + model: ["Top right", "Top left"] + currentIndex: root.settings.notificationPosition === "top-left" ? 1 : 0 + onActivated: root.settings.notificationPosition = currentIndex === 1 ? "top-left" : "top-right" + } + } + SettingsRow { + theme: root.theme + label: "Popup duration" + SpinBox { + palette.base: root.theme.surfaceVariant + palette.text: root.theme.text + palette.button: root.theme.surfaceVariant + palette.buttonText: root.theme.text + from: 1000 + to: 15000 + stepSize: 500 + value: root.settings.notificationPopupDuration + editable: true + onValueModified: root.settings.notificationPopupDuration = value + } + Label { text: "ms"; color: root.theme.mutedText } + } + } + + SettingsCard { + theme: root.theme + title: "Current queue" + description: "Click the bar badge to open the existing notification center." + Layout.fillWidth: true + CCButton { + theme: root.theme + text: "Open notifications" + accent: true + onClicked: if (root.notificationCenter) root.notificationCenter.open = !root.notificationCenter.open + } + } + } +} diff --git a/fedora/quickshell/control-center/PanelPage.qml b/fedora/quickshell/control-center/PanelPage.qml new file mode 100644 index 0000000..3f9af25 --- /dev/null +++ b/fedora/quickshell/control-center/PanelPage.qml @@ -0,0 +1,93 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property var settings + property var theme + implicitHeight: pageColumn.implicitHeight + + ColumnLayout { + id: pageColumn + width: parent.width + spacing: 10 + + SettingsCard { + theme: root.theme + title: "Panel" + description: "The right-side full-height slide-out panel." + Layout.fillWidth: true + SettingsRow { + theme: root.theme + label: "Width" + SpinBox { + palette.base: root.theme.surfaceVariant + palette.text: root.theme.text + palette.button: root.theme.surfaceVariant + palette.buttonText: root.theme.text + from: 360 + to: 720 + value: root.settings.panelWidth + editable: true + onValueModified: root.settings.panelWidth = value + } + } + SettingsRow { + theme: root.theme + label: "Corner radius" + SpinBox { + palette.base: root.theme.surfaceVariant + palette.text: root.theme.text + palette.button: root.theme.surfaceVariant + palette.buttonText: root.theme.text + from: 8 + to: 40 + value: root.settings.panelRadius + editable: true + onValueModified: root.settings.panelRadius = value + } + } + SettingsRow { + theme: root.theme + label: "Opacity" + CCSlider { + theme: root.theme + implicitWidth: 130 + from: 0.75 + to: 1.0 + value: root.settings.panelOpacity + onMoved: root.settings.panelOpacity = value + } + Label { + text: Math.round(root.settings.panelOpacity * 100) + "%" + color: root.theme.text + } + } + SettingsRow { + theme: root.theme + label: "Open from corner" + description: "A small top-right hot corner." + CCSwitch { + theme: root.theme + checked: root.settings.panelOpenOnCorner + onToggled: root.settings.panelOpenOnCorner = checked + } + } + } + + SettingsCard { + theme: root.theme + title: "Motion" + description: "Content appears after the slide completes." + Layout.fillWidth: true + Label { + text: "The panel keeps its existing open/close animation and overlay dismissal." + color: root.theme.mutedText + wrapMode: Text.Wrap + Layout.fillWidth: true + } + } + } +} diff --git a/fedora/quickshell/control-center/QuickPage.qml b/fedora/quickshell/control-center/QuickPage.qml new file mode 100644 index 0000000..64999c0 --- /dev/null +++ b/fedora/quickshell/control-center/QuickPage.qml @@ -0,0 +1,219 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Quickshell.Bluetooth +import Quickshell.Networking +import Quickshell.Services.Pipewire +import Quickshell.Services.UPower + +Item { + id: root + + property var controlCenter + property var settings + property var theme + implicitHeight: pageColumn.implicitHeight + + ColumnLayout { + id: pageColumn + width: parent.width + spacing: 10 + + SettingsCard { + theme: root.theme + title: "Wi-Fi" + description: root.controlCenter && root.controlCenter.wifiDevice + ? (root.controlCenter.wifiDevice.name || "Available networks") + : "Wireless networking unavailable" + Layout.fillWidth: true + + SettingsRow { + theme: root.theme + label: "Wireless" + CCSwitch { + theme: root.theme + checked: Networking.wifiEnabled + enabled: Networking.wifiHardwareEnabled + onToggled: Networking.wifiEnabled = checked + } + } + SettingsRow { + theme: root.theme + label: root.controlCenter && root.controlCenter.wifiDevice + && root.controlCenter.wifiDevice.networks.values.find(network => network.connected) + ? root.controlCenter.wifiDevice.networks.values.find(network => network.connected).name + : "Not connected" + CCButton { + theme: root.theme + text: "Scan" + onClicked: { + if (root.controlCenter && root.controlCenter.wifiDevice) + root.controlCenter.wifiDevice.scannerEnabled = true; + } + } + } + Repeater { + model: root.controlCenter && root.controlCenter.wifiDevice ? root.controlCenter.wifiDevice.networks : null + delegate: SettingsRow { + required property var modelData + theme: root.theme + label: modelData.name + CCButton { + theme: root.theme + text: modelData.connected ? "Disconnect" : "Connect" + enabled: !modelData.stateChanging + onClicked: modelData.connected ? modelData.disconnect() : root.controlCenter.connectWifi(modelData) + } + } + } + SettingsRow { + visible: root.controlCenter && root.controlCenter.pendingNetwork !== null + theme: root.theme + label: "Password" + TextField { + implicitWidth: 150 + palette.base: root.theme.surfaceVariant + palette.text: root.theme.text + palette.placeholderText: root.theme.mutedText + palette.highlight: root.theme.primary + placeholderText: "Network password" + echoMode: TextInput.Password + text: root.controlCenter ? root.controlCenter.wifiPassword : "" + onTextChanged: if (root.controlCenter) root.controlCenter.wifiPassword = text + onAccepted: if (root.controlCenter) root.controlCenter.submitWifiPassword() + } + CCButton { + theme: root.theme + text: "Join" + enabled: root.controlCenter && root.controlCenter.wifiPassword.length > 0 + onClicked: root.controlCenter.submitWifiPassword() + } + } + } + + SettingsCard { + theme: root.theme + title: "Bluetooth" + description: root.controlCenter && root.controlCenter.bluetoothAdapter ? "Nearby devices" : "Bluetooth unavailable" + Layout.fillWidth: true + + SettingsRow { + theme: root.theme + label: "Bluetooth" + CCSwitch { + theme: root.theme + checked: root.controlCenter && root.controlCenter.bluetoothAdapter + ? root.controlCenter.bluetoothAdapter.enabled : false + enabled: root.controlCenter && root.controlCenter.bluetoothAdapter !== null + onToggled: if (root.controlCenter.bluetoothAdapter) root.controlCenter.bluetoothAdapter.enabled = checked + } + CCButton { + theme: root.theme + text: root.controlCenter && root.controlCenter.bluetoothAdapter && root.controlCenter.bluetoothAdapter.discovering ? "Stop" : "Scan" + enabled: root.controlCenter && root.controlCenter.bluetoothAdapter && root.controlCenter.bluetoothAdapter.enabled + onClicked: if (root.controlCenter.bluetoothAdapter) root.controlCenter.bluetoothAdapter.discovering = !root.controlCenter.bluetoothAdapter.discovering + } + } + Repeater { + model: root.controlCenter && root.controlCenter.bluetoothAdapter ? root.controlCenter.bluetoothAdapter.devices : null + delegate: SettingsRow { + required property var modelData + theme: root.theme + label: modelData.name || modelData.deviceName || "Unknown device" + description: modelData.connected ? "Connected" : (modelData.paired ? "Paired" : "Not paired") + Label { + visible: modelData.batteryAvailable + text: Math.round(modelData.battery * 100) + "%" + color: root.theme.mutedText + } + CCButton { + theme: root.theme + text: modelData.connected ? "Disconnect" : (modelData.paired ? "Connect" : "Pair") + onClicked: { + if (modelData.connected) modelData.disconnect(); + else if (modelData.paired) modelData.connect(); + else modelData.pair(); + } + } + } + } + } + + SettingsCard { + theme: root.theme + title: "Audio" + description: root.controlCenter && root.controlCenter.audioSink + ? (root.controlCenter.audioSink.description || "Default output") : "Audio unavailable" + Layout.fillWidth: true + + SettingsRow { + theme: root.theme + label: "Output volume" + CCButton { + theme: root.theme + text: root.controlCenter && root.controlCenter.audioSink && root.controlCenter.audioSink.audio && root.controlCenter.audioSink.audio.muted ? "Unmute" : "Mute" + enabled: !!(root.controlCenter && root.controlCenter.audioSink && root.controlCenter.audioSink.audio) + onClicked: root.controlCenter.audioSink.audio.muted = !root.controlCenter.audioSink.audio.muted + } + Label { + text: root.controlCenter && root.controlCenter.audioSink && root.controlCenter.audioSink.audio ? Math.round(root.controlCenter.audioSink.audio.volume * 100) + "%" : "--" + color: root.theme.text + } + } + CCSlider { + theme: root.theme + Layout.fillWidth: true + from: 0 + to: 1 + value: root.controlCenter && root.controlCenter.audioSink && root.controlCenter.audioSink.audio ? root.controlCenter.audioSink.audio.volume : 0 + enabled: !!(root.controlCenter && root.controlCenter.audioSink && root.controlCenter.audioSink.audio) + onMoved: if (root.controlCenter.audioSink && root.controlCenter.audioSink.audio) root.controlCenter.audioSink.audio.volume = value + } + Repeater { + model: Pipewire.nodes + delegate: SettingsRow { + required property var modelData + visible: modelData.isSink && !modelData.isStream + theme: root.theme + label: modelData.description || modelData.name + CCButton { + theme: root.theme + text: modelData === root.controlCenter.audioSink ? "Default" : "Use" + enabled: modelData !== root.controlCenter.audioSink + onClicked: Pipewire.preferredDefaultAudioSink = modelData + } + } + } + } + + SettingsCard { + theme: root.theme + title: "Display" + description: "Brightness and power profile" + Layout.fillWidth: true + SettingsRow { + theme: root.theme + label: "Brightness" + description: root.controlCenter && root.controlCenter.brightness > 0 ? root.controlCenter.brightness + "%" : "Unavailable" + } + CCSlider { + theme: root.theme + Layout.fillWidth: true + from: 1 + to: 100 + value: root.controlCenter ? root.controlCenter.brightness : 0 + enabled: root.controlCenter && root.controlCenter.brightness > 0 + onPressedChanged: if (!pressed && root.controlCenter) root.controlCenter.setBrightness(value) + } + SettingsRow { + theme: root.theme + label: "Power profile" + description: PowerProfile.toString(PowerProfiles.profile) + CCButton { theme: root.theme; text: "Saver"; onClicked: PowerProfiles.profile = PowerProfile.PowerSaver } + CCButton { theme: root.theme; text: "Balanced"; accent: true; onClicked: PowerProfiles.profile = PowerProfile.Balanced } + CCButton { theme: root.theme; text: "Performance"; enabled: PowerProfiles.hasPerformanceProfile; onClicked: PowerProfiles.profile = PowerProfile.Performance } + } + } + } +} diff --git a/fedora/quickshell/control-center/Settings.qml b/fedora/quickshell/control-center/Settings.qml index 3d48b27..d84a41c 100644 --- a/fedora/quickshell/control-center/Settings.qml +++ b/fedora/quickshell/control-center/Settings.qml @@ -9,6 +9,39 @@ QtObject { property alias temperatureInterval: values.temperatureInterval property alias temperatureWarning: values.temperatureWarning property alias temperatureCritical: values.temperatureCritical + property alias panelStyle: values.panelStyle + property alias panelWidth: values.panelWidth + property alias panelRadius: values.panelRadius + property alias panelOpacity: values.panelOpacity + property alias panelOpenOnCorner: values.panelOpenOnCorner + property alias notificationBadge: values.notificationBadge + property alias notificationPosition: values.notificationPosition + property alias notificationPopupDuration: values.notificationPopupDuration + property alias barModules: values.barModules + property alias hiddenBarModules: values.hiddenBarModules + + readonly property var defaultBarModules: [ + "weather", "mail", "media", "notifications", "gpu", "cpu", "memory", + "disk", "keyboard", "audio", "calendar", "settings", "tray" + ] + + function visibleBarModules() { + const configured = Array.isArray(root.barModules) ? root.barModules : root.defaultBarModules; + const hidden = Array.isArray(root.hiddenBarModules) ? root.hiddenBarModules : []; + return configured.filter(module => root.defaultBarModules.indexOf(module) >= 0 && hidden.indexOf(module) < 0); + } + + function setBarModules(modules) { + root.barModules = modules.filter(module => root.defaultBarModules.indexOf(module) >= 0); + } + + function setModuleVisible(module, visible) { + let hidden = Array.isArray(root.hiddenBarModules) ? root.hiddenBarModules.slice() : []; + const index = hidden.indexOf(module); + if (visible && index >= 0) hidden.splice(index, 1); + if (!visible && index < 0) hidden.push(module); + root.hiddenBarModules = hidden; + } function setTemperatureWarning(value) { root.temperatureWarning = Math.min(value, root.temperatureCritical - 1); @@ -31,6 +64,19 @@ QtObject { property int temperatureInterval: 2000 property int temperatureWarning: 75 property int temperatureCritical: 90 + property string panelStyle: "cards" + property int panelWidth: 470 + property int panelRadius: 24 + property real panelOpacity: 1.0 + property bool panelOpenOnCorner: false + property bool notificationBadge: true + property string notificationPosition: "top-right" + property int notificationPopupDuration: 5000 + property var barModules: [ + "weather", "mail", "media", "notifications", "gpu", "cpu", "memory", + "disk", "keyboard", "audio", "calendar", "settings", "tray" + ] + property var hiddenBarModules: [] } } } diff --git a/fedora/quickshell/control-center/SettingsCard.qml b/fedora/quickshell/control-center/SettingsCard.qml new file mode 100644 index 0000000..b0d7967 --- /dev/null +++ b/fedora/quickshell/control-center/SettingsCard.qml @@ -0,0 +1,58 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property var theme + property string title: "" + property string description: "" + default property alias contentData: content.data + + implicitHeight: cardColumn.implicitHeight + 24 + + Rectangle { + anchors.fill: parent + radius: root.theme ? root.theme.cardRadius : 14 + color: root.theme ? root.theme.surface : "#181825" + border.color: root.theme ? root.theme.border : "#313244" + border.width: 1 + } + + ColumnLayout { + id: cardColumn + anchors.fill: parent + anchors.margins: 12 + spacing: 8 + + ColumnLayout { + visible: root.title.length > 0 || root.description.length > 0 + Layout.fillWidth: true + spacing: 1 + + Label { + text: root.title + color: root.theme ? root.theme.text : "#cdd6f4" + font.pixelSize: 15 + font.bold: true + Layout.fillWidth: true + elide: Text.ElideRight + } + Label { + visible: root.description.length > 0 + text: root.description + color: root.theme ? root.theme.mutedText : "#9399b2" + font.pixelSize: 11 + Layout.fillWidth: true + wrapMode: Text.Wrap + } + } + + ColumnLayout { + id: content + Layout.fillWidth: true + spacing: 7 + } + } +} diff --git a/fedora/quickshell/control-center/SettingsRow.qml b/fedora/quickshell/control-center/SettingsRow.qml new file mode 100644 index 0000000..ac74355 --- /dev/null +++ b/fedora/quickshell/control-center/SettingsRow.qml @@ -0,0 +1,40 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +RowLayout { + id: root + + property var theme + property string label: "" + property string description: "" + default property alias controlData: controls.data + + Layout.fillWidth: true + spacing: 10 + + ColumnLayout { + Layout.fillWidth: true + spacing: 1 + + Label { + text: root.label + color: root.theme ? root.theme.text : "#cdd6f4" + Layout.fillWidth: true + elide: Text.ElideRight + } + Label { + visible: root.description.length > 0 + text: root.description + color: root.theme ? root.theme.mutedText : "#9399b2" + font.pixelSize: 10 + Layout.fillWidth: true + wrapMode: Text.Wrap + } + } + + RowLayout { + id: controls + spacing: 6 + } +} diff --git a/fedora/quickshell/control-center/StatusPage.qml b/fedora/quickshell/control-center/StatusPage.qml new file mode 100644 index 0000000..480d002 --- /dev/null +++ b/fedora/quickshell/control-center/StatusPage.qml @@ -0,0 +1,91 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property var settings + property var theme + property string cpuTemperature: "--" + property string gpuTemperature: "--" + property string temperatureColorCpu: "#9399b2" + property string temperatureColorGpu: "#9399b2" + implicitHeight: pageColumn.implicitHeight + + ColumnLayout { + id: pageColumn + width: parent.width + spacing: 10 + + SettingsCard { + theme: root.theme + title: "Temperatures" + description: "The existing CPU and GPU readers remain active in the bar service." + Layout.fillWidth: true + SettingsRow { theme: root.theme; label: "CPU temperature"; Label { text: root.cpuTemperature; color: root.temperatureColorCpu } } + SettingsRow { theme: root.theme; label: "GPU temperature"; Label { text: root.gpuTemperature; color: root.temperatureColorGpu } } + } + + SettingsCard { + theme: root.theme + title: "Polling" + description: "Used by the system metrics and temperature readers." + Layout.fillWidth: true + SettingsRow { + theme: root.theme + label: "Refresh interval" + ComboBox { + palette.base: root.theme.surfaceVariant + palette.button: root.theme.surfaceVariant + palette.text: root.theme.text + palette.buttonText: root.theme.text + palette.highlight: root.theme.primary + model: ["1 second", "2 seconds", "5 seconds"] + currentIndex: [1000, 2000, 5000].indexOf(root.settings.temperatureInterval) + onActivated: root.settings.temperatureInterval = [1000, 2000, 5000][currentIndex] + } + } + SettingsRow { + theme: root.theme + label: "Warning / critical" + SpinBox { + palette.base: root.theme.surfaceVariant + palette.text: root.theme.text + palette.button: root.theme.surfaceVariant + palette.buttonText: root.theme.text + from: 40 + to: 99 + value: root.settings.temperatureWarning + editable: true + onValueModified: root.settings.setTemperatureWarning(value) + } + Label { text: "/"; color: root.theme.mutedText } + SpinBox { + palette.base: root.theme.surfaceVariant + palette.text: root.theme.text + palette.button: root.theme.surfaceVariant + palette.buttonText: root.theme.text + from: 41 + to: 110 + value: root.settings.temperatureCritical + editable: true + onValueModified: root.settings.setTemperatureCritical(value) + } + Label { text: "°C"; color: root.theme.mutedText } + } + } + + SettingsCard { + theme: root.theme + title: "Live status" + description: "Weather, keyboard layouts, workspaces and tray filtering keep their current polling paths." + Layout.fillWidth: true + Label { + text: "Screen-sharing tray items remain hidden." + color: root.theme.success + Layout.fillWidth: true + } + } + } +} diff --git a/fedora/quickshell/control-center/UiTokens.qml b/fedora/quickshell/control-center/UiTokens.qml new file mode 100644 index 0000000..4d1e171 --- /dev/null +++ b/fedora/quickshell/control-center/UiTokens.qml @@ -0,0 +1,19 @@ +import QtQuick + +QtObject { + readonly property color background: "#11111b" + readonly property color surface: "#181825" + readonly property color surfaceVariant: "#1e1e2e" + readonly property color surfaceRaised: "#24243a" + readonly property color border: "#313244" + readonly property color primary: "#89b4fa" + readonly property color secondary: "#cba6f7" + readonly property color warning: "#f9e2af" + readonly property color critical: "#f38ba8" + readonly property color success: "#a6e3a1" + readonly property color text: "#cdd6f4" + readonly property color mutedText: "#9399b2" + readonly property color dimText: "#6c7086" + readonly property int cardRadius: 14 + readonly property int controlRadius: 9 +} diff --git a/fedora/quickshell/control-center/shell.qml b/fedora/quickshell/control-center/shell.qml index 0392c44..81ed43f 100644 --- a/fedora/quickshell/control-center/shell.qml +++ b/fedora/quickshell/control-center/shell.qml @@ -15,10 +15,12 @@ Scope { property bool open: false property bool panelVisible: false + property bool contentReady: false property bool mediaOpen: false property bool notificationsOpen: false property bool osdOpen: false property string osdKind: "volume" + property int pageIndex: 0 property int brightness: 0 property var pendingNetwork: null property string wifiPassword: "" @@ -29,9 +31,8 @@ Scope { property alias cpuTemperature: statusBar.temperature property alias gpuTemperature: statusBar.gpuTemperature - Settings { - id: statusSettings - } + Settings { id: statusSettings } + UiTokens { id: uiTheme } Bar { id: statusBar @@ -50,6 +51,7 @@ Scope { Notifications { id: notificationCenter + settings: root.settings open: root.notificationsOpen onDismissed: root.notificationsOpen = false } @@ -74,7 +76,6 @@ Scope { network.connect(); return; } - root.pendingNetwork = network; root.wifiPassword = ""; } @@ -102,20 +103,23 @@ Scope { function temperatureColor(value) { const match = String(value).match(/(\d+)C\s*$/); const temperature = match ? Number(match[1]) : -1; - if (temperature < 0) return "#806d95"; - if (temperature >= root.settings.temperatureCritical) return "#c94f6d"; - if (temperature >= root.settings.temperatureWarning) return "#996315"; - return "#4d7c38"; + if (temperature < 0) return uiTheme.mutedText; + if (temperature >= root.settings.temperatureCritical) return uiTheme.critical; + if (temperature >= root.settings.temperatureWarning) return uiTheme.warning; + return uiTheme.success; } function openPanel() { closePanelTimer.stop(); root.panelVisible = true; + root.contentReady = false; openPanelTimer.restart(); } function closePanel() { openPanelTimer.stop(); + contentReadyTimer.stop(); + root.contentReady = false; root.open = false; closePanelTimer.restart(); } @@ -129,9 +133,17 @@ Scope { id: openPanelTimer interval: 1 repeat: false - onTriggered: root.open = true + onTriggered: { + root.open = true; + contentReadyTimer.restart(); + } + } + Timer { + id: contentReadyTimer + interval: 220 + repeat: false + onTriggered: root.contentReady = true } - Timer { id: closePanelTimer interval: 240 @@ -141,62 +153,22 @@ Scope { IpcHandler { target: "controlcenter" - - function toggle() { - root.togglePanel(); - } - - function open() { - root.openPanel(); - } - - function close() { - root.closePanel(); - } + function toggle() { root.togglePanel(); } + function open() { root.openPanel(); } + function close() { root.closePanel(); } } - IpcHandler { target: "osd" - - function volume_up() { - Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%+"]); - root.showOsd("volume"); - } - - function volume_down() { - Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%-"]); - root.showOsd("volume"); - } - - function volume_mute() { - Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"]); - root.showOsd("volume"); - } - - function mic_mute() { - Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SOURCE@", "toggle"]); - root.showOsd("mic"); - } - - function brightness_up() { - root.adjustBrightness(5); - } - - function brightness_down() { - root.adjustBrightness(-5); - } + function volume_up() { Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%+"]); root.showOsd("volume"); } + function volume_down() { Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%-"]); root.showOsd("volume"); } + function volume_mute() { Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"]); root.showOsd("volume"); } + function mic_mute() { Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SOURCE@", "toggle"]); root.showOsd("mic"); } + function brightness_up() { root.adjustBrightness(5); } + function brightness_down() { root.adjustBrightness(-5); } } + Timer { id: osdTimer; interval: 1400; repeat: false; onTriggered: root.osdOpen = false } - Timer { - id: osdTimer - interval: 1400 - repeat: false - onTriggered: root.osdOpen = false - } - - PwObjectTracker { - objects: [root.audioSink] - } + PwObjectTracker { objects: [root.audioSink] } Process { id: brightnessReader @@ -208,26 +180,18 @@ Scope { } } } - Timer { id: brightnessPoll interval: 3000 repeat: true running: root.open - onTriggered: { - if (!brightnessReader.running) brightnessReader.running = true; - } + onTriggered: if (!brightnessReader.running) brightnessReader.running = true } PanelWindow { visible: root.panelVisible color: "transparent" - anchors { - top: true - bottom: true - left: true - right: true - } + anchors { top: true; bottom: true; left: true; right: true } exclusionMode: ExclusionMode.Ignore WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: root.panelVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None @@ -240,509 +204,133 @@ Scope { Rectangle { id: card - width: Math.min(470, parent.width - 32) + width: Math.min(root.settings.panelWidth, parent.width - 24) height: parent.height anchors.top: parent.top - anchors.topMargin: 0 x: root.open ? parent.width - width - 16 : parent.width + 24 - radius: 20 - color: "#f5f2ea" - border.color: "#d8d0c2" + radius: root.settings.panelRadius + color: Qt.rgba(uiTheme.background.r, uiTheme.background.g, uiTheme.background.b, root.settings.panelOpacity) + border.color: uiTheme.border border.width: 1 z: 1 Behavior on x { - NumberAnimation { - duration: 220 - easing.type: Easing.OutCubic - } + NumberAnimation { duration: 220; easing.type: Easing.OutCubic } } - MouseArea { - anchors.fill: parent - onClicked: mouse.accepted = true - } + MouseArea { anchors.fill: parent; onClicked: mouse.accepted = true } - ColumnLayout { - id: content + RowLayout { anchors.fill: parent - anchors.margins: 18 + anchors.margins: 12 spacing: 12 - RowLayout { - Layout.fillWidth: true - - ColumnLayout { - Layout.fillWidth: true - spacing: 0 - - Label { - text: "Control center" - color: "#252321" - font.pixelSize: 22 - font.bold: true - } - Label { - text: "Niri / QuickShell" - color: "#7c756c" - font.pixelSize: 12 - } - } - - Button { - text: "✕" - ToolTip.visible: hovered - ToolTip.text: "Close" - onClicked: root.closePanel() - } + NavigationRail { + id: navigation + theme: uiTheme + currentIndex: root.pageIndex + Layout.fillHeight: true + onSelected: root.pageIndex = index } - ScrollView { + Rectangle { + Layout.fillHeight: true + Layout.preferredWidth: 1 + color: uiTheme.border + } + + ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true - clip: true + spacing: 10 - ColumnLayout { - width: parent.width - spacing: 10 - - Rectangle { + RowLayout { + Layout.fillWidth: true + ColumnLayout { Layout.fillWidth: true - implicitHeight: wifiColumn.implicitHeight + 24 - radius: 14 - color: "#e5f0ef" - - ColumnLayout { - id: wifiColumn - anchors.fill: parent - anchors.margins: 12 - spacing: 8 - - RowLayout { - Layout.fillWidth: true - Label { - text: "Wi-Fi" - color: "#1c666d" - font.pixelSize: 16 - font.bold: true - Layout.fillWidth: true - } - Label { - text: root.wifiDevice ? (root.wifiDevice.name || "Available") : "Unavailable" - color: "#52777a" - elide: Text.ElideRight - } - Switch { - checked: Networking.wifiEnabled - enabled: Networking.wifiHardwareEnabled - onToggled: Networking.wifiEnabled = checked - } - } - - RowLayout { - visible: root.wifiDevice !== null - Layout.fillWidth: true - Label { - text: root.wifiDevice && root.wifiDevice.networks.values.find(network => network.connected) - ? root.wifiDevice.networks.values.find(network => network.connected).name - : "Not connected" - color: "#252321" - elide: Text.ElideRight - Layout.fillWidth: true - } - Button { - text: "Scan" - onClicked: { - if (root.wifiDevice) root.wifiDevice.scannerEnabled = true; - } - } - } - - Repeater { - model: root.wifiDevice ? root.wifiDevice.networks : null - delegate: RowLayout { - Layout.fillWidth: true - spacing: 8 - - Label { - text: modelData.name - color: "#252321" - elide: Text.ElideRight - Layout.fillWidth: true - } - Label { - text: modelData.connected ? "Connected" : Math.round(modelData.signalStrength) + "%" - color: modelData.connected ? "#1c666d" : "#7c756c" - } - Button { - text: modelData.connected ? "Disconnect" : "Connect" - enabled: !modelData.stateChanging - onClicked: modelData.connected ? modelData.disconnect() : root.connectWifi(modelData) - } - } - } - - RowLayout { - visible: root.pendingNetwork !== null - Layout.fillWidth: true - TextField { - Layout.fillWidth: true - placeholderText: "Password for " + (root.pendingNetwork ? root.pendingNetwork.name : "network") - echoMode: TextInput.Password - text: root.wifiPassword - onTextChanged: root.wifiPassword = text - onAccepted: root.submitWifiPassword() - } - Button { - text: "Join" - enabled: root.wifiPassword.length > 0 - onClicked: root.submitWifiPassword() - } - } + spacing: 0 + Label { + text: navigation.names[root.pageIndex] + color: uiTheme.text + font.pixelSize: 22 + font.bold: true + } + Label { + text: "Control center / Niri" + color: uiTheme.mutedText + font.pixelSize: 11 } } + CCButton { theme: uiTheme; text: "Close"; onClicked: root.closePanel() } + } - Rectangle { - Layout.fillWidth: true - implicitHeight: bluetoothColumn.implicitHeight + 24 - radius: 14 - color: "#eceafd" + ScrollView { + id: pageScroll + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + contentWidth: availableWidth - ColumnLayout { - id: bluetoothColumn - anchors.fill: parent - anchors.margins: 12 - spacing: 8 - - RowLayout { - Layout.fillWidth: true - Label { - text: "Bluetooth" - color: "#55499c" - font.pixelSize: 16 - font.bold: true - Layout.fillWidth: true - } - Switch { - checked: root.bluetoothAdapter ? root.bluetoothAdapter.enabled : false - enabled: root.bluetoothAdapter !== null - onToggled: { - if (root.bluetoothAdapter) root.bluetoothAdapter.enabled = checked; - } - } - Button { - text: root.bluetoothAdapter && root.bluetoothAdapter.discovering ? "Stop scan" : "Scan" - enabled: root.bluetoothAdapter && root.bluetoothAdapter.enabled - onClicked: { - if (root.bluetoothAdapter) root.bluetoothAdapter.discovering = !root.bluetoothAdapter.discovering; - } - } - } - - Repeater { - model: root.bluetoothAdapter ? root.bluetoothAdapter.devices : null - delegate: RowLayout { - Layout.fillWidth: true - spacing: 8 - - ColumnLayout { - Layout.fillWidth: true - spacing: 0 - Label { - text: modelData.name || modelData.deviceName || "Unknown device" - color: "#252321" - elide: Text.ElideRight - Layout.fillWidth: true - } - Label { - text: modelData.connected ? "Connected" : (modelData.paired ? "Paired" : "Not paired") - color: "#7c756c" - font.pixelSize: 11 - } - } - Label { - visible: modelData.batteryAvailable - text: Math.round(modelData.battery * 100) + "%" - color: "#7c756c" - } - Button { - text: modelData.connected ? "Disconnect" : (modelData.paired ? "Connect" : "Pair") - onClicked: { - if (modelData.connected) modelData.disconnect(); - else if (modelData.paired) modelData.connect(); - else modelData.pair(); - } - } - } - } - } - } - - Rectangle { - Layout.fillWidth: true - implicitHeight: audioColumn.implicitHeight + 24 - radius: 14 - color: "#fff1d7" - - ColumnLayout { - id: audioColumn - anchors.fill: parent - anchors.margins: 12 - spacing: 8 - - RowLayout { - Layout.fillWidth: true - Label { - text: "Audio" - color: "#996315" - font.pixelSize: 16 - font.bold: true - Layout.fillWidth: true - } - Label { - text: root.audioSink ? (root.audioSink.description || "Default output") : "Unavailable" - color: "#806a48" - elide: Text.ElideRight - Layout.maximumWidth: 230 - } - } - - RowLayout { - Layout.fillWidth: true - Button { - text: root.audioSink && root.audioSink.audio && root.audioSink.audio.muted ? "Unmute" : "Mute" - enabled: root.audioSink && root.audioSink.audio - onClicked: { - if (root.audioSink && root.audioSink.audio) root.audioSink.audio.muted = !root.audioSink.audio.muted; - } - } - Slider { - Layout.fillWidth: true - from: 0 - to: 1 - value: root.audioSink && root.audioSink.audio ? root.audioSink.audio.volume : 0 - enabled: root.audioSink && root.audioSink.audio - onMoved: { - if (root.audioSink && root.audioSink.audio) root.audioSink.audio.volume = value; - } - } - Label { - text: root.audioSink && root.audioSink.audio ? Math.round(root.audioSink.audio.volume * 100) + "%" : "--" - color: "#806a48" - } - } - - Repeater { - model: Pipewire.nodes - delegate: RowLayout { - visible: modelData.isSink && !modelData.isStream - Layout.fillWidth: true - Label { - text: modelData.description || modelData.name - color: "#252321" - elide: Text.ElideRight - Layout.fillWidth: true - } - Button { - text: modelData === root.audioSink ? "Default" : "Use" - enabled: modelData !== root.audioSink - onClicked: Pipewire.preferredDefaultAudioSink = modelData - } - } - } - } - } - - Rectangle { - Layout.fillWidth: true - implicitHeight: displayColumn.implicitHeight + 24 - radius: 14 - color: "#f8e4dc" - - ColumnLayout { - id: displayColumn - anchors.fill: parent - anchors.margins: 12 - spacing: 8 - - RowLayout { - Layout.fillWidth: true - Label { - text: "Display brightness" - color: "#9e4d39" - font.pixelSize: 16 - font.bold: true - Layout.fillWidth: true - } - Label { - text: root.brightness > 0 ? root.brightness + "%" : "Unavailable" - color: "#8b665b" - } - } - Slider { - Layout.fillWidth: true - from: 1 - to: 100 - value: root.brightness - enabled: root.brightness > 0 - onPressedChanged: { - if (!pressed) root.setBrightness(value); - } - } - } - } - - Rectangle { - Layout.fillWidth: true - implicitHeight: statusColumn.implicitHeight + 24 - radius: 14 - color: "#eee8f7" - - ColumnLayout { - id: statusColumn - anchors.fill: parent - anchors.margins: 12 - spacing: 8 - - RowLayout { - Layout.fillWidth: true - Label { - text: "Status bar" - color: "#684b8f" - font.pixelSize: 16 - font.bold: true - Layout.fillWidth: true - } - Label { - text: "Temperature" - color: "#806d95" - } - } - - RowLayout { - Layout.fillWidth: true - Label { - text: "CPU temperature" - color: "#252321" - Layout.fillWidth: true - } - Label { - text: root.cpuTemperature - color: root.temperatureColor(root.cpuTemperature) - } - } - - RowLayout { - Layout.fillWidth: true - Label { - text: "GPU temperature" - color: "#252321" - Layout.fillWidth: true - } - Label { - text: root.gpuTemperature - color: root.temperatureColor(root.gpuTemperature) - } - } - - RowLayout { - Layout.fillWidth: true - Label { - text: "Refresh interval" - color: "#252321" - Layout.fillWidth: true - } - ComboBox { - model: ["1 second", "2 seconds", "5 seconds"] - currentIndex: [1000, 2000, 5000].indexOf(root.settings.temperatureInterval) - onActivated: root.settings.temperatureInterval = [1000, 2000, 5000][currentIndex] - } - } - - RowLayout { - Layout.fillWidth: true - Label { - text: "Warning / critical" - color: "#252321" - Layout.fillWidth: true - } - SpinBox { - from: 40 - to: 99 - value: root.settings.temperatureWarning - editable: true - onValueModified: root.settings.setTemperatureWarning(value) - } - Label { - text: "/" - color: "#7c756c" - } - SpinBox { - from: 41 - to: 110 - value: root.settings.temperatureCritical - editable: true - onValueModified: root.settings.setTemperatureCritical(value) - } - Label { - text: "°C" - color: "#7c756c" - } - } - } - } - - Rectangle { - Layout.fillWidth: true - implicitHeight: powerColumn.implicitHeight + 24 - radius: 14 - color: "#e8f1df" - - ColumnLayout { - id: powerColumn - anchors.fill: parent - anchors.margins: 12 - spacing: 8 - - RowLayout { - Layout.fillWidth: true - Label { - text: "Power profile" - color: "#4d7c38" - font.pixelSize: 16 - font.bold: true - Layout.fillWidth: true - } - Label { - text: PowerProfile.toString(PowerProfiles.profile) - color: "#66825a" - } - } - - RowLayout { - Layout.fillWidth: true - Button { - text: "Saver" - Layout.fillWidth: true - onClicked: PowerProfiles.profile = PowerProfile.PowerSaver - } - Button { - text: "Balanced" - Layout.fillWidth: true - onClicked: PowerProfiles.profile = PowerProfile.Balanced - } - Button { - text: "Performance" - enabled: PowerProfiles.hasPerformanceProfile - Layout.fillWidth: true - onClicked: PowerProfiles.profile = PowerProfile.Performance - } - } - } + Loader { + id: pageLoader + width: pageScroll.availableWidth + active: root.contentReady + sourceComponent: [quickPage, barPage, panelPage, notificationsPage, statusPage][root.pageIndex] + opacity: root.contentReady ? 1 : 0 + Behavior on opacity { NumberAnimation { duration: 140 } } } } } } } } + + PanelWindow { + visible: root.settings.panelOpenOnCorner && !root.panelVisible + implicitWidth: 30 + implicitHeight: 30 + anchors { top: true; right: true } + color: "transparent" + exclusionMode: ExclusionMode.Ignore + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + WlrLayershell.namespace: "quickshell-control-center-hot-corner" + Rectangle { + anchors.fill: parent + anchors.margins: 4 + radius: 8 + color: uiTheme.primary + opacity: 0.35 + MouseArea { anchors.fill: parent; onClicked: root.openPanel() } + } + } + + Component { + id: quickPage + QuickPage { width: pageLoader.width; controlCenter: root; settings: root.settings; theme: uiTheme } + } + Component { + id: barPage + BarPage { width: pageLoader.width; settings: root.settings; theme: uiTheme; availableModules: root.settings.defaultBarModules } + } + Component { + id: panelPage + PanelPage { width: pageLoader.width; settings: root.settings; theme: uiTheme } + } + Component { + id: notificationsPage + NotificationsPage { width: pageLoader.width; settings: root.settings; theme: uiTheme; notificationCenter: notificationCenter } + } + Component { + id: statusPage + StatusPage { + width: pageLoader.width + settings: root.settings + theme: uiTheme + cpuTemperature: root.cpuTemperature + gpuTemperature: root.gpuTemperature + temperatureColorCpu: root.temperatureColor(root.cpuTemperature) + temperatureColorGpu: root.temperatureColor(root.gpuTemperature) + } + } }