NSectionEditor: added ability to move Desktop widgets from one screen to another.

This commit is contained in:
Lemmy
2026-01-03 17:32:36 -05:00
parent 3073dd725c
commit c015e8445b
16 changed files with 119 additions and 28 deletions
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Mittlere Sektion",
"move-to-left-section": "Linke Sektion",
"move-to-right-section": "Rechte Sektion",
"move-to-section": "Gehe zu {section}",
"mute": "Stummschalten",
"next-media": "Nächster Titel",
"next-month": "Nächster Monat",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Center section",
"move-to-left-section": "Left section",
"move-to-right-section": "Right section",
"move-to-section": "Move to {section}",
"mute": "Mute",
"next-media": "Next track",
"next-month": "Next month",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Sección central",
"move-to-left-section": "Sección izquierda",
"move-to-right-section": "Sección derecha",
"move-to-section": "Mover a {section}",
"mute": "Silenciar",
"next-media": "Siguiente pista",
"next-month": "Mes siguiente",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Section centrale",
"move-to-left-section": "Section de gauche",
"move-to-right-section": "Section de droite",
"move-to-section": "Aller à {section}",
"mute": "Muet / Couper le son",
"next-media": "Piste suivante",
"next-month": "Mois suivant",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Középső szakasz",
"move-to-left-section": "Bal oldali szakasz",
"move-to-right-section": "Jobb oldali szakasz",
"move-to-section": "Ugrás a(z) {section} szakaszhoz",
"mute": "Némítás",
"next-media": "Következő zene",
"next-month": "Következő hónap",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "中央セクション",
"move-to-left-section": "左セクション",
"move-to-right-section": "右セクション",
"move-to-section": "{section} に移動",
"mute": "ミュート",
"next-media": "次のトラック",
"next-month": "翌月",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Navenda beşê",
"move-to-left-section": "Beşa çepê",
"move-to-right-section": "Beşa rastê",
"move-to-section": "Here'ke {section} here.",
"mute": "Bêdeng bike",
"next-media": "Strana pêş",
"next-month": "Meha bê",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Middelste sectie",
"move-to-left-section": "Linker sectie",
"move-to-right-section": "Rechter sectie",
"move-to-section": "Ga naar {section}",
"mute": "Dempen",
"next-media": "Volgende track",
"next-month": "Volgende maand",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Sekcja środkowa",
"move-to-left-section": "Sekcja lewa",
"move-to-right-section": "Sekcja prawa",
"move-to-section": "Przejdź do {sekcja}",
"mute": "Wycisz",
"next-media": "Następny utwór",
"next-month": "Następny miesiąc",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Seção central",
"move-to-left-section": "Seção esquerda",
"move-to-right-section": "Seção direita",
"move-to-section": "Mover para {section}",
"mute": "Silenciar",
"next-media": "Próxima faixa",
"next-month": "Próximo mês",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Центральная секция",
"move-to-left-section": "Левая секция",
"move-to-right-section": "Правая секция",
"move-to-section": "Перейти к {section}",
"mute": "Отключить звук",
"next-media": "Следующий трек",
"next-month": "Следующий месяц",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Orta bölüm",
"move-to-left-section": "Sol bölüm",
"move-to-right-section": "Sağ bölüm",
"move-to-section": "{section} bölümüne git.",
"mute": "Sessiz",
"next-media": "Sonraki parça",
"next-month": "Sonraki ay",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "Центральна секція",
"move-to-left-section": "Ліва секція",
"move-to-right-section": "Права секція",
"move-to-section": "Перейти до {section}",
"mute": "Вимкнути звук",
"next-media": "Наступний трек",
"next-month": "Наступний місяць",
+1
View File
@@ -2879,6 +2879,7 @@
"move-to-center-section": "中央部分",
"move-to-left-section": "左侧部分",
"move-to-right-section": "右侧部分",
"move-to-section": "移至{section}",
"mute": "静音",
"next-media": "下一首",
"next-month": "下个月",
@@ -67,6 +67,34 @@ ColumnLayout {
Layout.fillWidth: true
}
// Helper to get screen names array
function getScreenNames() {
var names = [];
for (var i = 0; i < Quickshell.screens.length; i++) {
names.push(Quickshell.screens[i].name);
}
return names;
}
// Helper to get screen labels map (just screen names, NSectionEditor adds "Move to" prefix)
function getScreenLabels() {
var labels = {};
for (var i = 0; i < Quickshell.screens.length; i++) {
var screen = Quickshell.screens[i];
labels[screen.name] = screen.name;
}
return labels;
}
// Helper to get screen icons map
function getScreenIcons() {
var icons = {};
for (var i = 0; i < Quickshell.screens.length; i++) {
icons[Quickshell.screens[i].name] = "device-desktop";
}
return icons;
}
// One NSectionEditor per monitor
Repeater {
model: Settings.data.desktopWidgets.enabled ? Quickshell.screens : []
@@ -88,11 +116,14 @@ ColumnLayout {
widgetRegistry: DesktopWidgetRegistry
widgetModel: getWidgetsForMonitor(modelData.name)
availableWidgets: root.availableWidgetsModel
availableSections: [] // No sections to move between - hides move menu items
availableSections: root.getScreenNames()
sectionLabels: root.getScreenLabels()
sectionIcons: root.getScreenIcons()
draggable: false // Desktop widgets are positioned by X,Y, not list order
maxWidgets: -1
onAddWidget: (widgetId, section) => _addWidgetToMonitor(modelData.name, widgetId)
onRemoveWidget: (section, index) => _removeWidgetFromMonitor(modelData.name, index)
onMoveWidget: (fromSection, index, toSection) => _moveWidgetToMonitor(fromSection, index, toSection)
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsForMonitor(modelData.name, index, settings)
onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest)
}
@@ -251,4 +282,30 @@ ColumnLayout {
setWidgetsForMonitor(monitorName, newArray);
}
}
function _moveWidgetToMonitor(fromMonitor, index, toMonitor) {
Logger.i("DesktopWidgetsTab", "Moving widget from", fromMonitor, "index", index, "to", toMonitor);
var sourceWidgets = getWidgetsForMonitor(fromMonitor);
if (index < 0 || index >= sourceWidgets.length) {
Logger.e("DesktopWidgetsTab", "Invalid index", index, "for monitor", fromMonitor);
return;
}
// Get the widget to move
var newSourceWidgets = sourceWidgets.slice();
var widget = Object.assign({}, newSourceWidgets.splice(index, 1)[0]);
// Reset position and scale to ensure widget is accessible on new screen
widget.x = 100;
widget.y = 100;
widget.scale = 1.0;
// Add to destination monitor
var destWidgets = getWidgetsForMonitor(toMonitor).slice();
destWidgets.push(widget);
// Update both monitors
setWidgetsForMonitor(fromMonitor, newSourceWidgets);
setWidgetsForMonitor(toMonitor, destWidgets);
}
}
+47 -27
View File
@@ -15,9 +15,27 @@ NBox {
property var widgetModel: []
property var availableWidgets: []
property var availableSections: ["left", "center", "right"]
property var sectionLabels: ({}) // Map of sectionId -> display label
property var sectionIcons: ({}) // Map of sectionId -> icon name
property int maxWidgets: -1 // -1 means unlimited
property bool draggable: true // Enable/disable drag reordering
// Get display label for a section
function getSectionLabel(sectionId) {
if (sectionLabels && sectionLabels[sectionId]) {
return sectionLabels[sectionId];
}
return sectionId; // Fallback to section ID
}
// Get icon for a section
function getSectionIcon(sectionId) {
if (sectionIcons && sectionIcons[sectionId]) {
return sectionIcons[sectionId];
}
return "arrow-right"; // Default fallback icon
}
property var widgetRegistry: null
property string settingsDialogComponent: "BarWidgetSettingsDialog.qml"
@@ -281,38 +299,40 @@ NBox {
id: contextMenu
parent: Overlay.overlay
width: 240 * Style.uiScaleRatio
model: [
{
"label": I18n.tr("tooltips.move-to-left-section"),
"action": "left",
"icon": "arrow-bar-to-left",
"visible": root.availableSections.includes("left") && root.sectionId !== "left"
},
{
"label": I18n.tr("tooltips.move-to-center-section"),
"action": "center",
"icon": "layout-columns",
"visible": root.availableSections.includes("center") && root.sectionId !== "center"
},
{
"label": I18n.tr("tooltips.move-to-right-section"),
"action": "right",
"icon": "arrow-bar-to-right",
"visible": root.availableSections.includes("right") && root.sectionId !== "right"
},
{
"label": I18n.tr("tooltips.remove-widget"),
"action": "remove",
"icon": "trash",
"visible": true
model: {
var items = [];
// Add move options for each available section (except current)
for (var i = 0; i < root.availableSections.length; i++) {
var section = root.availableSections[i];
if (section !== root.sectionId) {
var label = root.getSectionLabel(section);
// Capitalize first letter
var capitalizedLabel = label.charAt(0).toUpperCase() + label.slice(1);
items.push({
"label": I18n.tr("tooltips.move-to-section", {
"section": capitalizedLabel
}),
"action": section,
"icon": root.getSectionIcon(section),
"visible": true
});
}
}
]
// Add remove option
items.push({
"label": I18n.tr("tooltips.remove-widget"),
"action": "remove",
"icon": "trash",
"visible": true
});
return items;
}
onTriggered: action => {
if (action === "remove") {
root.removeWidget(root.sectionId, index);
root.removeWidget(root.sectionId, widgetItem.index);
} else {
root.moveWidget(root.sectionId, index, action);
root.moveWidget(root.sectionId, widgetItem.index, action);
}
}
}