mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2025-12-06 06:36:15 +00:00
455 lines
14 KiB
QML
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|