Make PowerPanel look nice

This commit is contained in:
Ly-sec 2025-08-20 20:04:39 +02:00
parent 1fc717f354
commit 6538ef1459
4 changed files with 351 additions and 352 deletions

View file

@ -0,0 +1,347 @@
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
NPanel {
id: root
panelWidth: 420 * scaling
panelHeight: 370 * scaling
panelAnchorCentered: true
// Timer properties
property int timerDuration: 5000 // 5 seconds
property string pendingAction: ""
property bool timerActive: false
property int timeRemaining: 0
// Cancel timer when panel is closing
onClosed: {
cancelTimer()
}
// Timer management
function startTimer(action) {
if (timerActive && pendingAction === action) {
// Second click - execute immediately
executeAction(action)
return
}
pendingAction = action
timeRemaining = timerDuration
timerActive = true
countdownTimer.start()
}
function cancelTimer() {
timerActive = false
pendingAction = ""
timeRemaining = 0
countdownTimer.stop()
}
function executeAction(action) {
// Stop timer but don't reset other properties yet
countdownTimer.stop()
switch(action) {
case "lock":
// Access lockScreen directly like IPCManager does
if (!lockScreen.isLoaded) {
lockScreen.isLoaded = true
}
break
case "suspend":
suspendProcess.running = true
break
case "reboot":
rebootProcess.running = true
break
case "logout":
CompositorService.logout()
break
case "shutdown":
shutdownProcess.running = true
break
}
// Reset timer state and close panel
cancelTimer()
root.close()
}
// System processes
Process {
id: shutdownProcess
command: ["shutdown", "-h", "now"]
running: false
}
Process {
id: rebootProcess
command: ["reboot"]
running: false
}
Process {
id: suspendProcess
command: ["systemctl", "suspend"]
running: false
}
// Countdown timer
Timer {
id: countdownTimer
interval: 100
repeat: true
onTriggered: {
timeRemaining -= interval
if (timeRemaining <= 0) {
executeAction(pendingAction)
}
}
}
panelContent: Rectangle {
color: Color.transparent
ColumnLayout {
anchors.fill: parent
anchors.topMargin: Style.marginL * scaling
anchors.leftMargin: Style.marginL * scaling
anchors.rightMargin: Style.marginL * scaling
anchors.bottomMargin: Style.marginM * scaling
spacing: Style.marginS * scaling
// Header with title and close button
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 0.8 * scaling
NText {
text: timerActive ? `${pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1)} in ${Math.ceil(timeRemaining / 1000)}s` : "Power Options"
font.weight: Style.fontWeightBold
font.pointSize: Style.fontSizeM * scaling
color: timerActive ? Color.mPrimary : Color.mOnSurface
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
}
Item { Layout.fillWidth: true }
NIconButton {
icon: timerActive ? "block" : "close"
tooltipText: timerActive ? "Cancel Timer" : "Close"
Layout.alignment: Qt.AlignVCenter
colorBg: timerActive ? Color.applyOpacity(Color.mError, "20") : Color.transparent
colorFg: timerActive ? Color.mError : Color.mOnSurface
onClicked: {
if (timerActive) {
cancelTimer()
} else {
cancelTimer()
root.close()
}
}
}
}
// Power options
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginM * scaling
// Lock Screen
PowerButton {
Layout.fillWidth: true
icon: "lock_outline"
title: "Lock Screen"
subtitle: "Lock your session"
onClicked: startTimer("lock")
pending: timerActive && pendingAction === "lock"
}
// Suspend
PowerButton {
Layout.fillWidth: true
icon: "bedtime"
title: "Suspend"
subtitle: "Put the system to sleep"
onClicked: startTimer("suspend")
pending: timerActive && pendingAction === "suspend"
}
// Reboot
PowerButton {
Layout.fillWidth: true
icon: "refresh"
title: "Reboot"
subtitle: "Restart the system"
onClicked: startTimer("reboot")
pending: timerActive && pendingAction === "reboot"
}
// Logout
PowerButton {
Layout.fillWidth: true
icon: "exit_to_app"
title: "Logout"
subtitle: "End your session"
onClicked: startTimer("logout")
pending: timerActive && pendingAction === "logout"
}
// Shutdown
PowerButton {
Layout.fillWidth: true
icon: "power_settings_new"
title: "Shutdown"
subtitle: "Turn off the system"
onClicked: startTimer("shutdown")
pending: timerActive && pendingAction === "shutdown"
isShutdown: true
}
}
}
}
// Custom power button component
component PowerButton: Rectangle {
id: buttonRoot
property string icon: ""
property string title: ""
property string subtitle: ""
property bool pending: false
property bool isShutdown: false
signal clicked()
height: Style.baseWidgetSize * 1.5 * scaling
radius: Style.radiusS * scaling
color: {
if (pending) return Color.applyOpacity(Color.mPrimary, "20")
if (mouseArea.containsMouse) return Color.mSurfaceVariant
return Color.transparent
}
border.width: pending ? 2 * scaling : (mouseArea.containsMouse ? 1 * scaling : 0)
border.color: pending ? Color.mPrimary : Color.mOutline
Behavior on color {
ColorAnimation { duration: 150 }
}
Item {
anchors.fill: parent
anchors.margins: Style.marginM * scaling
// Icon on the left
NIcon {
id: iconElement
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: buttonRoot.icon
color: {
if (buttonRoot.pending) return Color.mPrimary
if (buttonRoot.isShutdown && mouseArea.containsMouse) return Color.mError
if (mouseArea.containsMouse) return Color.mPrimary
return Color.mOnSurface
}
font.pointSize: Style.fontSizeL * scaling
width: Style.baseWidgetSize * 0.6 * scaling
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
Behavior on color {
ColorAnimation { duration: 150 }
}
}
// Text content in the middle
Column {
anchors.left: iconElement.right
anchors.right: pendingIndicator.visible ? pendingIndicator.left : parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.marginM * scaling
anchors.rightMargin: pendingIndicator.visible ? Style.marginM * scaling : 0
spacing: 2 * scaling
NText {
text: buttonRoot.title
font.weight: Style.fontWeightMedium
font.pointSize: Style.fontSizeS * scaling
color: {
if (buttonRoot.pending) return Color.mPrimary
if (buttonRoot.isShutdown && mouseArea.containsMouse) return Color.mError
if (mouseArea.containsMouse) return Color.mPrimary
return Color.mOnSurface
}
Behavior on color {
ColorAnimation { duration: 150 }
}
}
NText {
text: {
if (buttonRoot.pending) {
return "Click again to execute immediately"
}
return buttonRoot.subtitle
}
font.pointSize: Style.fontSizeXS * scaling
color: {
if (buttonRoot.pending) return Color.mPrimary
return Color.mOnSurfaceVariant
}
opacity: 0.8
wrapMode: Text.WordWrap
}
}
// Pending indicator on the right
Rectangle {
id: pendingIndicator
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: 24 * scaling
height: 24 * scaling
radius: 12 * scaling
color: Color.mPrimary
visible: buttonRoot.pending
NText {
anchors.centerIn: parent
text: Math.ceil(timeRemaining / 1000)
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: buttonRoot.clicked()
}
}
}

View file

@ -70,7 +70,7 @@ NBox {
icon: "power_settings_new"
tooltipText: "Power Menu"
onClicked: {
powerMenu.open(screen)
powerPanel.open(screen)
sidePanel.close()
}
}

View file

@ -1,349 +0,0 @@
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
import qs.Modules.LockScreen
NPanel {
id: root
panelWidth: 500 * scaling
panelHeight: 300 * scaling
panelAnchorCentered: true
property var entriesCount: 5
property var entryHeight: Style.baseWidgetSize * scaling
panelContent: Rectangle {
color: Color.transparent
// ----------------------------------
// System functions
function logout() {
CompositorService.logout()
}
function suspend() {
suspendProcess.running = true
}
function shutdown() {
shutdownProcess.running = true
}
function reboot() {
rebootProcess.running = true
}
Process {
id: shutdownProcess
command: ["shutdown", "-h", "now"]
running: false
}
Process {
id: rebootProcess
command: ["reboot"]
running: false
}
Process {
id: suspendProcess
command: ["systemctl", "suspend"]
running: false
}
Process {
id: logoutProcess
command: ["loginctl", "terminate-user", Quickshell.env("USER")]
running: false
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginS * scaling
spacing: Style.marginXS * scaling
// --------------
// Lock
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: entryHeight
radius: Style.radiusS * scaling
color: lockButtonArea.containsMouse ? Color.mTertiary : Color.transparent
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.marginM * scaling
anchors.rightMargin: Style.marginM * scaling
Row {
id: lockRow
spacing: Style.marginS * scaling
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
NIcon {
text: "lock_outline"
color: lockButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
NText {
text: "Lock Screen"
color: lockButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
font.pointSize: Style.fontSizeS * scaling
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
}
}
MouseArea {
id: lockButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
// Add acceptedButtons to ensure proper click handling
acceptedButtons: Qt.LeftButton
onClicked: {
// Lock the screen
lockScreen.isLoaded = true
root.close()
}
}
}
// --------------
// Suspend
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: entryHeight
radius: Style.radiusS * scaling
color: suspendButtonArea.containsMouse ? Color.mTertiary : Color.transparent
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.marginM * scaling
anchors.rightMargin: Style.marginM * scaling
Row {
id: suspendRow
spacing: Style.marginS * scaling
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
NIcon {
text: "bedtime"
color: suspendButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
NText {
text: "Suspend"
color: suspendButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
font.pointSize: Style.fontSizeS * scaling
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
}
}
MouseArea {
id: suspendButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onClicked: {
suspend()
root.close()
}
}
}
// --------------
// Reboot
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: entryHeight
radius: Style.radiusS * scaling
color: rebootButtonArea.containsMouse ? Color.mTertiary : Color.transparent
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.marginM * scaling
anchors.rightMargin: Style.marginM * scaling
Row {
id: rebootRow
spacing: Style.marginS * scaling
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
NIcon {
text: "refresh"
color: rebootButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
NText {
text: "Reboot"
color: rebootButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
font.pointSize: Style.fontSizeS * scaling
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
}
}
MouseArea {
id: rebootButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onClicked: {
reboot()
root.close()
}
}
}
// --------------
// Logout
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: entryHeight
radius: Style.radiusS * scaling
color: logoutButtonArea.containsMouse ? Color.mTertiary : Color.transparent
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.marginM * scaling
anchors.rightMargin: Style.marginM * scaling
Row {
id: logoutRow
spacing: Style.marginS * scaling
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
NIcon {
text: "exit_to_app"
color: logoutButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
NText {
text: "Logout"
color: logoutButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
font.pointSize: Style.fontSizeS * scaling
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
}
}
MouseArea {
id: logoutButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onClicked: {
logout()
root.close()
}
}
}
// --------------
// Shutdown
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: entryHeight
radius: Style.radiusS * scaling
color: shutdownButtonArea.containsMouse ? Color.mTertiary : Color.transparent
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.marginM * scaling
anchors.rightMargin: Style.marginM * scaling
Row {
id: shutdownRow
spacing: Style.marginS * scaling
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
NIcon {
text: "power_settings_new"
color: shutdownButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
NText {
text: "Shutdown"
color: shutdownButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
font.pointSize: Style.fontSizeS * scaling
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1 * scaling
}
}
}
MouseArea {
id: shutdownButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onClicked: {
shutdown()
root.close()
}
}
}
}
}
}

View file

@ -22,6 +22,7 @@ import qs.Modules.IPC
import qs.Modules.LockScreen
import qs.Modules.Notification
import qs.Modules.SettingsPanel
import qs.Modules.PowerPanel
import qs.Modules.SidePanel
import qs.Modules.Toast
@ -65,8 +66,8 @@ ShellRoot {
id: lockScreen
}
PowerMenu {
id: powerMenu
PowerPanel {
id: powerPanel
}
ToastManager {}