Merge branch 'desktop-widgets'

This commit is contained in:
Lemmy
2025-12-21 19:23:08 -05:00
21 changed files with 731 additions and 240 deletions
+9 -6
View File
@@ -527,6 +527,7 @@
"close-app": "{app} schließen",
"connect-vpn": "Mit {name} verbinden",
"cycle-visualizer": "Zyklus-Visualisierer",
"delete": "Löschen",
"disable-bluetooth": "Bluetooth deaktivieren",
"disable-dnd": "'Nicht stören' deaktivieren",
"disable-wifi": "WLAN deaktivieren",
@@ -1407,16 +1408,18 @@
}
},
"edit-mode": {
"add-widget": "Widget hinzufügen",
"button": {
"label": "Bearbeitungsmodus aktivieren"
},
"controls-explanation": "Linke Maustaste: Widget verschieben\nRechte Maustaste: Widget Größe ändern",
"controls-explanation": "Linksklick & Ziehen: Widget verschieben oder Größe ändern.\nRechtsklick: Kontextmenü-Optionen öffnen.",
"description": "Aktiviere den Bearbeitungsmodus, um Desktop-Widgets zu verschieben und neu zu positionieren. Im aktivierten Zustand zeigen Widgets eine Ziehumrandung und können neu positioniert werden.",
"exit-button": "Bearbeitungsmodus verlassen",
"grid-snap": {
"label": "Raster einrasten"
},
"label": "Bearbeitungsmodus"
"label": "Bearbeitungsmodus",
"open-settings": "Einstellungen öffnen"
},
"enabled": {
"description": "Desktop-Widgets vollständig aktivieren oder deaktivieren.",
@@ -1758,6 +1761,10 @@
"description": "Elemente in einem Raster statt in einer Liste anzeigen.",
"label": "Rasteransicht"
},
"icon-mode": {
"description": "Verwende native System-Icons anstelle von Tabler-Icons.",
"label": "Native Icons verwenden"
},
"position": {
"description": "Wählen Sie, wo das Starter-Panel erscheint.",
"label": "Position"
@@ -1774,10 +1781,6 @@
"description": "Wenn aktiviert, erscheinen häufig gestartete Apps zuerst in der Liste.",
"label": "Nach Häufigkeit sortieren"
},
"icon-mode": {
"description": "Verwende native System-Icons anstelle von Tabler-Icons.",
"label": "Native Icons verwenden"
},
"terminal-command": {
"description": "Befehl zum Starten eines Terminals. Z.B. 'kitty -e' oder 'gnome-terminal --'.",
"label": "Terminalbefehl"
+9 -6
View File
@@ -527,6 +527,7 @@
"close-app": "Close {app}",
"connect-vpn": "Connect to {name}",
"cycle-visualizer": "Cycle visualizer",
"delete": "Delete",
"disable-bluetooth": "Disable Bluetooth",
"disable-dnd": "Disable Do Not Disturb",
"disable-wifi": "Disable Wi-Fi",
@@ -1407,16 +1408,18 @@
}
},
"edit-mode": {
"add-widget": "Add widget",
"button": {
"label": "Enter edit mode"
},
"controls-explanation": "Left Mouse Button: Move widget\nRight Mouse Button: Resize widget",
"controls-explanation": "Left-click & drag: Move or resize the widget.\nRight-click: Open the context menu options.",
"description": "Enable edit mode to move and reposition desktop widgets. When enabled, widgets show a drag outline and can be repositioned.",
"exit-button": "Exit edit mode",
"grid-snap": {
"label": "Grid snap"
},
"label": "Edit mode"
"label": "Edit mode",
"open-settings": "Open settings"
},
"enabled": {
"description": "Enable or disable desktop widgets entirely.",
@@ -1758,6 +1761,10 @@
"description": "Display items in a grid layout instead of a list.",
"label": "Grid view"
},
"icon-mode": {
"description": "Use native system icons instead of Tabler icons.",
"label": "Use native icons"
},
"position": {
"description": "Choose where the launcher panel appears.",
"label": "Position"
@@ -1774,10 +1781,6 @@
"description": "When enabled, frequently launched apps appear first in the list.",
"label": "Sort by most used"
},
"icon-mode": {
"description": "Use native system icons instead of Tabler icons.",
"label": "Use native icons"
},
"terminal-command": {
"description": "Command to launch a terminal. E.g., 'kitty -e' or 'gnome-terminal --'.",
"label": "Terminal command"
+9 -6
View File
@@ -527,6 +527,7 @@
"close-app": "Cerrar {app}",
"connect-vpn": "Conectarse a {name}",
"cycle-visualizer": "Visualizador de ciclos",
"delete": "Borrar",
"disable-bluetooth": "Desactivar Bluetooth",
"disable-dnd": "Desactivar No molestar",
"disable-wifi": "Desactivar Wi-Fi",
@@ -1407,16 +1408,18 @@
}
},
"edit-mode": {
"add-widget": "Añadir widget",
"button": {
"label": "Entrar en modo de edición"
},
"controls-explanation": "Botón izquierdo del ratón: Mover widget\nBotón derecho del ratón: Redimensionar widget",
"controls-explanation": "Clic izquierdo y arrastrar: Mover o redimensionar el widget.\nClic derecho: Abrir las opciones del menú contextual.",
"description": "Habilita el modo de edición para mover y cambiar la posición de los widgets del escritorio. Cuando está habilitado, los widgets muestran un contorno de arrastre y se pueden reposicionar.",
"exit-button": "Salir del modo de edición",
"grid-snap": {
"label": "Ajustar a cuadrícula"
},
"label": "Modo de edición"
"label": "Modo de edición",
"open-settings": "Abrir ajustes"
},
"enabled": {
"description": "Activar o desactivar los widgets de escritorio por completo.",
@@ -1758,6 +1761,10 @@
"description": "Mostrar elementos en una cuadrícula en lugar de una lista.",
"label": "Vista de cuadrícula"
},
"icon-mode": {
"description": "Usa iconos nativos del sistema en lugar de iconos Tabler.",
"label": "Usar iconos nativos"
},
"position": {
"description": "Elige dónde aparece el panel del lanzador.",
"label": "Posición"
@@ -1774,10 +1781,6 @@
"description": "Cuando está activado, las aplicaciones más utilizadas aparecen primero en la lista.",
"label": "Ordenar por más usados"
},
"icon-mode": {
"description": "Usa iconos nativos del sistema en lugar de iconos Tabler.",
"label": "Usar iconos nativos"
},
"terminal-command": {
"description": "Comando para iniciar un terminal. Por ejemplo, 'kitty -e' o 'gnome-terminal --'.",
"label": "Comando de terminal"
+9 -6
View File
@@ -527,6 +527,7 @@
"close-app": "Fermer {app}",
"connect-vpn": "Se connecter à {name}",
"cycle-visualizer": "Visualiseur de cycle",
"delete": "Supprimer",
"disable-bluetooth": "Désactiver le Bluetooth",
"disable-dnd": "Désactiver le mode Ne pas déranger",
"disable-wifi": "Désactiver le Wi-Fi",
@@ -1407,16 +1408,18 @@
}
},
"edit-mode": {
"add-widget": "Ajouter un widget",
"button": {
"label": "Entrer en mode édition"
},
"controls-explanation": "Bouton gauche de la souris : Déplacer le widget\nBouton droit de la souris : Redimensionner le widget",
"controls-explanation": "Clic gauche et glisser-déposer : Déplacer ou redimensionner le widget.\nClic droit : Ouvrir les options du menu contextuel.",
"description": "Activer le mode édition pour déplacer et repositionner les widgets du bureau. Une fois activé, les widgets affichent un contour de glissement et peuvent être repositionnés.",
"exit-button": "Quitter le mode édition",
"grid-snap": {
"label": "Aligner sur la grille"
},
"label": "Mode édition"
"label": "Mode édition",
"open-settings": "Ouvrir les paramètres"
},
"enabled": {
"description": "Activer ou désactiver complètement les widgets du bureau.",
@@ -1758,6 +1761,10 @@
"description": "Afficher les éléments dans une grille au lieu d'une liste.",
"label": "Vue grille"
},
"icon-mode": {
"description": "Utiliser les icônes natives du système au lieu des icônes Tabler.",
"label": "Utiliser les icônes natives"
},
"position": {
"description": "Choisissez où le panneau du lanceur apparaît.",
"label": "Position"
@@ -1774,10 +1781,6 @@
"description": "Lorsque cette option est activée, les applications fréquemment lancées apparaissent en premier dans la liste.",
"label": "Trier par les plus utilisés"
},
"icon-mode": {
"description": "Utiliser les icônes natives du système au lieu des icônes Tabler.",
"label": "Utiliser les icônes natives"
},
"terminal-command": {
"description": "Commande pour lancer un terminal. Ex: 'kitty -e' ou 'gnome-terminal --'.",
"label": "Commande du terminal"
+9 -6
View File
@@ -527,6 +527,7 @@
"close-app": "{app} を閉じる",
"connect-vpn": "{name} に接続",
"cycle-visualizer": "ビジュアライザーを切り替える",
"delete": "削除 (Sakujo)",
"disable-bluetooth": "Bluetooth を無効化",
"disable-dnd": "おやすみモードを無効化",
"disable-wifi": "Wi-Fi を無効化",
@@ -1407,16 +1408,18 @@
}
},
"edit-mode": {
"add-widget": "ウィジェットを追加",
"button": {
"label": "編集モードに入る"
},
"controls-explanation": "左クリック: ウィジェット移動\n右クリック: ウィジェットのサイズを変更",
"controls-explanation": "左クリックでドラッグ:ウィジェット移動またはサイズ変更。\n右クリック:コンテキストメニューオプションを開く。",
"description": "編集モードを有効にして、デスクトップウィジェットの移動や配置変更を行います。有効にするとウィジェットに枠線が表示され、位置を変更できるようになります。",
"exit-button": "編集モードを終了",
"grid-snap": {
"label": "グリッドにスナップ"
},
"label": "編集モード"
"label": "編集モード",
"open-settings": "設定を開く"
},
"enabled": {
"description": "デスクトップウィジェット全体を有効または無効にします。",
@@ -1758,6 +1761,10 @@
"description": "リスト形式の代わりに、グリッド(格子状)レイアウトで項目を表示します。",
"label": "グリッド表示"
},
"icon-mode": {
"description": "Tablerアイコンではなく、ネイティブのシステムアイコンを使用します。",
"label": "ネイティブアイコンを使用"
},
"position": {
"description": "ランチャーパネルの表示位置を選択します。",
"label": "表示位置"
@@ -1774,10 +1781,6 @@
"description": "有効にすると、よく使うアプリがリストの上位に表示されます。",
"label": "使用頻度順に並べ替え"
},
"icon-mode": {
"description": "Tablerアイコンではなく、ネイティブのシステムアイコンを使用します。",
"label": "ネイティブアイコンを使用"
},
"terminal-command": {
"description": "ターミナルを起動するためのコマンド(例: 'kitty -e' or 'gnome-terminal --')。",
"label": "ターミナル起動コマンド"
+9 -6
View File
@@ -527,6 +527,7 @@
"close-app": "Sluit {app}",
"connect-vpn": "Verbinding maken met {name}",
"cycle-visualizer": "Cyclusvisualisatie",
"delete": "Verwijderen",
"disable-bluetooth": "Bluetooth uitschakelen",
"disable-dnd": "Niet Storen uitschakelen",
"disable-wifi": "Wi-Fi uitschakelen",
@@ -1407,16 +1408,18 @@
}
},
"edit-mode": {
"add-widget": "Widget toevoegen",
"button": {
"label": "Ga naar de bewerkingsmodus"
},
"controls-explanation": "Linkermuisknop: Widget verplaatsen\nRechtermuisknop: Widget vergroten/verkleinen",
"controls-explanation": "Linksklikken en slepen: Widget verplaatsen of het formaat wijzigen.\nRechtermuisknop: Het contextmenu openen.",
"description": "Schakel de bewerkingsmodus in om desktopwidgets te verplaatsen en te herpositioneren. In de ingeschakelde modus tonen widgets een sleepomtrek en kunnen ze worden verplaatst.",
"exit-button": "Bewerkingsmodus verlaten",
"grid-snap": {
"label": "Raster uitlijnen"
},
"label": "Bewerkingsmodus"
"label": "Bewerkingsmodus",
"open-settings": "Instellingen openen"
},
"enabled": {
"description": "Desktopwidgets volledig in- of uitschakelen.",
@@ -1758,6 +1761,10 @@
"description": "Items in een raster weergeven in plaats van een lijst.",
"label": "Rasterweergave"
},
"icon-mode": {
"description": "Gebruik native systeemiconen in plaats van Tabler-iconen.",
"label": "Gebruik native iconen"
},
"position": {
"description": "Kies waar het launcher-paneel verschijnt.",
"label": "Positie"
@@ -1774,10 +1781,6 @@
"description": "Indien ingeschakeld, verschijnen vaak gestarte apps bovenaan in de lijst.",
"label": "Sorteren op meest gebruikt"
},
"icon-mode": {
"description": "Gebruik native systeemiconen in plaats van Tabler-iconen.",
"label": "Gebruik native iconen"
},
"terminal-command": {
"description": "Commando om een terminal te starten. Bijv. 'kitty -e' of 'gnome-terminal --'.",
"label": "Terminalcommando"
+9 -6
View File
@@ -527,6 +527,7 @@
"close-app": "Fechar {app}",
"connect-vpn": "Conectar-se a {name}",
"cycle-visualizer": "Visualizador de ciclo",
"delete": "Apagar",
"disable-bluetooth": "Desativar Bluetooth",
"disable-dnd": "Desativar o Não Perturbe",
"disable-wifi": "Desativar Wi-Fi",
@@ -1407,16 +1408,18 @@
}
},
"edit-mode": {
"add-widget": "Adicionar widget",
"button": {
"label": "Entrar no modo de edição"
},
"controls-explanation": "Botão esquerdo do mouse: Mover widget\nBotão direito do mouse: Redimensionar widget",
"controls-explanation": "Clique esquerdo e arraste: Mover ou redimensionar o widget.\nClique direito: Abrir as opções do menu de contexto.",
"description": "Ative o modo de edição para mover e reposicionar widgets da área de trabalho. Quando ativado, os widgets exibem um contorno de arrastar e podem ser reposicionados.",
"exit-button": "Sair do modo de edição",
"grid-snap": {
"label": "Alinhar à grade"
},
"label": "Modo de edição"
"label": "Modo de edição",
"open-settings": "Abrir configurações"
},
"enabled": {
"description": "Ativar ou desativar widgets da área de trabalho completamente.",
@@ -1758,6 +1761,10 @@
"description": "Exibir itens em uma grade em vez de uma lista.",
"label": "Visualização em grade"
},
"icon-mode": {
"description": "Use ícones nativos do sistema em vez de ícones Tabler.",
"label": "Usar ícones nativos"
},
"position": {
"description": "Escolha onde o painel do lançador aparece.",
"label": "Posição"
@@ -1774,10 +1781,6 @@
"description": "Quando ativado, os aplicativos mais usados aparecem primeiro na lista.",
"label": "Ordenar por mais usados"
},
"icon-mode": {
"description": "Use ícones nativos do sistema em vez de ícones Tabler.",
"label": "Usar ícones nativos"
},
"terminal-command": {
"description": "Comando para iniciar um terminal. Ex: 'kitty -e' ou 'gnome-terminal --'.",
"label": "Comando do terminal"
+9 -6
View File
@@ -527,6 +527,7 @@
"close-app": "Закрыть {app}",
"connect-vpn": "Подключиться к {name}",
"cycle-visualizer": "Визуализатор циклов",
"delete": "Удалить",
"disable-bluetooth": "Отключить Bluetooth",
"disable-dnd": "Отключить режим \"Не беспокоить\"",
"disable-wifi": "Отключить Wi-Fi",
@@ -1407,16 +1408,18 @@
}
},
"edit-mode": {
"add-widget": "Добавить виджет",
"button": {
"label": "Войти в режим редактирования"
},
"controls-explanation": "Левая кнопка мыши: Перемещать виджет\nПравая кнопка мыши: Изменять размер виджета",
"controls-explanation": "Левый клик и перетаскивание: Переместить или изменить размер виджета.\nПравый клик: Открыть контекстное меню.",
"description": "Включите режим редактирования, чтобы перемещать и изменять положение виджетов рабочего стола. В этом режиме виджеты отображаются с контуром перетаскивания и могут быть перемещены.",
"exit-button": "Выйти из режима редактирования",
"grid-snap": {
"label": "Привязка к сетке"
},
"label": "Режим редактирования"
"label": "Режим редактирования",
"open-settings": "Открыть настройки"
},
"enabled": {
"description": "Включить или отключить виджеты рабочего стола полностью.",
@@ -1758,6 +1761,10 @@
"description": "Показывать элементы в виде сетки вместо списка.",
"label": "Вид сетки"
},
"icon-mode": {
"description": "Использовать нативные системные иконки вместо иконок Tabler.",
"label": "Использовать нативные иконки"
},
"position": {
"description": "Выберите, где появляется панель запуска.",
"label": "Положение"
@@ -1774,10 +1781,6 @@
"description": "Если включено, часто запускаемые приложения появляются в списке первыми.",
"label": "Сортировать по частоте использования"
},
"icon-mode": {
"description": "Использовать нативные системные иконки вместо иконок Tabler.",
"label": "Использовать нативные иконки"
},
"terminal-command": {
"description": "Команда для запуска терминала. Например, 'kitty -e' или 'gnome-terminal --'.",
"label": "Команда терминала"
+9 -6
View File
@@ -527,6 +527,7 @@
"close-app": "{app}'i kapat",
"connect-vpn": "{name} bağlantısına bağlan",
"cycle-visualizer": "Döngü görselleştirici",
"delete": "Sil",
"disable-bluetooth": "Bluetooth'u kapat",
"disable-dnd": "Rahatsız Etmeyin'i Kapat",
"disable-wifi": "Kablosuz Bağlantıyı kapat",
@@ -1407,16 +1408,18 @@
}
},
"edit-mode": {
"add-widget": "Araç ekle",
"button": {
"label": "Düzenleme moduna gir"
},
"controls-explanation": "Sol fare tuşu: Araç taşı\nSağ fare tuşu: Araç yeniden boyutlandır",
"controls-explanation": "Sol tıklayıp sürükleyin: Bileşeni taşıyın veya yeniden boyutlandırın.\nSağ tıklayın: Bağlam menüsü seçeneklerini açın.",
"description": "Masaüstü araçlarını taşımak ve yeniden konumlandırmak için düzenleme modunu etkinleştirin. Etkinleştirildiğinde, araçlar bir sürükleme ana hattı gösterir ve yeniden konumlandırılabilir.",
"exit-button": "Düzenleme modundan çık",
"grid-snap": {
"label": "Izgaraya hizala"
},
"label": "Düzenleme modu"
"label": "Düzenleme modu",
"open-settings": "Ayarları aç"
},
"enabled": {
"description": "Masaüstü araçlarını tamamen etkinleştir veya devre dışı bırak.",
@@ -1758,6 +1761,10 @@
"description": "Öğeleri liste yerine ızgara düzeninde görüntüle.",
"label": "Izgara görünümü"
},
"icon-mode": {
"description": "Tabler simgeleri yerine yerel sistem simgelerini kullanın.",
"label": "Yerel simgeleri kullan"
},
"position": {
"description": "Başlatıcı panelinin nerede görüneceğini seçin.",
"label": "Konum"
@@ -1774,10 +1781,6 @@
"description": "Etkinleştirildiğinde, sıkça başlatılan uygulamalar listede ilk olarak görünür.",
"label": "En çok kullanılana göre sırala"
},
"icon-mode": {
"description": "Tabler simgeleri yerine yerel sistem simgelerini kullanın.",
"label": "Yerel simgeleri kullan"
},
"terminal-command": {
"description": "Bir terminal başlatmak için komut. Örn., 'kitty -e' veya 'gnome-terminal --'.",
"label": "Terminal komutu"
+9 -6
View File
@@ -527,6 +527,7 @@
"close-app": "Закрити {app}",
"connect-vpn": "Підключитися до {name}",
"cycle-visualizer": "Візуалізатор циклів",
"delete": "Видалити",
"disable-bluetooth": "Вимкнути Bluetooth",
"disable-dnd": "Вимкнути режим \"Не турбувати\"",
"disable-wifi": "Вимкнути Wi-Fi",
@@ -1407,16 +1408,18 @@
}
},
"edit-mode": {
"add-widget": "Додати віджет",
"button": {
"label": "Увійти в режим редагування"
},
"controls-explanation": "Ліва кнопка миші: Переміщувати віджет\nПрава кнопка миші: Змінювати розмір віджета",
"controls-explanation": "Клацання лівою кнопкою миші та перетягування: Перемістити або змінити розмір віджета.\nКлацання правою кнопкою миші: Відкрити контекстне меню з опціями.",
"description": "Увімкніть режим редагування, щоб переміщувати та змінювати розташування віджетів робочого столу. У ввімкненому стані віджети відображають контур перетягування, і їх можна переміщувати.",
"exit-button": "Вийти з режиму редагування",
"grid-snap": {
"label": "Прив'язка до сітки"
},
"label": "Режим редагування"
"label": "Режим редагування",
"open-settings": "Відкрити налаштування"
},
"enabled": {
"description": "Увімкнути або вимкнути віджети робочого столу повністю.",
@@ -1758,6 +1761,10 @@
"description": "Показувати елементи у вигляді сітки замість списку.",
"label": "Режим сітки"
},
"icon-mode": {
"description": "Використовувати нативні системні іконки замість іконок Tabler.",
"label": "Використовувати нативні іконки"
},
"position": {
"description": "Виберіть, де з'являється панель лаунчера.",
"label": "Положення"
@@ -1774,10 +1781,6 @@
"description": "Коли увімкнено, часто використовувані застосунки з'являються першими в списку.",
"label": "Сортувати за використанням"
},
"icon-mode": {
"description": "Використовувати нативні системні іконки замість іконок Tabler.",
"label": "Використовувати нативні іконки"
},
"terminal-command": {
"description": "Команда для запуску терміналу. Напр., 'kitty -e' або 'gnome-terminal --'.",
"label": "Команда терміналу"
+9 -6
View File
@@ -527,6 +527,7 @@
"close-app": "关闭 {app}",
"connect-vpn": "连接 {name}",
"cycle-visualizer": "切换可视化器样式",
"delete": "删除",
"disable-bluetooth": "禁用蓝牙",
"disable-dnd": "关闭勿扰模式",
"disable-wifi": "禁用Wi-Fi",
@@ -1407,16 +1408,18 @@
}
},
"edit-mode": {
"add-widget": "添加小部件",
"button": {
"label": "进入编辑模式"
},
"controls-explanation": "鼠标左键:移动小组件\n鼠标右键:调整小组件大小",
"controls-explanation": "左键点击并拖动:移动或调整小部件大小。\n右键点击:打开上下文菜单选项。",
"description": "启用编辑模式以移动和重新定位桌面小组件。启用后,小组件会显示拖动轮廓,并且可以重新定位。",
"exit-button": "退出编辑模式",
"grid-snap": {
"label": "网格对齐"
},
"label": "编辑模式"
"label": "编辑模式",
"open-settings": "打开设置"
},
"enabled": {
"description": "完全启用或禁用桌面小部件。",
@@ -1758,6 +1761,10 @@
"description": "以网格布局而非列表显示项目。",
"label": "网格视图"
},
"icon-mode": {
"description": "使用本地系统图标而不是 Tabler 图标。",
"label": "使用本地图标"
},
"position": {
"description": "选择启动器面板出现的位置。",
"label": "位置"
@@ -1774,10 +1781,6 @@
"description": "启用后,经常启动的应用程序将显示在列表首位。",
"label": "按使用频率排序"
},
"icon-mode": {
"description": "使用本地系统图标而不是 Tabler 图标。",
"label": "使用本地图标"
},
"terminal-command": {
"description": "启动终端的命令。例如,'kitty -e'或'gnome-terminal --'。",
"label": "终端命令"
-1
View File
@@ -418,7 +418,6 @@
},
"desktopWidgets": {
"enabled": false,
"editMode": false,
"gridSnap": false,
"monitorWidgets": []
}
-1
View File
@@ -652,7 +652,6 @@ Singleton {
// desktop widgets
property JsonObject desktopWidgets: JsonObject {
property bool enabled: false
property bool editMode: false
property bool gridSnap: false
property list<var> monitorWidgets: []
// Format: [{ "name": "DP-1", "widgets": [...] }, { "name": "HDMI-1", "widgets": [...] }]
+208 -104
View File
@@ -4,6 +4,7 @@ import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Modules.Panels.Settings
import qs.Services.Compositor
import qs.Services.Noctalia
import qs.Services.Power
@@ -70,6 +71,57 @@ Variants {
Logger.d("DesktopWidgets", "Created panel window for", screen?.name);
}
// Add a new widget to the current screen
function addWidgetToCurrentScreen(widgetId) {
var monitorName = window.screen.name;
var newWidget = {
"id": widgetId
};
// Load default metadata if available
var metadata = DesktopWidgetRegistry.widgetMetadata[widgetId];
if (metadata) {
Object.keys(metadata).forEach(function (key) {
if (key !== "allowUserSettings") {
newWidget[key] = metadata[key];
}
});
}
// Place at screen center
newWidget.x = (window.screen.width / 2) - 100;
newWidget.y = (window.screen.height / 2) - 100;
newWidget.scale = 1.0;
// Get current widgets and add new one
var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || [];
var newMonitorWidgets = monitorWidgets.slice();
var found = false;
for (var i = 0; i < newMonitorWidgets.length; i++) {
if (newMonitorWidgets[i].name === monitorName) {
var widgets = (newMonitorWidgets[i].widgets || []).slice();
widgets.push(newWidget);
newMonitorWidgets[i] = {
"name": monitorName,
"widgets": widgets
};
found = true;
break;
}
}
if (!found) {
newMonitorWidgets.push({
"name": monitorName,
"widgets": [newWidget]
});
}
Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets;
Logger.i("DesktopWidgets", "Added widget", widgetId, "to", monitorName);
}
Item {
id: widgetsContainer
anchors.fill: parent
@@ -78,7 +130,7 @@ Variants {
// Using Loader to properly unload Canvas when not needed
Loader {
id: gridOverlayLoader
active: Settings.data.desktopWidgets.editMode && Settings.data.desktopWidgets.enabled && Settings.data.desktopWidgets.gridSnap
active: DesktopWidgetRegistry.editMode && Settings.data.desktopWidgets.enabled && Settings.data.desktopWidgets.gridSnap
anchors.fill: parent
z: -1 // Behind widgets but above background
asynchronous: false
@@ -197,6 +249,10 @@ Variants {
gridOverlay.requestPaint();
}
}
}
Connections {
target: DesktopWidgetRegistry
function onEditModeChanged() {
if (gridOverlayLoader.active) {
gridOverlay.requestPaint();
@@ -245,140 +301,188 @@ Variants {
}
}
// Background for edit mode controls
// Edit mode controls panel
Rectangle {
id: editModeControlsBackground
visible: Settings.data.desktopWidgets.editMode && Settings.data.desktopWidgets.enabled
id: editModeControlsPanel
visible: DesktopWidgetRegistry.editMode && Settings.data.desktopWidgets.enabled
readonly property string barPos: Settings.data.bar.position || "top"
readonly property bool barFloating: Settings.data.bar.floating || false
// Calculate offset from bar based on position and floating state
readonly property int barOffsetTop: {
if (barPos !== "top")
return Style.marginXL * Style.uiScaleRatio;
return Style.marginM;
const floatMarginV = barFloating ? Math.ceil(Settings.data.bar.marginVertical * Style.marginXL) : 0;
return Style.barHeight + floatMarginV + Style.marginM + (Style.marginXL * Style.uiScaleRatio);
return Style.barHeight + floatMarginV + Style.marginM;
}
readonly property int barOffsetRight: {
if (barPos !== "right")
return Style.marginXL * Style.uiScaleRatio;
return Style.marginM;
const floatMarginH = barFloating ? Math.ceil(Settings.data.bar.marginHorizontal * Style.marginXL) : 0;
return Style.barHeight + floatMarginH + Style.marginM + (Style.marginXL * Style.uiScaleRatio);
return Style.barHeight + floatMarginH + Style.marginM;
}
anchors {
top: parent.top
right: parent.right
topMargin: barOffsetTop
rightMargin: barOffsetRight
// Internal state for drag tracking (session-only, resets on restart)
QtObject {
id: panelInternal
property bool isDragging: false
property real dragOffsetX: 0
property real dragOffsetY: 0
// Default position: top-right corner accounting for bar
property real baseX: widgetsContainer.width - editModeControlsPanel.width - editModeControlsPanel.barOffsetRight
property real baseY: editModeControlsPanel.barOffsetTop
}
// Calculate width to accommodate all controls
width: {
var buttonWidth = editModeButton.visible ? editModeButton.implicitWidth : 0;
var explanationWidth = controlsExplanation.visible ? controlsExplanation.width : 0;
var checkboxWidth = gridSnapCheckbox.visible ? gridSnapCheckbox.implicitWidth : 0;
return Math.max(buttonWidth, explanationWidth, checkboxWidth, 200) + (Style.marginXL * 2);
// Reset position when bar position changes
Connections {
target: Settings.data.bar
function onPositionChanged() {
panelInternal.baseX = widgetsContainer.width - editModeControlsPanel.width - editModeControlsPanel.barOffsetRight;
panelInternal.baseY = editModeControlsPanel.barOffsetTop;
}
}
// Calculate height to cover all controls with spacing
height: {
var buttonHeight = editModeButton.visible ? editModeButton.height : 0;
var explanationHeight = controlsExplanation.visible ? controlsExplanation.height : 0;
var checkboxHeight = gridSnapCheckbox.visible ? gridSnapCheckbox.height : 0;
return buttonHeight + Style.marginXL + explanationHeight + Style.marginXL + checkboxHeight + (Style.marginXL * 2);
}
x: panelInternal.isDragging ? panelInternal.dragOffsetX : panelInternal.baseX
y: panelInternal.isDragging ? panelInternal.dragOffsetY : panelInternal.baseY
width: controlsLayout.implicitWidth + (Style.marginXL * 2)
height: controlsLayout.implicitHeight + (Style.marginXL * 2)
color: Qt.rgba(Color.mSurface.r, Color.mSurface.g, Color.mSurface.b, 0.85)
radius: Style.radiusL
border {
width: 1
color: Qt.alpha(Color.mOutline, 0.2)
width: Style.borderS
color: Color.mOutline
}
z: 9999
}
// Exit edit mode button
NButton {
id: editModeButton
visible: Settings.data.desktopWidgets.editMode && Settings.data.desktopWidgets.enabled
// Drag area for relocating the panel
MouseArea {
id: dragArea
anchors.fill: parent
cursorShape: panelInternal.isDragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor
readonly property string barPos: Settings.data.bar.position || "top"
readonly property bool barFloating: Settings.data.bar.floating || false
// Calculate offset from bar based on position and floating state
readonly property int barOffsetTop: {
if (barPos !== "top")
return Style.marginXL * Style.uiScaleRatio;
const floatMarginV = barFloating ? Math.ceil(Settings.data.bar.marginVertical * Style.marginXL) : 0;
return Style.barHeight + floatMarginV + Style.marginM + (Style.marginXL * Style.uiScaleRatio);
}
readonly property int barOffsetRight: {
if (barPos !== "right")
return Style.marginXL * Style.uiScaleRatio;
const floatMarginH = barFloating ? Math.ceil(Settings.data.bar.marginHorizontal * Style.marginXL) : 0;
return Style.barHeight + floatMarginH + Style.marginM + (Style.marginXL * Style.uiScaleRatio);
property point pressPos: Qt.point(0, 0)
onPressed: mouse => {
pressPos = mapToItem(widgetsContainer, mouse.x, mouse.y);
panelInternal.dragOffsetX = editModeControlsPanel.x;
panelInternal.dragOffsetY = editModeControlsPanel.y;
panelInternal.isDragging = true;
}
onPositionChanged: mouse => {
if (panelInternal.isDragging && pressed) {
var currentPos = mapToItem(widgetsContainer, mouse.x, mouse.y);
var deltaX = currentPos.x - pressPos.x;
var deltaY = currentPos.y - pressPos.y;
var newX = panelInternal.baseX + deltaX;
var newY = panelInternal.baseY + deltaY;
// Boundary clamping
newX = Math.max(0, Math.min(newX, widgetsContainer.width - editModeControlsPanel.width));
newY = Math.max(0, Math.min(newY, widgetsContainer.height - editModeControlsPanel.height));
panelInternal.dragOffsetX = newX;
panelInternal.dragOffsetY = newY;
}
}
onReleased: {
if (panelInternal.isDragging) {
panelInternal.baseX = panelInternal.dragOffsetX;
panelInternal.baseY = panelInternal.dragOffsetY;
panelInternal.isDragging = false;
}
}
onCanceled: {
panelInternal.isDragging = false;
}
}
anchors {
top: editModeControlsBackground.top
right: editModeControlsBackground.right
topMargin: Style.marginXL
rightMargin: Style.marginXL
}
text: I18n.tr("settings.desktop-widgets.edit-mode.exit-button")
icon: "logout"
//backgroundColor: Color.mSurface
//textColor: Color.mOnSurface
//hoverColor: Color.mSurfaceVariant
outlined: false
fontSize: Style.fontSizeM * 1.1
iconSize: Style.fontSizeL * 1.1
z: 10000
onClicked: Settings.data.desktopWidgets.editMode = false
}
ColumnLayout {
id: controlsLayout
anchors {
fill: parent
margins: Style.marginXL
}
spacing: Style.marginL
// Controls explanation text
NText {
id: controlsExplanation
visible: Settings.data.desktopWidgets.editMode && Settings.data.desktopWidgets.enabled
anchors {
top: editModeButton.bottom
right: editModeControlsBackground.right
topMargin: Style.marginXL
rightMargin: Style.marginXL
}
text: I18n.tr("settings.desktop-widgets.edit-mode.controls-explanation")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignRight
wrapMode: Text.WordWrap
width: Math.min(implicitWidth, 300 * Style.uiScaleRatio)
z: 10000
}
RowLayout {
Layout.alignment: Qt.AlignRight
spacing: Style.marginS
// Grid snap checkbox
RowLayout {
id: gridSnapCheckbox
visible: Settings.data.desktopWidgets.editMode && Settings.data.desktopWidgets.enabled
anchors {
top: controlsExplanation.bottom
right: editModeControlsBackground.right
topMargin: Style.marginXL
rightMargin: Style.marginXL
}
spacing: Style.marginS
z: 10000
NIconButton {
id: addWidgetButton
icon: "layout-grid-add"
tooltipText: I18n.tr("settings.desktop-widgets.edit-mode.add-widget")
onClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(window.screen);
if (popupMenuWindow) {
// Build menu items from registry
var items = [];
var widgets = DesktopWidgetRegistry.widgets;
for (var id in widgets) {
items.push({
action: id,
text: DesktopWidgetRegistry.getWidgetDisplayName(id),
icon: "layout-grid-add"
});
}
var globalPos = addWidgetButton.mapToItem(null, 0, addWidgetButton.height + Style.marginS);
popupMenuWindow.showDynamicContextMenu(items, globalPos.x, globalPos.y, function (widgetId) {
addWidgetToCurrentScreen(widgetId);
return false;
});
}
}
}
NText {
text: I18n.tr("settings.desktop-widgets.edit-mode.grid-snap.label")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignRight
}
NIconButton {
icon: "grid-4x4"
tooltipText: I18n.tr("settings.desktop-widgets.edit-mode.grid-snap.label")
colorBg: Settings.data.desktopWidgets.gridSnap ? Color.mPrimary : Color.mSurfaceVariant
colorFg: Settings.data.desktopWidgets.gridSnap ? Color.mOnPrimary : Color.mPrimary
onClicked: Settings.data.desktopWidgets.gridSnap = !Settings.data.desktopWidgets.gridSnap
}
NCheckbox {
checked: Settings.data.desktopWidgets.gridSnap
onToggled: checked => Settings.data.desktopWidgets.gridSnap = checked
NIconButton {
icon: "settings"
tooltipText: I18n.tr("settings.desktop-widgets.edit-mode.open-settings")
onClicked: {
if (Settings.data.ui.settingsPanelMode === "window") {
SettingsPanelService.toggleWindow(SettingsPanel.Tab.DesktopWidgets);
} else {
var settingsPanel = PanelService.getPanel("settingsPanel", screenLoader.modelData);
if (settingsPanel) {
settingsPanel.requestedTab = SettingsPanel.Tab.DesktopWidgets;
settingsPanel.toggle();
}
}
}
}
NButton {
text: I18n.tr("settings.desktop-widgets.edit-mode.exit-button")
icon: "logout"
outlined: false
fontSize: Style.fontSizeS
iconSize: Style.fontSizeM
onClicked: DesktopWidgetRegistry.editMode = false
}
}
NText {
Layout.alignment: Qt.AlignRight
Layout.maximumWidth: 300 * Style.uiScaleRatio
text: I18n.tr("settings.desktop-widgets.edit-mode.controls-explanation")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignRight
wrapMode: Text.WordWrap
}
}
}
}
+331 -63
View File
@@ -2,6 +2,9 @@ import QtQuick
import QtQuick.Effects
import Quickshell
import qs.Commons
import qs.Services.Noctalia
import qs.Services.UI
import qs.Widgets
Item {
id: root
@@ -26,6 +29,7 @@ Item {
readonly property real scaleSensitivity: 0.0015
readonly property real scaleUpdateThreshold: 0.015
readonly property real cornerScaleSensitivity: 0.0003 // Much lower sensitivity for corner handles
// Grid size ensures lines pass through screen center on both axes
readonly property int gridSize: {
@@ -133,6 +137,105 @@ Item {
}
}
function removeWidget() {
if (widgetIndex < 0 || !screen || !screen.name) {
return;
}
var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || [];
var newMonitorWidgets = monitorWidgets.slice();
for (var i = 0; i < newMonitorWidgets.length; i++) {
if (newMonitorWidgets[i].name === screen.name) {
var widgets = (newMonitorWidgets[i].widgets || []).slice();
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
widgets.splice(widgetIndex, 1);
newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], {
"widgets": widgets
});
Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets;
}
break;
}
}
}
function openWidgetSettings() {
if (!widgetData || !widgetData.id || !screen) {
return;
}
var widgetId = widgetData.id;
var hasSettings = false;
// Check if widget has settings
if (DesktopWidgetRegistry.isPluginWidget(widgetId)) {
var pluginId = widgetId.replace("plugin:", "");
var manifest = PluginRegistry.getPluginManifest(pluginId);
if (manifest && manifest.entryPoints && manifest.entryPoints.settings) {
hasSettings = true;
}
} else {
hasSettings = DesktopWidgetRegistry.widgetSettingsMap[widgetId] !== undefined;
}
if (!hasSettings) {
Logger.w("DraggableDesktopWidget", "Widget does not have settings:", widgetId);
return;
}
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (!popupMenuWindow) {
Logger.e("DraggableDesktopWidget", "No popup menu window found for screen");
return;
}
// Hide the dynamic context menu (popup window stays open for the dialog)
if (popupMenuWindow.hideDynamicMenu) {
popupMenuWindow.hideDynamicMenu();
}
var component = Qt.createComponent(Quickshell.shellDir + "/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml");
function instantiateAndOpen() {
var dialog = component.createObject(popupMenuWindow.dialogParent, {
"widgetIndex": widgetIndex,
"widgetData": widgetData,
"widgetId": widgetId,
"sectionId": screen.name
});
if (dialog) {
dialog.updateWidgetSettings.connect((sec, idx, settings) => {
root.updateWidgetData(settings);
});
popupMenuWindow.hasDialog = true;
dialog.closed.connect(() => {
popupMenuWindow.hasDialog = false;
popupMenuWindow.close();
dialog.destroy();
});
dialog.open();
} else {
Logger.e("DraggableDesktopWidget", "Failed to create widget settings dialog");
}
}
if (component.status === Component.Ready) {
instantiateAndOpen();
} else if (component.status === Component.Error) {
Logger.e("DraggableDesktopWidget", "Error loading settings dialog component:", component.errorString());
} else {
component.statusChanged.connect(() => {
if (component.status === Component.Ready) {
instantiateAndOpen();
} else if (component.status === Component.Error) {
Logger.e("DraggableDesktopWidget", "Error loading settings dialog component:", component.errorString());
}
});
}
}
x: internal.isDragging ? internal.dragOffsetX : internal.baseX
y: internal.isDragging ? internal.dragOffsetY : internal.baseY
@@ -173,10 +276,10 @@ Item {
id: decorationRect
anchors.fill: parent
anchors.margins: -Style.marginS
color: Settings.data.desktopWidgets.editMode ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.1) : Color.transparent
border.color: (Settings.data.desktopWidgets.editMode || internal.isDragging) ? (internal.isDragging ? Color.mOutline : Color.mPrimary) : Color.transparent
border.width: Settings.data.desktopWidgets.editMode ? 3 : (internal.isDragging ? 2 : 0)
radius: Style.radiusL + Style.marginS
color: DesktopWidgetRegistry.editMode ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.1) : Color.transparent
border.color: (DesktopWidgetRegistry.editMode || internal.isDragging) ? (internal.isDragging ? Color.mOutline : Color.mPrimary) : Color.transparent
border.width: DesktopWidgetRegistry.editMode ? 3 : 0
radius: Style.radiusL
z: -1
}
@@ -210,26 +313,59 @@ Item {
z: 1
}
// Drag and Scale MouseArea - handles both dragging (left-click) and scaling (right-click)
// Context menu model and handler - menu is created dynamically in PopupMenuWindow
property var contextMenuModel: {
var hasSettings = false;
if (widgetData && widgetData.id) {
var widgetId = widgetData.id;
if (DesktopWidgetRegistry.isPluginWidget(widgetId)) {
var pluginId = widgetId.replace("plugin:", "");
var manifest = PluginRegistry.getPluginManifest(pluginId);
hasSettings = manifest && manifest.entryPoints && manifest.entryPoints.settings;
} else {
hasSettings = DesktopWidgetRegistry.widgetSettingsMap[widgetId] !== undefined;
}
}
var items = [];
if (hasSettings) {
items.push({
"label": I18n.tr("context-menu.widget-settings"),
"action": "widget-settings",
"icon": "settings"
});
}
items.push({
"label": I18n.tr("context-menu.delete"),
"action": "delete",
"icon": "trash"
});
return items;
}
function handleContextMenuAction(action) {
if (action === "widget-settings") {
// Don't close - openWidgetSettings will use the popup window for the dialog
root.openWidgetSettings();
return true; // Signal that we're handling close ourselves
} else if (action === "delete") {
root.removeWidget();
return false; // Let caller close the popup
}
return false;
}
// Drag MouseArea - handles dragging (left-click)
MouseArea {
id: interactionArea
id: dragArea
anchors.fill: parent
z: 1000
visible: Settings.data.desktopWidgets.editMode
cursorShape: {
if (internal.isDragging)
return Qt.ClosedHandCursor;
if (internal.isScaling)
return Qt.SizeAllCursor;
// Change cursor based on which button user is likely to press
// Right mouse button for scaling, left for dragging
return Qt.OpenHandCursor;
}
visible: DesktopWidgetRegistry.editMode
cursorShape: internal.isDragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
acceptedButtons: Qt.LeftButton
property point pressPos: Qt.point(0, 0)
property real initialScale: 1.0
onPressed: mouse => {
// Prevent starting new operation if one is already in progress
@@ -238,21 +374,10 @@ Item {
}
pressPos = Qt.point(mouse.x, mouse.y);
if (mouse.button === Qt.LeftButton) {
internal.operationType = "drag";
internal.dragOffsetX = root.x;
internal.dragOffsetY = root.y;
internal.isDragging = true;
} else if (mouse.button === Qt.RightButton) {
internal.operationType = "scale";
internal.isScaling = true;
internal.initialWidth = root.width;
internal.initialHeight = root.height;
internal.initialMousePos = Qt.point(mouse.x, mouse.y);
internal.initialScale = root.widgetScale;
internal.lastScale = root.widgetScale;
}
internal.operationType = "drag";
internal.dragOffsetX = root.x;
internal.dragOffsetY = root.y;
internal.isDragging = true;
}
onPositionChanged: mouse => {
@@ -263,7 +388,6 @@ Item {
var deltaX = globalCurrentPos.x - globalPressPos.x;
var deltaY = globalCurrentPos.y - globalPressPos.y;
// Calculate new position based on original position when drag started
var newX = internal.dragOffsetX + deltaX;
var newY = internal.dragOffsetY + deltaY;
@@ -287,24 +411,6 @@ Item {
internal.dragOffsetX = newX;
internal.dragOffsetY = newY;
} else if (internal.isScaling && pressed && internal.operationType === "scale") {
var dx = mouse.x - internal.initialMousePos.x;
var dy = mouse.y - internal.initialMousePos.y;
// Use primary direction of movement to determine scale change
var primaryMovement = (Math.abs(dx) > Math.abs(dy)) ? dx : dy;
// Scale change relative to initial widget size ensures consistent behavior
var scaleChange = primaryMovement * root.scaleSensitivity;
// Add to last applied scale (not initial) to allow smooth continuous scaling
var newScale = Math.max(minScale, Math.min(maxScale, internal.lastScale + scaleChange));
// Apply smoothing threshold to prevent rapid changes
if (Math.abs(root.widgetScale - newScale) > root.scaleUpdateThreshold && !isNaN(newScale) && newScale > 0) {
root.widgetScale = newScale;
internal.lastScale = newScale;
}
}
}
@@ -320,23 +426,185 @@ Item {
internal.centerX = internal.baseX + root.width / 2;
internal.isDragging = false;
internal.operationType = "";
} else if (internal.isScaling && internal.operationType === "scale") {
root.updateWidgetData({
"scale": root.widgetScale
});
internal.isScaling = false;
internal.operationType = "";
internal.lastScale = root.widgetScale;
}
}
onCanceled: {
internal.isDragging = false;
internal.isScaling = false;
internal.operationType = "";
// Sync lastScale when operation is canceled to prevent drift
internal.lastScale = root.widgetScale;
}
}
// Right-click MouseArea for context menu
MouseArea {
id: contextMenuArea
anchors.fill: parent
z: 1001
visible: DesktopWidgetRegistry.editMode
acceptedButtons: Qt.RightButton
hoverEnabled: true
onPressed: mouse => {
if (mouse.button === Qt.RightButton) {
var popupMenuWindow = PanelService.getPopupMenuWindow(root.screen);
if (popupMenuWindow) {
// Map click position to screen coordinates
var globalPos = root.mapToItem(null, mouse.x, mouse.y);
// Use dynamic context menu (created in PopupMenuWindow's Top layer)
// This ensures input events work correctly for desktop widgets (Bottom layer)
popupMenuWindow.showDynamicContextMenu(root.contextMenuModel, globalPos.x, globalPos.y, root.handleContextMenuAction);
}
}
}
}
// Corner handles for scaling - using Repeater to avoid code duplication
readonly property real cornerHandleSize: 8
readonly property real outlineMargin: Style.marginS
readonly property color colorHandle: Color.mSecondary
// Corner handle model: defines position, opposite corner, cursor, and triangle points for each corner
// xMult/yMult: multipliers for position (0 = left/top edge, 1 = right/bottom edge)
// oppXMult/oppYMult: multipliers for opposite corner calculation
// cursor: resize cursor type (FDiag for TL-BR diagonal, BDiag for TR-BL diagonal)
// points: triangle vertices as [x, y] pairs normalized to cornerHandleSize
readonly property var cornerHandleModel: [
{
xMult: 0,
yMult: 0,
oppXMult: 1,
oppYMult: 1,
cursor: Qt.SizeFDiagCursor,
points: [[0, 0], [1, 0], [0, 1]]
},
{
xMult: 1,
yMult: 0,
oppXMult: 0,
oppYMult: 1,
cursor: Qt.SizeBDiagCursor,
points: [[1, 0], [1, 1], [0, 0]]
},
{
xMult: 0,
yMult: 1,
oppXMult: 1,
oppYMult: 0,
cursor: Qt.SizeBDiagCursor,
points: [[0, 1], [0, 0], [1, 1]]
},
{
xMult: 1,
yMult: 1,
oppXMult: 0,
oppYMult: 0,
cursor: Qt.SizeFDiagCursor,
points: [[1, 1], [1, 0], [0, 1]]
}
]
Repeater {
model: root.cornerHandleModel
delegate: Canvas {
id: cornerHandle
required property var modelData
required property int index
visible: DesktopWidgetRegistry.editMode && !internal.isDragging
x: modelData.xMult * (root.width + outlineMargin) - (modelData.xMult === 0 ? outlineMargin : cornerHandleSize)
y: modelData.yMult * (root.height + outlineMargin) - (modelData.yMult === 0 ? outlineMargin : cornerHandleSize)
width: cornerHandleSize
height: cornerHandleSize
z: 2000
onPaint: {
var ctx = getContext("2d");
ctx.reset();
ctx.fillStyle = colorHandle;
ctx.beginPath();
ctx.moveTo(modelData.points[0][0] * cornerHandleSize, modelData.points[0][1] * cornerHandleSize);
ctx.lineTo(modelData.points[1][0] * cornerHandleSize, modelData.points[1][1] * cornerHandleSize);
ctx.lineTo(modelData.points[2][0] * cornerHandleSize, modelData.points[2][1] * cornerHandleSize);
ctx.closePath();
ctx.fill();
}
Component.onCompleted: requestPaint()
onVisibleChanged: if (visible)
requestPaint()
Connections {
target: root
function onWidthChanged() {
if (cornerHandle.visible)
cornerHandle.requestPaint();
}
function onHeightChanged() {
if (cornerHandle.visible)
cornerHandle.requestPaint();
}
}
MouseArea {
id: scaleMouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
cursorShape: cornerHandle.modelData.cursor
property point pressPos: Qt.point(0, 0)
// Capture opposite corner at press time to avoid feedback loop during scaling
property real oppositeCornerX: 0
property real oppositeCornerY: 0
property real initialDistance: 0
onPressed: mouse => {
if (internal.operationType !== "") {
return;
}
pressPos = mapToItem(root.parent, mouse.x, mouse.y);
// Calculate and store opposite corner position using initial scale
oppositeCornerX = root.x + cornerHandle.modelData.oppXMult * root.width * root.widgetScale;
oppositeCornerY = root.y + cornerHandle.modelData.oppYMult * root.height * root.widgetScale;
initialDistance = Math.sqrt(Math.pow(pressPos.x - oppositeCornerX, 2) + Math.pow(pressPos.y - oppositeCornerY, 2));
internal.operationType = "scale";
internal.isScaling = true;
internal.initialScale = root.widgetScale;
internal.lastScale = root.widgetScale;
}
onPositionChanged: mouse => {
if (internal.isScaling && pressed && internal.operationType === "scale" && initialDistance > 0) {
var currentPos = mapToItem(root.parent, mouse.x, mouse.y);
// Use the fixed opposite corner position captured at press time
var currentDistance = Math.sqrt(Math.pow(currentPos.x - oppositeCornerX, 2) + Math.pow(currentPos.y - oppositeCornerY, 2));
var scaleRatio = currentDistance / initialDistance;
var newScale = Math.max(minScale, Math.min(maxScale, internal.initialScale * scaleRatio));
if (!isNaN(newScale) && newScale > 0) {
root.widgetScale = newScale;
internal.lastScale = newScale;
}
}
}
onReleased: mouse => {
if (internal.isScaling && internal.operationType === "scale") {
root.updateWidgetData({
"scale": root.widgetScale
});
internal.isScaling = false;
internal.operationType = "";
internal.lastScale = root.widgetScale;
}
}
onCanceled: {
internal.isScaling = false;
internal.operationType = "";
internal.lastScale = root.widgetScale;
}
}
}
}
}
@@ -5,6 +5,7 @@ import Quickshell
import qs.Commons
import qs.Modules.DesktopWidgets
import qs.Services.Media
import qs.Services.UI
import qs.Widgets
import qs.Widgets.AudioSpectrum
@@ -22,7 +23,7 @@ DraggableDesktopWidget {
// State
readonly property bool shouldHideIdle: (hideMode === "idle") && !isPlaying
readonly property bool shouldHideEmpty: !hasPlayer && hideMode === "hidden"
readonly property bool isHidden: (shouldHideIdle || shouldHideEmpty) && !Settings.data.desktopWidgets.editMode
readonly property bool isHidden: (shouldHideIdle || shouldHideEmpty) && !DesktopWidgetRegistry.editMode
visible: !isHidden
// CavaService registration for visualizer
+56
View File
@@ -3,6 +3,7 @@ import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services.UI
import qs.Widgets
// Generic full-screen popup window for menus and context menus
// This is a top-level PanelWindow (sibling to MainScreen, not nested inside it)
@@ -20,6 +21,9 @@ PanelWindow {
// Expose the trayMenu Loader directly (for backward compatibility)
readonly property alias trayMenuLoader: trayMenuLoader
// Dynamic context menu callback for items in other windows (e.g., desktop widgets)
property var dynamicMenuCallback: null
anchors.top: true
anchors.left: true
anchors.right: true
@@ -55,6 +59,26 @@ PanelWindow {
}
}
// Dynamic context menu - created as child of this window (Top layer) so input works correctly
// Used for items in other windows like desktop widgets (bottom layer)
NPopupContextMenu {
id: dynamicMenu
visible: false
screen: root.screen
onTriggered: (action, item) => {
if (root.dynamicMenuCallback) {
// Callback returns true if it will handle closing (e.g., opening a dialog)
var handled = root.dynamicMenuCallback(action);
if (!handled) {
root.close();
}
} else {
root.close();
}
}
}
function open() {
visible = true;
}
@@ -67,6 +91,35 @@ PanelWindow {
}
}
// Show a dynamic context menu with model and callback at screen coordinates
// Used for items in other window layers (e.g., desktop widgets in bottom layer)
function showDynamicContextMenu(model, screenX, screenY, callback) {
dynamicMenu.model = model;
dynamicMenuCallback = callback;
// Use the anchor point item for positioning at absolute coordinates
dynamicMenu.anchorItem = anchorPoint;
anchorPoint.x = screenX;
anchorPoint.y = screenY;
contentItem = dynamicMenu;
dynamicMenu.visible = true;
open();
}
// Invisible anchor point for dynamic menu positioning
Item {
id: anchorPoint
width: 1
height: 1
}
// Hide just the dynamic menu without closing the popup window
// Used when transitioning from context menu to a dialog
function hideDynamicMenu() {
dynamicMenu.visible = false;
}
function close() {
visible = false;
// Call close/hide method on current content
@@ -77,6 +130,9 @@ PanelWindow {
contentItem.close();
}
}
// Hide dynamic menu
dynamicMenu.visible = false;
dynamicMenuCallback = null;
// Restore TrayMenu as default content
if (trayMenuLoader.item) {
contentItem = trayMenuLoader.item;
@@ -45,11 +45,11 @@ ColumnLayout {
NButton {
visible: Settings.data.desktopWidgets.enabled
Layout.fillWidth: true
text: Settings.data.desktopWidgets.editMode ? I18n.tr("settings.desktop-widgets.edit-mode.exit-button") : I18n.tr("settings.desktop-widgets.edit-mode.button.label")
text: DesktopWidgetRegistry.editMode ? I18n.tr("settings.desktop-widgets.edit-mode.exit-button") : I18n.tr("settings.desktop-widgets.edit-mode.button.label")
icon: "edit"
onClicked: {
Settings.data.desktopWidgets.editMode = !Settings.data.desktopWidgets.editMode;
if (Settings.data.desktopWidgets.editMode && Settings.data.ui.settingsPanelMode !== "window") {
DesktopWidgetRegistry.editMode = !DesktopWidgetRegistry.editMode;
if (DesktopWidgetRegistry.editMode && Settings.data.ui.settingsPanelMode !== "window") {
var item = root.parent;
while (item) {
if (item.closeRequested !== undefined) {
+1 -1
View File
@@ -490,7 +490,7 @@ Item {
Settings.data.desktopWidgets.enabled = true;
}
function edit() {
Settings.data.desktopWidgets.editMode = !Settings.data.desktopWidgets.editMode;
DesktopWidgetRegistry.editMode = !DesktopWidgetRegistry.editMode;
}
}
}
+15
View File
@@ -4,10 +4,14 @@ import QtQuick
import Quickshell
import qs.Commons
import qs.Modules.DesktopWidgets.Widgets
import qs.Services.Noctalia
Singleton {
id: root
// Transient state - not persisted, resets on shell restart
property bool editMode: false
// Signal emitted when plugin widgets are registered/unregistered
signal pluginWidgetRegistryUpdated
@@ -111,6 +115,17 @@ Singleton {
return Object.keys(pluginWidgets);
}
// Get display name for a widget ID
function getWidgetDisplayName(widgetId) {
if (widgetId.startsWith("plugin:")) {
var pluginId = widgetId.replace("plugin:", "");
var manifest = PluginRegistry.getPluginManifest(pluginId);
return manifest ? manifest.name : pluginId;
}
// Core widgets - return as-is (Clock, MediaPlayer, Weather)
return widgetId;
}
// Register a plugin desktop widget
function registerPluginWidget(pluginId, component, metadata) {
if (!pluginId || !component) {
+16
View File
@@ -118,6 +118,22 @@ PopupWindow {
}
anchor.rect.y: {
if (anchorItem && screen) {
// Check if using absolute positioning (small anchor point item)
const isAbsolutePosition = anchorItem.width <= 1 && anchorItem.height <= 1;
if (isAbsolutePosition) {
// For absolute positioning, show menu directly at anchor Y
// Only adjust if menu would clip at bottom
const anchorGlobalPos = anchorItem.mapToItem(null, 0, 0);
const menuBottom = anchorGlobalPos.y + implicitHeight;
if (menuBottom > screen.height - Style.marginM) {
// Position above the click point instead
return -implicitHeight;
}
return 0;
}
const anchorCenterY = anchorItem.height / 2;
// Calculate base Y position based on bar orientation