SessionMenuTab: added tab & SessionMenu settings

SessionMenu: hook up settings, update height calculation
i18n: add translations
This commit is contained in:
Ly-sec
2025-11-12 19:46:19 +01:00
parent 4f6ed4335a
commit dbedf6c25c
16 changed files with 675 additions and 19 deletions
+75 -19
View File
@@ -16,41 +16,89 @@ SmartPanel {
id: root
preferredWidth: Math.round(400 * Style.uiScaleRatio)
preferredHeight: Math.round(340 * Style.uiScaleRatio)
preferredHeight: {
var headerHeight = Settings.data.sessionMenu.showHeader ? Style.baseWidgetSize * 0.6 : 0
panelAnchorHorizontalCenter: true
panelAnchorVerticalCenter: true
var dividerHeight = Settings.data.sessionMenu.showHeader ? Style.marginS : 0
var buttonHeight = Style.baseWidgetSize * 1.3 * Style.uiScaleRatio
var buttonSpacing = Style.marginS
var enabledCount = powerOptions.length
var headerSpacing = Settings.data.sessionMenu.showHeader ? (Style.marginL * 2) : 0
var baseHeight = (Style.marginL * 4) + headerHeight + dividerHeight + headerSpacing
var buttonsHeight = enabledCount > 0 ? (buttonHeight * enabledCount) + (buttonSpacing * (enabledCount - 1)) : 0
return Math.round(baseHeight + buttonsHeight)
}
// Positioning
readonly property string panelPosition: Settings.data.sessionMenu.position
panelAnchorHorizontalCenter: panelPosition === "center" || panelPosition.endsWith("_center")
panelAnchorVerticalCenter: panelPosition === "center"
panelAnchorLeft: panelPosition !== "center" && panelPosition.endsWith("_left")
panelAnchorRight: panelPosition !== "center" && panelPosition.endsWith("_right")
panelAnchorBottom: panelPosition.startsWith("bottom_")
panelAnchorTop: panelPosition.startsWith("top_")
// Timer properties
property int timerDuration: 9000 // 9 seconds
readonly property int timerDuration: Settings.data.sessionMenu.countdownDuration
property string pendingAction: ""
property bool timerActive: false
property int timeRemaining: 0
// Navigation properties
property int selectedIndex: 0
readonly property var powerOptions: [{
"action": "lock",
// Action metadata mapping
readonly property var actionMetadata: {
"lock": {
"icon": "lock",
"title": I18n.tr("session-menu.lock")
}, {
"action": "suspend",
"title": I18n.tr("session-menu.lock"),
"isShutdown": false
},
"suspend": {
"icon": "suspend",
"title": I18n.tr("session-menu.suspend")
}, {
"action": "reboot",
"title": I18n.tr("session-menu.suspend"),
"isShutdown": false
},
"reboot": {
"icon": "reboot",
"title": I18n.tr("session-menu.reboot")
}, {
"action": "logout",
"title": I18n.tr("session-menu.reboot"),
"isShutdown": false
},
"logout": {
"icon": "logout",
"title": I18n.tr("session-menu.logout")
}, {
"action": "shutdown",
"title": I18n.tr("session-menu.logout"),
"isShutdown": false
},
"shutdown": {
"icon": "shutdown",
"title": I18n.tr("session-menu.shutdown"),
"isShutdown": true
}]
}
}
// Build powerOptions from settings, filtering enabled ones and adding metadata
readonly property var powerOptions: (function () {
var options = []
var settingsOptions = Settings.data.sessionMenu.powerOptions || []
for (var i = 0; i < settingsOptions.length; i++) {
var settingOption = settingsOptions[i]
if (settingOption.enabled && actionMetadata[settingOption.action]) {
var metadata = actionMetadata[settingOption.action]
options.push({
"action": settingOption.action,
"icon": metadata.icon,
"title": metadata.title,
"isShutdown": metadata.isShutdown
})
}
}
return options
})()
// Lifecycle handlers
onOpened: {
@@ -64,6 +112,12 @@ SmartPanel {
// Timer management
function startTimer(action) {
// If countdown is disabled, execute immediately
if (!Settings.data.sessionMenu.enableCountdown) {
executeAction(action)
return
}
if (timerActive && pendingAction === action) {
// Second click - execute immediately
executeAction(action)
@@ -245,6 +299,7 @@ SmartPanel {
// Header with title and close button
RowLayout {
visible: Settings.data.sessionMenu.showHeader
Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 0.6
@@ -283,6 +338,7 @@ SmartPanel {
}
NDivider {
visible: Settings.data.sessionMenu.showHeader
Layout.fillWidth: true
}
+10
View File
@@ -76,6 +76,7 @@ SmartPanel {
Network,
Notifications,
ScreenRecorder,
SessionMenu,
UserInterface,
Wallpaper
}
@@ -161,6 +162,10 @@ SmartPanel {
id: lockScreenTab
LockScreenTab {}
}
Component {
id: sessionMenuTab
SessionMenuTab {}
}
// Order *DOES* matter
function updateTabsModel() {
@@ -249,6 +254,11 @@ SmartPanel {
"label": "settings.hooks.title",
"icon": "settings-hooks",
"source": hooksTab
}, {
"id": SettingsPanel.Tab.SessionMenu,
"label": "settings.session-menu.title",
"icon": "settings-session-menu",
"source": sessionMenuTab
}, {
"id": SettingsPanel.Tab.About,
"label": "settings.about.title",
@@ -0,0 +1,227 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.UI
import qs.Widgets
ColumnLayout {
id: root
spacing: Style.marginL
property list<var> entriesModel: []
property list<var> entriesDefault: [{
"id": "lock",
"text": I18n.tr("session-menu.lock"),
"enabled": true,
"required": false
}, {
"id": "suspend",
"text": I18n.tr("session-menu.suspend"),
"enabled": true,
"required": false
}, {
"id": "reboot",
"text": I18n.tr("session-menu.reboot"),
"enabled": true,
"required": false
}, {
"id": "logout",
"text": I18n.tr("session-menu.logout"),
"enabled": true,
"required": false
}, {
"id": "shutdown",
"text": I18n.tr("session-menu.shutdown"),
"enabled": true,
"required": false
}]
// Handler for drag start - disables panel background clicks
function handleDragStart() {
var currentScreen = Quickshell.screens && Quickshell.screens.length > 0 ? Quickshell.screens[0] : null
if (currentScreen) {
var panel = PanelService.getPanel("settingsPanel", currentScreen)
if (panel && panel.disableBackgroundClick) {
panel.disableBackgroundClick()
}
}
}
// Handler for drag end - re-enables panel background clicks
function handleDragEnd() {
var currentScreen = Quickshell.screens && Quickshell.screens.length > 0 ? Quickshell.screens[0] : null
if (currentScreen) {
var panel = PanelService.getPanel("settingsPanel", currentScreen)
if (panel && panel.enableBackgroundClick) {
panel.enableBackgroundClick()
}
}
}
function saveEntries() {
var toSave = []
for (var i = 0; i < entriesModel.length; i++) {
toSave.push({
"action": entriesModel[i].id,
"enabled": entriesModel[i].enabled
})
}
Settings.data.sessionMenu.powerOptions = toSave
}
Component.onCompleted: {
entriesModel = []
// Add the entries available in settings
for (var i = 0; i < Settings.data.sessionMenu.powerOptions.length; i++) {
const settingEntry = Settings.data.sessionMenu.powerOptions[i]
for (var j = 0; j < entriesDefault.length; j++) {
if (settingEntry.action === entriesDefault[j].id) {
var entry = entriesDefault[j]
entry.enabled = settingEntry.enabled
entriesModel.push(entry)
}
}
}
// Add any missing entries from default
for (var i = 0; i < entriesDefault.length; i++) {
var found = false
for (var j = 0; j < entriesModel.length; j++) {
if (entriesModel[j].id === entriesDefault[i].id) {
found = true
break
}
}
if (!found) {
var entry = entriesDefault[i]
entriesModel.push(entry)
}
}
saveEntries()
}
NHeader {
label: I18n.tr("settings.session-menu.general.section.label")
description: I18n.tr("settings.session-menu.general.section.description")
}
NComboBox {
label: I18n.tr("settings.session-menu.position.label")
description: I18n.tr("settings.session-menu.position.description")
Layout.fillWidth: true
model: [{
"key": "center",
"name": I18n.tr("options.control-center.position.center")
}, {
"key": "top_center",
"name": I18n.tr("options.control-center.position.top_center")
}, {
"key": "top_left",
"name": I18n.tr("options.control-center.position.top_left")
}, {
"key": "top_right",
"name": I18n.tr("options.control-center.position.top_right")
}, {
"key": "bottom_center",
"name": I18n.tr("options.control-center.position.bottom_center")
}, {
"key": "bottom_left",
"name": I18n.tr("options.control-center.position.bottom_left")
}, {
"key": "bottom_right",
"name": I18n.tr("options.control-center.position.bottom_right")
}]
currentKey: Settings.data.sessionMenu.position
onSelected: function (key) {
Settings.data.sessionMenu.position = key
}
}
NToggle {
Layout.fillWidth: true
label: I18n.tr("settings.session-menu.show-header.label")
description: I18n.tr("settings.session-menu.show-header.description")
checked: Settings.data.sessionMenu.showHeader
onToggled: checked => Settings.data.sessionMenu.showHeader = checked
}
NToggle {
Layout.fillWidth: true
label: I18n.tr("settings.session-menu.enable-countdown.label")
description: I18n.tr("settings.session-menu.enable-countdown.description")
checked: Settings.data.sessionMenu.enableCountdown
onToggled: checked => Settings.data.sessionMenu.enableCountdown = checked
}
ColumnLayout {
visible: Settings.data.sessionMenu.enableCountdown
spacing: Style.marginXXS
Layout.fillWidth: true
NLabel {
label: I18n.tr("settings.session-menu.countdown-duration.label")
description: I18n.tr("settings.session-menu.countdown-duration.description")
}
NValueSlider {
Layout.fillWidth: true
from: 1000
to: 30000
stepSize: 1000
value: Settings.data.sessionMenu.countdownDuration
onMoved: value => Settings.data.sessionMenu.countdownDuration = value
text: Math.round(Settings.data.sessionMenu.countdownDuration / 1000) + "s"
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL
Layout.bottomMargin: Style.marginL
}
// Entries Management Section
ColumnLayout {
spacing: Style.marginXXS
Layout.fillWidth: true
NHeader {
label: I18n.tr("settings.session-menu.entries.section.label")
description: I18n.tr("settings.session-menu.entries.section.description")
}
NReorderCheckboxes {
Layout.fillWidth: true
model: entriesModel
disabledIds: []
onDragPotentialStarted: {
root.handleDragStart()
}
onDragPotentialEnded: {
root.handleDragEnd()
}
onItemToggled: function (index, enabled) {
var newModel = entriesModel.slice()
newModel[index] = Object.assign({}, newModel[index], {
"enabled": enabled
})
entriesModel = newModel
saveEntries()
}
onItemsReordered: function (fromIndex, toIndex) {
var newModel = entriesModel.slice()
var item = newModel.splice(fromIndex, 1)[0]
newModel.splice(toIndex, 0, item)
entriesModel = newModel
saveEntries()
}
}
}
}