Files
noctalia-shell/Modules/Panels/Settings/Tabs/SessionMenuTab.qml
ItsLemmy 1ff64efc64 autofmt
2025-11-22 16:50:09 -05:00

455 lines
14 KiB
QML

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": "hibernate",
"text": I18n.tr("session-menu.hibernate"),
"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
}
]
function saveEntries() {
var toSave = [];
for (var i = 0; i < entriesModel.length; i++) {
toSave.push({
"action": entriesModel[i].id,
"enabled": entriesModel[i].enabled,
"countdownEnabled": entriesModel[i].countdownEnabled !== undefined ? entriesModel[i].countdownEnabled : true
});
}
Settings.data.sessionMenu.powerOptions = toSave;
}
function updateEntry(idx, properties) {
var newModel = entriesModel.slice();
newModel[idx] = Object.assign({}, newModel[idx], properties);
entriesModel = newModel;
saveEntries();
}
function reorderEntries(fromIndex, toIndex) {
var newModel = entriesModel.slice();
var item = newModel.splice(fromIndex, 1)[0];
newModel.splice(toIndex, 0, item);
entriesModel = newModel;
saveEntries();
}
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;
// Default countdownEnabled to true for backward compatibility
entry.countdownEnabled = settingEntry.countdownEnabled !== undefined ? settingEntry.countdownEnabled : true;
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];
// Default countdownEnabled to true for new entries
entry.countdownEnabled = true;
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.marginM
Layout.fillWidth: true
NHeader {
label: I18n.tr("settings.session-menu.entries.section.label")
description: I18n.tr("settings.session-menu.entries.section.description")
}
// List of items
Item {
Layout.fillWidth: true
implicitHeight: listView.contentHeight
ListView {
id: listView
anchors.fill: parent
spacing: Style.marginS
interactive: false
clip: true
model: entriesModel
delegate: Item {
id: delegateItem
width: listView.width
height: contentRow.height
required property int index
required property var modelData
property bool dragging: false
property int dragStartY: 0
property int dragStartIndex: -1
property int dragTargetIndex: -1
Rectangle {
anchors.fill: parent
radius: Style.radiusM
color: delegateItem.dragging ? Color.mSurfaceVariant : Color.transparent
border.color: delegateItem.dragging ? Color.mOutline : Color.transparent
border.width: Style.borderS
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
RowLayout {
id: contentRow
width: parent.width
spacing: Style.marginS
// Drag handle
Rectangle {
Layout.preferredWidth: Style.baseWidgetSize * 0.7
Layout.preferredHeight: Style.baseWidgetSize * 0.7
Layout.alignment: Qt.AlignVCenter
radius: Style.radiusXS
color: dragHandleMouseArea.containsMouse ? Color.mSurfaceVariant : Color.transparent
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
ColumnLayout {
anchors.centerIn: parent
spacing: 2
Repeater {
model: 3
Rectangle {
Layout.preferredWidth: Style.baseWidgetSize * 0.28
Layout.preferredHeight: 2
radius: 1
color: Color.mOutline
}
}
}
MouseArea {
id: dragHandleMouseArea
anchors.fill: parent
cursorShape: Qt.SizeVerCursor
hoverEnabled: true
preventStealing: false
z: 1000
onPressed: mouse => {
delegateItem.dragStartIndex = delegateItem.index;
delegateItem.dragTargetIndex = delegateItem.index;
delegateItem.dragStartY = delegateItem.y;
delegateItem.dragging = true;
delegateItem.z = 999;
preventStealing = true;
}
onPositionChanged: mouse => {
if (delegateItem.dragging) {
var dy = mouse.y - height / 2;
var newY = delegateItem.y + dy;
newY = Math.max(0, Math.min(newY, listView.contentHeight - delegateItem.height));
delegateItem.y = newY;
var targetIndex = Math.floor((newY + delegateItem.height / 2) / (delegateItem.height + Style.marginS));
targetIndex = Math.max(0, Math.min(targetIndex, listView.count - 1));
delegateItem.dragTargetIndex = targetIndex;
}
}
onReleased: {
preventStealing = false;
if (delegateItem.dragStartIndex !== -1 && delegateItem.dragTargetIndex !== -1 && delegateItem.dragStartIndex !== delegateItem.dragTargetIndex) {
root.reorderEntries(delegateItem.dragStartIndex, delegateItem.dragTargetIndex);
}
delegateItem.dragging = false;
delegateItem.dragStartIndex = -1;
delegateItem.dragTargetIndex = -1;
delegateItem.z = 0;
}
onCanceled: {
preventStealing = false;
delegateItem.dragging = false;
delegateItem.dragStartIndex = -1;
delegateItem.dragTargetIndex = -1;
delegateItem.z = 0;
}
}
}
// Enable checkbox
Rectangle {
Layout.preferredWidth: Style.baseWidgetSize * 0.7
Layout.preferredHeight: Style.baseWidgetSize * 0.7
Layout.alignment: Qt.AlignVCenter
radius: Style.radiusXS
color: modelData.enabled ? Color.mPrimary : Color.mSurface
border.color: Color.mOutline
border.width: Style.borderS
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
NIcon {
visible: modelData.enabled
anchors.centerIn: parent
anchors.horizontalCenterOffset: -1
icon: "check"
color: Color.mOnPrimary
pointSize: Math.max(Style.fontSizeXS, Style.baseWidgetSize * 0.35)
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
root.updateEntry(index, {
"enabled": !modelData.enabled
});
}
}
}
// Label
NText {
Layout.fillWidth: true
text: modelData.text
color: Color.mOnSurface
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
// Countdown toggle with icon (only shown when global countdown is enabled)
RowLayout {
visible: Settings.data.sessionMenu.enableCountdown
spacing: Style.marginXS
Layout.alignment: Qt.AlignVCenter
NIcon {
icon: "clock"
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeS
}
NToggle {
checked: modelData.countdownEnabled !== undefined ? modelData.countdownEnabled : true
onToggled: function (checked) {
root.updateEntry(delegateItem.index, {
"countdownEnabled": checked
});
}
}
}
}
// Position binding for non-dragging state
y: {
if (delegateItem.dragging) {
return delegateItem.y;
}
var draggedIndex = -1;
var targetIndex = -1;
for (var i = 0; i < listView.count; i++) {
var item = listView.itemAtIndex(i);
if (item && item.dragging) {
draggedIndex = item.dragStartIndex;
targetIndex = item.dragTargetIndex;
break;
}
}
if (draggedIndex !== -1 && targetIndex !== -1 && draggedIndex !== targetIndex) {
var currentIndex = delegateItem.index;
if (draggedIndex < targetIndex) {
if (currentIndex > draggedIndex && currentIndex <= targetIndex) {
return (currentIndex - 1) * (delegateItem.height + Style.marginS);
}
} else {
if (currentIndex >= targetIndex && currentIndex < draggedIndex) {
return (currentIndex + 1) * (delegateItem.height + Style.marginS);
}
}
}
return delegateItem.index * (delegateItem.height + Style.marginS);
}
Behavior on y {
enabled: !delegateItem.dragging
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutQuad
}
}
}
}
}
}
}