Refactor: Panels and Bar background are now drawn separately with Shapes.

This commit is contained in:
ItsLemmy
2025-11-06 10:41:48 -05:00
parent 76a182a90c
commit e29c6ee1a6
109 changed files with 2381 additions and 2192 deletions
+441
View File
@@ -0,0 +1,441 @@
import QtQuick
import QtQuick.Controls
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.MainScreen
SmartPanel {
id: root
preferredWidth: 400 * Style.uiScaleRatio
preferredHeight: 340 * Style.uiScaleRatio
panelAnchorHorizontalCenter: true
panelAnchorVerticalCenter: true
// Timer properties
property int timerDuration: 9000 // 9 seconds
property string pendingAction: ""
property bool timerActive: false
property int timeRemaining: 0
// Navigation properties
property int selectedIndex: 0
readonly property var powerOptions: [{
"action": "lock",
"icon": "lock",
"title": I18n.tr("session-menu.lock")
}, {
"action": "suspend",
"icon": "suspend",
"title": I18n.tr("session-menu.suspend")
}, {
"action": "reboot",
"icon": "reboot",
"title": I18n.tr("session-menu.reboot")
}, {
"action": "logout",
"icon": "logout",
"title": I18n.tr("session-menu.logout")
}, {
"action": "shutdown",
"icon": "shutdown",
"title": I18n.tr("session-menu.shutdown"),
"isShutdown": true
}]
// Lifecycle handlers
onOpened: {
selectedIndex = 0
}
onClosed: {
cancelTimer()
selectedIndex = 0
}
// 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 via PanelService
if (PanelService.lockScreen && !PanelService.lockScreen.active) {
PanelService.lockScreen.active = true
}
break
case "suspend":
// Check if we should lock before suspending
if (Settings.data.general.lockOnSuspend) {
CompositorService.lockAndSuspend()
} else {
CompositorService.suspend()
}
break
case "reboot":
CompositorService.reboot()
break
case "logout":
CompositorService.logout()
break
case "shutdown":
CompositorService.shutdown()
break
}
// Reset timer state and close panel
cancelTimer()
root.close()
}
// Navigation functions
function selectNextWrapped() {
if (powerOptions.length > 0) {
selectedIndex = (selectedIndex + 1) % powerOptions.length
}
}
function selectPreviousWrapped() {
if (powerOptions.length > 0) {
selectedIndex = (((selectedIndex - 1) % powerOptions.length) + powerOptions.length) % powerOptions.length
}
}
function selectFirst() {
selectedIndex = 0
}
function selectLast() {
if (powerOptions.length > 0) {
selectedIndex = powerOptions.length - 1
} else {
selectedIndex = 0
}
}
function activate() {
if (powerOptions.length > 0 && powerOptions[selectedIndex]) {
const option = powerOptions[selectedIndex]
startTimer(option.action)
}
}
// Override keyboard handlers from SmartPanel
function onEscapePressed() {
if (timerActive) {
cancelTimer()
} else {
cancelTimer()
close()
}
}
function onTabPressed() {
selectNextWrapped()
}
function onShiftTabPressed() {
selectPreviousWrapped()
}
function onUpPressed() {
selectPreviousWrapped()
}
function onDownPressed() {
selectNextWrapped()
}
function onReturnPressed() {
activate()
}
function onHomePressed() {
selectFirst()
}
function onEndPressed() {
selectLast()
}
function onCtrlJPressed() {
selectNextWrapped()
}
function onCtrlKPressed() {
selectPreviousWrapped()
}
// Countdown timer
Timer {
id: countdownTimer
interval: 100
repeat: true
onTriggered: {
timeRemaining -= interval
if (timeRemaining <= 0) {
executeAction(pendingAction)
}
}
}
panelContent: Rectangle {
id: ui
color: Color.transparent
// Navigation functions
function selectFirst() {
root.selectFirst()
}
function selectLast() {
root.selectLast()
}
function selectNextWrapped() {
root.selectNextWrapped()
}
function selectPreviousWrapped() {
root.selectPreviousWrapped()
}
function activate() {
root.activate()
}
NBox {
anchors.fill: parent
anchors.margins: Style.marginL
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginL
spacing: Style.marginL
// Header with title and close button
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 0.6
NText {
text: timerActive ? I18n.tr("session-menu.action-in-seconds", {
"action": I18n.tr("session-menu." + pendingAction),
"seconds": Math.ceil(timeRemaining / 1000)
}) : I18n.tr("session-menu.title")
font.weight: Style.fontWeightBold
pointSize: Style.fontSizeL
color: timerActive ? Color.mPrimary : Color.mOnSurface
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
}
Item {
Layout.fillWidth: true
}
NIconButton {
icon: timerActive ? "stop" : "close"
tooltipText: timerActive ? I18n.tr("tooltips.cancel-timer") : I18n.tr("tooltips.close")
Layout.alignment: Qt.AlignVCenter
baseSize: Style.baseWidgetSize * 0.7
colorBg: timerActive ? Qt.alpha(Color.mError, 0.08) : Color.transparent
colorFg: timerActive ? Color.mError : Color.mOnSurface
onClicked: {
if (timerActive) {
cancelTimer()
} else {
cancelTimer()
root.close()
}
}
}
}
NDivider {
Layout.fillWidth: true
}
// Power options
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginS
Repeater {
model: powerOptions
delegate: PowerButton {
Layout.fillWidth: true
icon: modelData.icon
title: modelData.title
isShutdown: modelData.isShutdown || false
isSelected: index === selectedIndex
onClicked: {
selectedIndex = index
startTimer(modelData.action)
}
pending: timerActive && pendingAction === modelData.action
}
}
}
}
}
}
// Custom power button component
component PowerButton: Rectangle {
id: buttonRoot
property string icon: ""
property string title: ""
property bool pending: false
property bool isShutdown: false
property bool isSelected: false
signal clicked
height: Style.baseWidgetSize * 1.3 * Style.uiScaleRatio
radius: Style.radiusS
color: {
if (pending) {
return Qt.alpha(Color.mPrimary, 0.08)
}
if (isSelected || mouseArea.containsMouse) {
return Color.mHover
}
return Color.transparent
}
border.width: pending ? Math.max(Style.borderM) : 0
border.color: pending ? Color.mPrimary : Color.mOutline
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.OutCirc
}
}
Item {
anchors.fill: parent
anchors.margins: Style.marginM
// Icon on the left
NIcon {
id: iconElement
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
icon: buttonRoot.icon
color: {
if (buttonRoot.pending)
return Color.mPrimary
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
return Color.mError
if (buttonRoot.isSelected || mouseArea.containsMouse)
return Color.mOnHover
return Color.mOnSurface
}
pointSize: Style.fontSizeXXL
width: Style.baseWidgetSize * 0.5
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.OutCirc
}
}
}
// Text content in the middle
ColumnLayout {
anchors.left: iconElement.right
anchors.right: pendingIndicator.visible ? pendingIndicator.left : parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.marginL
anchors.rightMargin: pendingIndicator.visible ? Style.marginM : 0
spacing: 0
NText {
text: buttonRoot.title
font.weight: Style.fontWeightMedium
pointSize: Style.fontSizeM
color: {
if (buttonRoot.pending)
return Color.mPrimary
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
return Color.mError
if (buttonRoot.isSelected || mouseArea.containsMouse)
return Color.mOnHover
return Color.mOnSurface
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.OutCirc
}
}
}
}
// Pending indicator on the right
Rectangle {
id: pendingIndicator
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: 20
height: 20
radius: width * 0.5
color: Color.mPrimary
visible: buttonRoot.pending
NText {
anchors.centerIn: parent
text: Math.ceil(timeRemaining / 1000)
pointSize: Style.fontSizeS
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: buttonRoot.clicked()
}
}
}