mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Merge branch 'noctalia-dev:main' into pr/networking-refactor-pt1
This commit is contained in:
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Werde Unterstützer",
|
||||
"changelog": "Änderungsprotokoll anzeigen",
|
||||
"changelog-on-startup": "Änderungsprotokoll bei Update anzeigen",
|
||||
"changelog-on-startup-desc": "Automatisch den Changelog anzeigen, wenn Noctalia aktualisiert wird.",
|
||||
"contributors-description": "Ein Dankeschön an unseren {count} <b>großartigen</b> Mitwirkenden!",
|
||||
"contributors-description-plural": "Ein Dankeschön an unsere {count} <b>großartigen</b> Mitwirkenden!",
|
||||
"copy-info": "Informationen kopieren",
|
||||
@@ -1671,8 +1673,8 @@
|
||||
"panels-overlay-label": "Panels & Leiste oben behalten",
|
||||
"scaling-description": "Ändert die Größe der allgemeinen Benutzeroberfläche, mit Ausnahme der Leiste.",
|
||||
"scaling-label": "Oberflächenskalierung",
|
||||
"scrollbar-always-visible-description": "Scrollbalken immer sichtbar lassen, wenn Inhalte scrollbar sind, anstatt sie nur beim Darüberfahren anzuzeigen.",
|
||||
"scrollbar-always-visible-label": "Immer Bildlaufleisten anzeigen",
|
||||
"scrollbar-always-visible-description": "Bildlaufleisten immer sichtbar lassen, wenn Inhalte scrollbar sind, anstatt sie nur beim Darüberfahren anzuzeigen.",
|
||||
"scrollbar-always-visible-label": "Bildlaufleisten immer anzeigen",
|
||||
"settings-panel-header": "Einstellungs-Panel",
|
||||
"settings-panel-mode-description": "Wählen Sie das Layout der Einstellungen (möglicherweise ist ein Neustart erforderlich).",
|
||||
"settings-panel-mode-label": "Einstellungs-Panel-Modus",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Schlagschatten",
|
||||
"title": "Benutzeroberfläche",
|
||||
"tooltips-description": "Tooltips in der gesamten Benutzeroberfläche aktivieren oder deaktivieren.",
|
||||
"tooltips-label": "Tooltips anzeigen"
|
||||
"tooltips-label": "Tooltips anzeigen",
|
||||
"translucent-widgets-description": "Mache Schaltflächen, Tabs und andere Widgets in Panels halbtransparent.",
|
||||
"translucent-widgets-label": "Transluzente Widgets"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Alphabetisch",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Become a supporter",
|
||||
"changelog": "View changelog",
|
||||
"changelog-on-startup": "Show changelog on update",
|
||||
"changelog-on-startup-desc": "Automatically show the changelog when Noctalia is updated",
|
||||
"contributors-description": "Shout-out to our {count} <b>awesome</b> contributor!",
|
||||
"contributors-description-plural": "Shout-out to our {count} <b>awesome</b> contributors!",
|
||||
"copy-info": "Copy info",
|
||||
@@ -1021,7 +1023,6 @@
|
||||
"edit-mode-description": "Enable edit mode to move and reposition desktop widgets. When enabled, widgets show a drag outline and can be repositioned.",
|
||||
"edit-mode-exit-button": "Exit edit mode",
|
||||
"edit-mode-grid-snap-label": "Grid snap",
|
||||
"edit-mode-grid-snap-scale-label": "Snap scale",
|
||||
"edit-mode-label": "Edit mode",
|
||||
"enabled-description": "Enable or disable desktop widgets entirely.",
|
||||
"enabled-label": "Enable desktop widgets",
|
||||
@@ -1686,7 +1687,9 @@
|
||||
"shadows-label": "Drop shadows",
|
||||
"title": "User Interface",
|
||||
"tooltips-description": "Enable or disable tooltips throughout the interface.",
|
||||
"tooltips-label": "Show tooltips"
|
||||
"tooltips-label": "Show tooltips",
|
||||
"translucent-widgets-description": "Make buttons, tabs, and other widgets inside panels semi-transparent.",
|
||||
"translucent-widgets-label": "Translucent widgets"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Alphabetical",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Hazte patrocinador",
|
||||
"changelog": "Ver registro de cambios",
|
||||
"changelog-on-startup": "Mostrar registro de cambios al actualizar",
|
||||
"changelog-on-startup-desc": "Mostrar automáticamente el registro de cambios cuando Noctalia se actualice.",
|
||||
"contributors-description": "¡Un saludo a nuestro <b>increíble</b> colaborador número {count}!",
|
||||
"contributors-description-plural": "¡Un saludo a nuestros {count} <b>increíbles</b> colaboradores!",
|
||||
"copy-info": "Copiar información",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Sombras paralelas",
|
||||
"title": "Interfaz de usuario",
|
||||
"tooltips-description": "Activar o desactivar los avisos emergentes en toda la interfaz.",
|
||||
"tooltips-label": "Mostrar textos emergentes"
|
||||
"tooltips-label": "Mostrar textos emergentes",
|
||||
"translucent-widgets-description": "Haz que los botones, pestañas y otros widgets dentro de los paneles sean semitransparentes.",
|
||||
"translucent-widgets-label": "Widgets translúcidos"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Alfabético",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Devenir un soutien",
|
||||
"changelog": "Afficher le journal des modifications",
|
||||
"changelog-on-startup": "Afficher le journal des modifications lors de la mise à jour",
|
||||
"changelog-on-startup-desc": "Afficher automatiquement le journal des modifications lors de la mise à jour de Noctalia.",
|
||||
"contributors-description": "Un grand merci à notre {count} <b>super</b> contributeur !",
|
||||
"contributors-description-plural": "Un grand merci à nos {count} <b>super</b> contributeurs !",
|
||||
"copy-info": "Copier les informations",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Ombres portées",
|
||||
"title": "Interface utilisateur",
|
||||
"tooltips-description": "Activer ou désactiver les infobulles dans toute l'interface.",
|
||||
"tooltips-label": "Afficher les infobulles"
|
||||
"tooltips-label": "Afficher les infobulles",
|
||||
"translucent-widgets-description": "Rendre les boutons, onglets et autres widgets à l'intérieur des panneaux semi-transparents.",
|
||||
"translucent-widgets-label": "Widgets translucides"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Alphabétique",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Legyél támogató",
|
||||
"changelog": "Változások megtekintése",
|
||||
"changelog-on-startup": "Változási napló megjelenítése frissítéskor",
|
||||
"changelog-on-startup-desc": "Automatikusan mutassa a változási naplót, amikor a Noctalia frissül.",
|
||||
"contributors-description": "Köszönet a(z) {count} <b>fantasztikus</b> közreműködőnknek!",
|
||||
"contributors-description-plural": "Köszönet a(z) {count} <b>fantasztikus</b> közreműködőnknek!",
|
||||
"copy-info": "Információk másolása",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Árnyékok",
|
||||
"title": "Felhasználói felület",
|
||||
"tooltips-description": "Engedélyezi vagy letiltja az eszköztippeket a felületen.",
|
||||
"tooltips-label": "Eszköztippek megjelenítése"
|
||||
"tooltips-label": "Eszköztippek megjelenítése",
|
||||
"translucent-widgets-description": "Tedd a gombokat, füleket és egyéb widgeteket a paneleken belül félig átlátszóvá.",
|
||||
"translucent-widgets-label": "Áttetsző widgetek"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Ábécérend",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Diventa sostenitore",
|
||||
"changelog": "Visualizza changelog",
|
||||
"changelog-on-startup": "Mostra il changelog all'aggiornamento",
|
||||
"changelog-on-startup-desc": "Mostra automaticamente il changelog quando Noctalia viene aggiornato.",
|
||||
"contributors-description": "Un ringraziamento al nostro {count} contributore <b>fantastico</b>!",
|
||||
"contributors-description-plural": "Un ringraziamento ai nostri {count} contributori <b>fantastici</b>!",
|
||||
"copy-info": "Copia info",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Ombre esterne",
|
||||
"title": "Interfaccia utente",
|
||||
"tooltips-description": "Abilita o disabilita i tooltip in tutta l’interfaccia.",
|
||||
"tooltips-label": "Mostra tooltip"
|
||||
"tooltips-label": "Mostra tooltip",
|
||||
"translucent-widgets-description": "Rendi semitrasparenti i pulsanti, le schede e gli altri widget all'interno dei pannelli.",
|
||||
"translucent-widgets-label": "Widget traslucidi"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Alfabetico",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "サポーターになる",
|
||||
"changelog": "変更履歴を見る",
|
||||
"changelog-on-startup": "アップデート時に変更履歴を表示",
|
||||
"changelog-on-startup-desc": "Noctaliaが更新されたときに、自動的に変更履歴を表示する。",
|
||||
"contributors-description": "{count}人の<b>素晴らしい</b>コントリビューターに感謝!",
|
||||
"contributors-description-plural": "{count}人の<b>素晴らしい</b>コントリビューターに感謝!",
|
||||
"copy-info": "情報をコピー",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "ドロップシャドウ",
|
||||
"title": "ユーザーインターフェース",
|
||||
"tooltips-description": "インターフェース全体のツールチップの有効・無効を切り替えます。",
|
||||
"tooltips-label": "ツールチップを表示"
|
||||
"tooltips-label": "ツールチップを表示",
|
||||
"translucent-widgets-description": "パネル内のボタン、タブ、その他のウィジェットを半透明にする。",
|
||||
"translucent-widgets-label": "半透明のウィジェット"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "アルファベット順",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "후원자 되기",
|
||||
"changelog": "변경 로그 보기",
|
||||
"changelog-on-startup": "업데이트 시 변경 로그 표시",
|
||||
"changelog-on-startup-desc": "Noctalia 업데이트 시 변경 로그를 자동으로 표시합니다.",
|
||||
"contributors-description": "{count}명의 <b>멋진</b> 기여자에게 감사를 전합니다!",
|
||||
"contributors-description-plural": "{count}명의 <b>멋진</b> 기여자들에게 감사를 전합니다!",
|
||||
"copy-info": "정보 복사",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "그림자",
|
||||
"title": "사용자 인터페이스",
|
||||
"tooltips-description": "인터페이스 전반에 툴팁을 활성화하거나 비활성화합니다.",
|
||||
"tooltips-label": "툴팁 표시"
|
||||
"tooltips-label": "툴팁 표시",
|
||||
"translucent-widgets-description": "패널 내의 버튼, 탭 및 기타 위젯을 반투명하게 만듭니다.",
|
||||
"translucent-widgets-label": "반투명 위젯"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "알파벳순",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Word supporter",
|
||||
"changelog": "Bekijk wijzigingslogboek",
|
||||
"changelog-on-startup": "Toon wijzigingslogboek bij update",
|
||||
"changelog-on-startup-desc": "Toon automatisch de changelog wanneer Noctalia wordt bijgewerkt.",
|
||||
"contributors-description": "Een shout-out naar onze {count} <b>geweldige</b> bijdrager!",
|
||||
"contributors-description-plural": "Een shout-out naar onze {count} <b>geweldige</b> bijdragers!",
|
||||
"copy-info": "Kopieer info",
|
||||
@@ -1672,7 +1674,7 @@
|
||||
"scaling-description": "Wijzigt de grootte van de algemene gebruikersinterface, exclusief de balk.",
|
||||
"scaling-label": "Interfaceschaling",
|
||||
"scrollbar-always-visible-description": "Houd schuifbalken altijd zichtbaar wanneer inhoud scrollbaar is, in plaats van ze alleen bij hover te tonen.",
|
||||
"scrollbar-always-visible-label": "Altijd schuifbalken tonen",
|
||||
"scrollbar-always-visible-label": "Schuifbalken altijd weergeven",
|
||||
"settings-panel-header": "Instellingenpaneel",
|
||||
"settings-panel-mode-description": "Kies lay-out voor instellingen (mogelijk opnieuw openen vereist).",
|
||||
"settings-panel-mode-label": "Instellingenpaneelmodus",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Slagschaduwen",
|
||||
"title": "Gebruikersinterface",
|
||||
"tooltips-description": "Schakel tooltips in of uit in de hele interface.",
|
||||
"tooltips-label": "Tooltips tonen"
|
||||
"tooltips-label": "Tooltips tonen",
|
||||
"translucent-widgets-description": "Maak knoppen, tabbladen en andere widgets in panelen semi-transparant.",
|
||||
"translucent-widgets-label": "Doorzichtige widgets"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Alfabetisch",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Zostań wspierającym",
|
||||
"changelog": "Zobacz dziennik zmian",
|
||||
"changelog-on-startup": "Pokaż dziennik zmian po aktualizacji",
|
||||
"changelog-on-startup-desc": "Automatycznie pokazuj dziennik zmian po aktualizacji Noctalia.",
|
||||
"contributors-description": "Podziękowania dla naszego {count} <b>niesamowitego</b> współtwórcy!",
|
||||
"contributors-description-plural": "Podziękowania dla naszych {count} <b>niesamowitych</b> współtwórców!",
|
||||
"copy-info": "Kopiuj informacje",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Cienie",
|
||||
"title": "Interfejs użytkownika",
|
||||
"tooltips-description": "Włącz lub wyłącz podpowiedzi w całym interfejsie.",
|
||||
"tooltips-label": "Pokaż podpowiedzi"
|
||||
"tooltips-label": "Pokaż podpowiedzi",
|
||||
"translucent-widgets-description": "Spraw, aby przyciski, zakładki i inne widżety w panelach były półprzezroczyste.",
|
||||
"translucent-widgets-label": "Półprzezroczyste widżety"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Alfabetyczny",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Torne-se um apoiador",
|
||||
"changelog": "Ver histórico de alterações",
|
||||
"changelog-on-startup": "Mostrar changelog na atualização",
|
||||
"changelog-on-startup-desc": "Mostrar automaticamente o registo de alterações quando o Noctalia for atualizado.",
|
||||
"contributors-description": "Agradecimentos ao nosso <b>incrível</b> colaborador!",
|
||||
"contributors-description-plural": "Agradecimentos aos nossos {count} <b>incríveis</b> colaboradores!",
|
||||
"copy-info": "Copiar informações",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Sombras projetadas",
|
||||
"title": "Interface do usuário",
|
||||
"tooltips-description": "Ativar ou desativar dicas de ferramentas em toda a interface.",
|
||||
"tooltips-label": "Mostrar dicas de ferramenta"
|
||||
"tooltips-label": "Mostrar dicas de ferramenta",
|
||||
"translucent-widgets-description": "Torne botões, abas e outros widgets dentro dos painéis semitransparentes.",
|
||||
"translucent-widgets-label": "Widgets translúcidos"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Alfabético",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Стать спонсором",
|
||||
"changelog": "Посмотреть список изменений",
|
||||
"changelog-on-startup": "Показывать журнал изменений при обновлении",
|
||||
"changelog-on-startup-desc": "Автоматически показывать журнал изменений при обновлении Noctalia.",
|
||||
"contributors-description": "Благодарим нашего <b>замечательного</b> участника: {count}!",
|
||||
"contributors-description-plural": "Благодарим наших <b>замечательных</b> участников: {count}!",
|
||||
"copy-info": "Копировать информацию",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Отбрасываемые тени",
|
||||
"title": "Пользовательский интерфейс",
|
||||
"tooltips-description": "Включить или отключить всплывающие подсказки во всем интерфейсе.",
|
||||
"tooltips-label": "Показывать всплывающие подсказки"
|
||||
"tooltips-label": "Показывать всплывающие подсказки",
|
||||
"translucent-widgets-description": "Сделайте кнопки, вкладки и другие виджеты внутри панелей полупрозрачными.",
|
||||
"translucent-widgets-label": "Полупрозрачные виджеты"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Алфавитный",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Bli supporter",
|
||||
"changelog": "Visa ändringslogg",
|
||||
"changelog-on-startup": "Visa ändringslogg vid uppdatering",
|
||||
"changelog-on-startup-desc": "Visa ändringsloggen automatiskt när Noctalia uppdateras.",
|
||||
"contributors-description": "Tack till våra {count} <b>fantastiska</b> bidragsgivare!",
|
||||
"contributors-description-plural": "Tack till våra {count} <b>fantastiska</b> bidragsgivare!",
|
||||
"copy-info": "Kopiera info",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Fallskugga",
|
||||
"title": "Användargränssnitt",
|
||||
"tooltips-description": "Aktivera eller inaktivera verktygstips i hela gränssnittet.",
|
||||
"tooltips-label": "Visa verktygstips"
|
||||
"tooltips-label": "Visa verktygstips",
|
||||
"translucent-widgets-description": "Gör knappar, flikar och andra widgets inuti paneler halvtransparenta.",
|
||||
"translucent-widgets-label": "Genomskinliga widgetar"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Alfabetiskt",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Destekçi ol",
|
||||
"changelog": "Değişiklik günlüğünü görüntüle",
|
||||
"changelog-on-startup": "Güncellemede değişiklik günlüğünü göster",
|
||||
"changelog-on-startup-desc": "Noctalia güncellendiğinde değişiklik günlüğünü otomatik olarak göster.",
|
||||
"contributors-description": "{count} <b>harika</b> katılımcımıza <b>teşekkürler</b>!",
|
||||
"contributors-description-plural": "{count} <b>harika</b> katılımcımıza <b>teşekkürler</b>!",
|
||||
"copy-info": "Bilgileri kopyala",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Gölge efektleri",
|
||||
"title": "Kullanıcı arayüzü",
|
||||
"tooltips-description": "Arayüz genelindeki ipuçlarını etkinleştirin veya devre dışı bırakın.",
|
||||
"tooltips-label": "İpuçlarını göster"
|
||||
"tooltips-label": "İpuçlarını göster",
|
||||
"translucent-widgets-description": "Panellerin içindeki düğmeleri, sekmeleri ve diğer widget'ları yarı saydam yapın.",
|
||||
"translucent-widgets-label": "Yarı saydam widget'lar"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Alfabetik",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "Стати прихильником",
|
||||
"changelog": "Переглянути журнал змін",
|
||||
"changelog-on-startup": "Показувати журнал змін під час оновлення",
|
||||
"changelog-on-startup-desc": "Автоматично показувати журнал змін при оновленні Noctalia.",
|
||||
"contributors-description": "Подяка нашому {count} <b>чудовому</b> учаснику!",
|
||||
"contributors-description-plural": "Подяка нашим {count} <b>чудовим</b> учасникам!",
|
||||
"copy-info": "Копіювати інформацію",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "Тіні",
|
||||
"title": "Користувацький інтерфейс",
|
||||
"tooltips-description": "Увімкнути або вимкнути підказки в інтерфейсі.",
|
||||
"tooltips-label": "Показувати підказки"
|
||||
"tooltips-label": "Показувати підказки",
|
||||
"translucent-widgets-description": "Зробіть кнопки, вкладки та інші віджети всередині панелей напівпрозорими.",
|
||||
"translucent-widgets-label": "Напівпрозорі віджети"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "Алфавітний",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "成为支持者",
|
||||
"changelog": "查看更改日志",
|
||||
"changelog-on-startup": "更新时显示更新日志",
|
||||
"changelog-on-startup-desc": "Noctalia 更新时自动显示更新日志。",
|
||||
"contributors-description": "向我们 {count} 位<b>超棒的</b>贡献者致敬!",
|
||||
"contributors-description-plural": "向我们 {count} 位<b>超棒的</b>贡献者致敬!",
|
||||
"copy-info": "复制信息",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "阴影",
|
||||
"title": "用户界面",
|
||||
"tooltips-description": "启用或禁用整个界面的提示信息。",
|
||||
"tooltips-label": "显示提示信息(Tooltip)"
|
||||
"tooltips-label": "显示提示信息(Tooltip)",
|
||||
"translucent-widgets-description": "使面板内的按钮、选项卡和其他小部件半透明。",
|
||||
"translucent-widgets-label": "半透明小部件"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "按字母顺序",
|
||||
|
||||
@@ -714,6 +714,8 @@
|
||||
"about": {
|
||||
"become-supporter": "成為支持者",
|
||||
"changelog": "檢視更新日誌",
|
||||
"changelog-on-startup": "更新時顯示變更日誌",
|
||||
"changelog-on-startup-desc": "Noctalia 更新時自動顯示更新日誌。",
|
||||
"contributors-description": "特別感謝我們這{count}位<b>超讚</b>的貢獻者!!",
|
||||
"contributors-description-plural": "特別感謝我們這{count}位<b>超讚</b>的貢獻者!!",
|
||||
"copy-info": "複製資訊",
|
||||
@@ -1684,7 +1686,9 @@
|
||||
"shadows-label": "陰影",
|
||||
"title": "使用者介面",
|
||||
"tooltips-description": "在整個介面啟用或停用提示框",
|
||||
"tooltips-label": "顯示提示框"
|
||||
"tooltips-label": "顯示提示框",
|
||||
"translucent-widgets-description": "使面板內的按鈕、分頁和其他小部件半透明。",
|
||||
"translucent-widgets-label": "半透明小工具"
|
||||
},
|
||||
"wallpaper": {
|
||||
"automation-change-mode-alphabetical": "照字母排序",
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
"scrollbarAlwaysVisible": true,
|
||||
"boxBorderEnabled": false,
|
||||
"panelBackgroundOpacity": 0.93,
|
||||
"translucentWidgets": false,
|
||||
"panelsAttachedToBar": true,
|
||||
"settingsPanelMode": "attached",
|
||||
"settingsPanelSideBarCardStyle": false
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"labelKey": "panels.about.changelog-on-startup",
|
||||
"descriptionKey": "panels.about.changelog-on-startup-desc",
|
||||
"widget": "NToggle",
|
||||
"tab": 21,
|
||||
"tabLabel": "panels.about.title",
|
||||
"subTab": 0,
|
||||
"subTabLabel": "common.info"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.about.telemetry-enabled",
|
||||
"descriptionKey": "panels.about.telemetry-desc",
|
||||
@@ -1971,6 +1980,15 @@
|
||||
"subTab": 0,
|
||||
"subTabLabel": "common.appearance"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.user-interface.translucent-widgets-label",
|
||||
"descriptionKey": "panels.user-interface.translucent-widgets-description",
|
||||
"widget": "NToggle",
|
||||
"tab": 1,
|
||||
"tabLabel": "panels.user-interface.title",
|
||||
"subTab": 0,
|
||||
"subTabLabel": "common.appearance"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.user-interface.shadows-direction-label",
|
||||
"descriptionKey": "panels.user-interface.shadows-direction-description",
|
||||
|
||||
@@ -324,6 +324,20 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
// Smart alpha calculation: automatically makes light mode more transparent
|
||||
function smartAlpha(baseColor, minAlpha = 0.4) {
|
||||
if (!Settings.data.ui.translucentWidgets)
|
||||
return baseColor;
|
||||
|
||||
let baseOpacity = Settings.data.ui.panelBackgroundOpacity;
|
||||
let targetOpacity = Settings.data.colorSchemes.darkMode ? baseOpacity : Math.pow(baseOpacity, 2);
|
||||
let alpha = Math.max(targetOpacity, minAlpha);
|
||||
|
||||
// Combine with the base color's existing alpha
|
||||
let resultAlpha = Math.max(0, baseColor.a - (1.0 - alpha));
|
||||
return Qt.alpha(baseColor, resultAlpha);
|
||||
}
|
||||
|
||||
readonly property var colorKeyModel: [
|
||||
{
|
||||
"key": "none",
|
||||
|
||||
@@ -330,6 +330,7 @@ Singleton {
|
||||
property bool scrollbarAlwaysVisible: true
|
||||
property bool boxBorderEnabled: false
|
||||
property real panelBackgroundOpacity: 0.93
|
||||
property bool translucentWidgets: false
|
||||
property bool panelsAttachedToBar: true
|
||||
property string settingsPanelMode: "attached" // "centered", "attached", "window"
|
||||
property bool settingsPanelSideBarCardStyle: false
|
||||
|
||||
+6
-11
@@ -109,18 +109,13 @@ Singleton {
|
||||
save();
|
||||
}
|
||||
|
||||
// Set a usage count directly (used for migration/merging)
|
||||
function recordLauncherUsageMerge(key, count) {
|
||||
// Migrate usage from one key to another, merging counts in a single save
|
||||
function migrateLauncherUsage(fromKey, toKey) {
|
||||
let counts = Object.assign({}, adapter.launcherUsage || {});
|
||||
counts[key] = count;
|
||||
adapter.launcherUsage = counts;
|
||||
save();
|
||||
}
|
||||
|
||||
// Remove a usage key (used for cleaning up legacy keys after migration)
|
||||
function clearLauncherUsage(key) {
|
||||
let counts = Object.assign({}, adapter.launcherUsage || {});
|
||||
delete counts[key];
|
||||
const fromCount = typeof counts[fromKey] === 'number' && isFinite(counts[fromKey]) ? counts[fromKey] : 0;
|
||||
const toCount = typeof counts[toKey] === 'number' && isFinite(counts[toKey]) ? counts[toKey] : 0;
|
||||
counts[toKey] = toCount + fromCount;
|
||||
delete counts[fromKey];
|
||||
adapter.launcherUsage = counts;
|
||||
save();
|
||||
}
|
||||
|
||||
+30
-35
@@ -26,8 +26,18 @@ var constants = {
|
||||
// Safe evaluation function that handles advanced math
|
||||
function evaluate(expression) {
|
||||
try {
|
||||
// Replace mathematical constants
|
||||
var processed = expression
|
||||
// Fixes decimal arithmetic
|
||||
var cleanExpr = expression.replace(/\s+/g, '').toLowerCase();
|
||||
|
||||
// Allows numbers (including decimals), basic operators, and explicitly permitted math terms only
|
||||
var safeRegex = /^(\d*\.?\d+|[+\-*/()^%,]|sin|cos|tan|asin|acos|atan|atan2|sinh|cosh|tanh|asinh|acosh|atanh|log|ln|exp|pow|sqrt|cbrt|abs|floor|ceil|round|trunc|min|max|random|pi|e|sind|cosd|tand)+$/;
|
||||
|
||||
if (!safeRegex.test(cleanExpr)) {
|
||||
throw new Error("Invalid characters or unauthorized functions in expression");
|
||||
}
|
||||
|
||||
// Replace mathematical constants (Original Structure)
|
||||
var processed = cleanExpr
|
||||
.replace(/\bpi\b/gi, Math.PI)
|
||||
.replace(/\be\b/gi, Math.E);
|
||||
|
||||
@@ -41,7 +51,7 @@ function evaluate(expression) {
|
||||
.replace(/\bacos\s*\(/g, 'Math.acos(')
|
||||
.replace(/\batan\s*\(/g, 'Math.atan(')
|
||||
.replace(/\batan2\s*\(/g, 'Math.atan2(')
|
||||
|
||||
|
||||
// Hyperbolic functions
|
||||
.replace(/\bsinh\s*\(/g, 'Math.sinh(')
|
||||
.replace(/\bcosh\s*\(/g, 'Math.cosh(')
|
||||
@@ -49,28 +59,28 @@ function evaluate(expression) {
|
||||
.replace(/\basinh\s*\(/g, 'Math.asinh(')
|
||||
.replace(/\bacosh\s*\(/g, 'Math.acosh(')
|
||||
.replace(/\batanh\s*\(/g, 'Math.atanh(')
|
||||
|
||||
|
||||
// Logarithmic and exponential functions
|
||||
.replace(/\blog\s*\(/g, 'Math.log10(')
|
||||
.replace(/\bln\s*\(/g, 'Math.log(')
|
||||
.replace(/\bexp\s*\(/g, 'Math.exp(')
|
||||
.replace(/\bpow\s*\(/g, 'Math.pow(')
|
||||
|
||||
|
||||
// Root functions
|
||||
.replace(/\bsqrt\s*\(/g, 'Math.sqrt(')
|
||||
.replace(/\bcbrt\s*\(/g, 'Math.cbrt(')
|
||||
|
||||
|
||||
// Rounding and absolute
|
||||
.replace(/\babs\s*\(/g, 'Math.abs(')
|
||||
.replace(/\bfloor\s*\(/g, 'Math.floor(')
|
||||
.replace(/\bceil\s*\(/g, 'Math.ceil(')
|
||||
.replace(/\bround\s*\(/g, 'Math.round(')
|
||||
.replace(/\btrunc\s*\(/g, 'Math.trunc(')
|
||||
|
||||
|
||||
// Min/Max
|
||||
.replace(/\bmin\s*\(/g, 'Math.min(')
|
||||
.replace(/\bmax\s*\(/g, 'Math.max(')
|
||||
|
||||
|
||||
// Random
|
||||
.replace(/\brandom\s*\(\s*\)/g, 'Math.random()');
|
||||
|
||||
@@ -83,25 +93,10 @@ function evaluate(expression) {
|
||||
// Handle ^ for exponentiation: convert 2^3 to Math.pow(2,3)
|
||||
processed = processed.replace(/([\d.]+|\))\^([\d.]+|\([^)]*\))/g, 'Math.pow($1,$2)');
|
||||
|
||||
// Sanitize expression (only allow safe characters)
|
||||
if (!/^[0-9+\-*/().\s\w,]+$/.test(processed)) {
|
||||
throw new Error("Invalid characters in expression");
|
||||
}
|
||||
// Replacing eval() with a scoped function constructor
|
||||
// This is safe because the strict whitelist guarantees only math reaches this point
|
||||
var result = new Function('return ' + processed)();
|
||||
|
||||
// Block dangerous identifiers (prototype chain traversal, code execution)
|
||||
if (/\b(constructor|prototype|__proto__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|Function|eval|require|import|process|global|window|this|self|globalThis|String|Object|Array|RegExp|Proxy|Reflect|setTimeout|setInterval)\b/.test(processed)) {
|
||||
throw new Error("Invalid expression");
|
||||
}
|
||||
|
||||
// Only allow Math.method property access - block any other dot-property chains
|
||||
var withoutMathCalls = processed.replace(/\bMath\.\w+/g, '0');
|
||||
if (/\./.test(withoutMathCalls)) {
|
||||
throw new Error("Invalid expression");
|
||||
}
|
||||
|
||||
// Evaluate the processed expression
|
||||
var result = eval(processed);
|
||||
|
||||
if (!isFinite(result) || isNaN(result)) {
|
||||
throw new Error("Invalid result");
|
||||
}
|
||||
@@ -117,12 +112,12 @@ function formatResult(result) {
|
||||
if (Number.isInteger(result)) {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
// Handle very large or very small numbers
|
||||
if (Math.abs(result) >= 1e15 || (Math.abs(result) < 1e-6 && result !== 0)) {
|
||||
return result.toExponential(6);
|
||||
}
|
||||
|
||||
|
||||
// Normal decimal formatting
|
||||
return parseFloat(result.toFixed(10)).toString();
|
||||
}
|
||||
@@ -131,35 +126,35 @@ function formatResult(result) {
|
||||
function getAvailableFunctions() {
|
||||
return [
|
||||
// Basic arithmetic: +, -, *, /, %, ^, ()
|
||||
|
||||
|
||||
// Trigonometric functions
|
||||
"sin(x), cos(x), tan(x) - trigonometric functions (radians)",
|
||||
"sind(x), cosd(x), tand(x) - trigonometric functions (degrees)",
|
||||
"asin(x), acos(x), atan(x) - inverse trigonometric",
|
||||
"atan2(y, x) - two-argument arctangent",
|
||||
|
||||
|
||||
// Hyperbolic functions
|
||||
"sinh(x), cosh(x), tanh(x) - hyperbolic functions",
|
||||
"asinh(x), acosh(x), atanh(x) - inverse hyperbolic",
|
||||
|
||||
|
||||
// Logarithmic and exponential
|
||||
"log(x) - base 10 logarithm",
|
||||
"ln(x) - natural logarithm",
|
||||
"exp(x) - e^x",
|
||||
"pow(x, y) - x^y",
|
||||
|
||||
|
||||
// Root functions
|
||||
"sqrt(x) - square root",
|
||||
"cbrt(x) - cube root",
|
||||
|
||||
|
||||
// Rounding and absolute
|
||||
"abs(x) - absolute value",
|
||||
"floor(x), ceil(x), round(x), trunc(x)",
|
||||
|
||||
|
||||
// Min/Max/Random
|
||||
"min(a, b, ...), max(a, b, ...)",
|
||||
"random() - random number 0-1",
|
||||
|
||||
|
||||
// Constants
|
||||
"pi, e - mathematical constants"
|
||||
];
|
||||
|
||||
@@ -44,6 +44,7 @@ Item {
|
||||
id: unifiedBackgroundsShape
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
asynchronous: true
|
||||
enabled: false
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -111,6 +112,7 @@ Item {
|
||||
id: panelBackgroundsShape
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
asynchronous: true
|
||||
enabled: false
|
||||
|
||||
/**
|
||||
@@ -159,6 +161,7 @@ Item {
|
||||
id: barBackgroundShape
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
asynchronous: true
|
||||
enabled: false
|
||||
|
||||
BarBackground {
|
||||
|
||||
@@ -25,6 +25,7 @@ Item {
|
||||
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
asynchronous: true
|
||||
enabled: false // Disable mouse input
|
||||
visible: cornersPath.cornerRadius > 0 && width > 0 && height > 0
|
||||
|
||||
|
||||
@@ -28,8 +28,13 @@ Variants {
|
||||
|
||||
property ListModel notificationModel: NotificationService.activeList
|
||||
|
||||
// Always create window (but with 0x0 dimensions when no notifications)
|
||||
active: notificationModel.count > 0 || delayTimer.running
|
||||
// Deferred activation to prevent re-entrant QML incubation crash.
|
||||
// Direct binding to notificationModel.count would activate the Loader
|
||||
// synchronously during ListModel.insert(), causing nested incubation
|
||||
// (Loader + inner Repeater both processing the model) which crashes
|
||||
// the V4 engine in QV4::Object::insertMember.
|
||||
property bool shouldBeActive: false
|
||||
active: shouldBeActive || delayTimer.running
|
||||
|
||||
// Keep loader active briefly after last notification to allow animations to complete
|
||||
Timer {
|
||||
@@ -38,10 +43,23 @@ Variants {
|
||||
repeat: false
|
||||
}
|
||||
|
||||
// Deferred activation timer - activates Loader on next event loop iteration
|
||||
Timer {
|
||||
id: activationTimer
|
||||
interval: 0
|
||||
repeat: false
|
||||
onTriggered: root.shouldBeActive = true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: notificationModel
|
||||
function onCountChanged() {
|
||||
if (notificationModel.count === 0 && root.active) {
|
||||
if (notificationModel.count > 0) {
|
||||
if (!root.shouldBeActive) {
|
||||
activationTimer.restart();
|
||||
}
|
||||
} else if (root.shouldBeActive) {
|
||||
root.shouldBeActive = false;
|
||||
delayTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
function selectNext(selectedIndex, resultsLength) {
|
||||
if (resultsLength > 0 && selectedIndex < resultsLength - 1)
|
||||
return selectedIndex + 1;
|
||||
return selectedIndex;
|
||||
}
|
||||
|
||||
function selectPrevious(selectedIndex, resultsLength) {
|
||||
if (resultsLength > 0 && selectedIndex > 0)
|
||||
return selectedIndex - 1;
|
||||
return selectedIndex;
|
||||
}
|
||||
|
||||
function selectNextWrapped(selectedIndex, resultsLength, allowWrap) {
|
||||
if (resultsLength > 0) {
|
||||
if (allowWrap)
|
||||
return (selectedIndex + 1) % resultsLength;
|
||||
return selectNext(selectedIndex, resultsLength);
|
||||
}
|
||||
return selectedIndex;
|
||||
}
|
||||
|
||||
function selectPreviousWrapped(selectedIndex, resultsLength, allowWrap) {
|
||||
if (resultsLength > 0) {
|
||||
if (allowWrap)
|
||||
return (((selectedIndex - 1) % resultsLength) + resultsLength) % resultsLength;
|
||||
return selectPrevious(selectedIndex, resultsLength);
|
||||
}
|
||||
return selectedIndex;
|
||||
}
|
||||
|
||||
function selectFirst() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function selectLast(resultsLength) {
|
||||
return resultsLength > 0 ? resultsLength - 1 : 0;
|
||||
}
|
||||
|
||||
function selectNextPage(selectedIndex, resultsLength, entryHeight) {
|
||||
if (resultsLength > 0) {
|
||||
var page = Math.max(1, Math.floor(600 / entryHeight));
|
||||
return Math.min(selectedIndex + page, resultsLength - 1);
|
||||
}
|
||||
return selectedIndex;
|
||||
}
|
||||
|
||||
function selectPreviousPage(selectedIndex, resultsLength, entryHeight) {
|
||||
if (resultsLength > 0) {
|
||||
var page = Math.max(1, Math.floor(600 / entryHeight));
|
||||
return Math.max(selectedIndex - page, 0);
|
||||
}
|
||||
return selectedIndex;
|
||||
}
|
||||
|
||||
function selectPreviousRow(selectedIndex, resultsLength, gridColumns) {
|
||||
if (resultsLength <= 0 || gridColumns <= 0)
|
||||
return selectedIndex;
|
||||
|
||||
var currentRow = Math.floor(selectedIndex / gridColumns);
|
||||
var currentCol = selectedIndex % gridColumns;
|
||||
|
||||
if (currentRow > 0) {
|
||||
var targetRow = currentRow - 1;
|
||||
var itemsInTargetRow = Math.min(gridColumns, resultsLength - targetRow * gridColumns);
|
||||
if (currentCol < itemsInTargetRow)
|
||||
return targetRow * gridColumns + currentCol;
|
||||
return targetRow * gridColumns + itemsInTargetRow - 1;
|
||||
}
|
||||
|
||||
// Wrap to last row, same column
|
||||
var totalRows = Math.ceil(resultsLength / gridColumns);
|
||||
var lastRow = totalRows - 1;
|
||||
var itemsInLastRow = Math.min(gridColumns, resultsLength - lastRow * gridColumns);
|
||||
if (currentCol < itemsInLastRow)
|
||||
return lastRow * gridColumns + currentCol;
|
||||
return resultsLength - 1;
|
||||
}
|
||||
|
||||
function selectNextRow(selectedIndex, resultsLength, gridColumns) {
|
||||
if (resultsLength <= 0 || gridColumns <= 0)
|
||||
return selectedIndex;
|
||||
|
||||
var currentRow = Math.floor(selectedIndex / gridColumns);
|
||||
var currentCol = selectedIndex % gridColumns;
|
||||
var totalRows = Math.ceil(resultsLength / gridColumns);
|
||||
|
||||
if (currentRow < totalRows - 1) {
|
||||
var targetRow = currentRow + 1;
|
||||
var targetIndex = targetRow * gridColumns + currentCol;
|
||||
if (targetIndex < resultsLength)
|
||||
return targetIndex;
|
||||
var itemsInTargetRow = resultsLength - targetRow * gridColumns;
|
||||
if (itemsInTargetRow > 0)
|
||||
return targetRow * gridColumns + itemsInTargetRow - 1;
|
||||
return Math.min(currentCol, resultsLength - 1);
|
||||
}
|
||||
|
||||
// Wrap to first row, same column
|
||||
return Math.min(currentCol, resultsLength - 1);
|
||||
}
|
||||
|
||||
function selectPreviousColumn(selectedIndex, resultsLength, gridColumns) {
|
||||
if (resultsLength <= 0)
|
||||
return selectedIndex;
|
||||
|
||||
var currentRow = Math.floor(selectedIndex / gridColumns);
|
||||
var currentCol = selectedIndex % gridColumns;
|
||||
|
||||
if (currentCol > 0)
|
||||
return currentRow * gridColumns + (currentCol - 1);
|
||||
if (currentRow > 0)
|
||||
return (currentRow - 1) * gridColumns + (gridColumns - 1);
|
||||
|
||||
var totalRows = Math.ceil(resultsLength / gridColumns);
|
||||
var lastRowIndex = (totalRows - 1) * gridColumns + (gridColumns - 1);
|
||||
return Math.min(lastRowIndex, resultsLength - 1);
|
||||
}
|
||||
|
||||
function selectNextColumn(selectedIndex, resultsLength, gridColumns) {
|
||||
if (resultsLength <= 0)
|
||||
return selectedIndex;
|
||||
|
||||
var currentRow = Math.floor(selectedIndex / gridColumns);
|
||||
var currentCol = selectedIndex % gridColumns;
|
||||
var itemsInCurrentRow = Math.min(gridColumns, resultsLength - currentRow * gridColumns);
|
||||
|
||||
if (currentCol < itemsInCurrentRow - 1)
|
||||
return currentRow * gridColumns + (currentCol + 1);
|
||||
|
||||
var totalRows = Math.ceil(resultsLength / gridColumns);
|
||||
if (currentRow < totalRows - 1)
|
||||
return (currentRow + 1) * gridColumns;
|
||||
return 0;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import "Helpers/LauncherNavigation.js" as LauncherNav
|
||||
|
||||
import "Providers"
|
||||
import qs.Commons
|
||||
@@ -311,11 +312,14 @@ Rectangle {
|
||||
let sb = b._score !== undefined ? b._score : 0;
|
||||
|
||||
// Boost scores for frequently used items from tracked providers
|
||||
// _score is normalized 0–1, so boost is scaled to nudge, not overwhelm
|
||||
if (boostByUsage) {
|
||||
if (a.provider && a.provider.trackUsage && a.usageKey)
|
||||
sa += 100.0 * Math.log2(1 + ShellState.getLauncherUsageCount(a.usageKey));
|
||||
if (b.provider && b.provider.trackUsage && b.usageKey)
|
||||
sb += 100.0 * Math.log2(1 + ShellState.getLauncherUsageCount(b.usageKey));
|
||||
if (a.provider && a.provider.trackUsage && a.usageKey) {
|
||||
sa += 0.1 * Math.log2(1 + ShellState.getLauncherUsageCount(a.usageKey));
|
||||
}
|
||||
if (b.provider && b.provider.trackUsage && b.usageKey) {
|
||||
sb += 0.1 * Math.log2(1 + ShellState.getLauncherUsageCount(b.usageKey));
|
||||
}
|
||||
}
|
||||
|
||||
return sb - sa;
|
||||
@@ -329,149 +333,42 @@ Rectangle {
|
||||
selectedIndex = 0;
|
||||
}
|
||||
|
||||
// Navigation functions
|
||||
// Navigation functions (delegated to LauncherNavigation.js)
|
||||
function selectNext() {
|
||||
if (results.length > 0 && selectedIndex < results.length - 1) {
|
||||
selectedIndex++;
|
||||
}
|
||||
selectedIndex = LauncherNav.selectNext(selectedIndex, results.length);
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (results.length > 0 && selectedIndex > 0) {
|
||||
selectedIndex--;
|
||||
}
|
||||
selectedIndex = LauncherNav.selectPrevious(selectedIndex, results.length);
|
||||
}
|
||||
|
||||
function selectNextWrapped() {
|
||||
if (results.length > 0) {
|
||||
if (allowWrapNavigation) {
|
||||
selectedIndex = (selectedIndex + 1) % results.length;
|
||||
} else {
|
||||
selectNext();
|
||||
}
|
||||
}
|
||||
selectedIndex = LauncherNav.selectNextWrapped(selectedIndex, results.length, allowWrapNavigation);
|
||||
}
|
||||
|
||||
function selectPreviousWrapped() {
|
||||
if (results.length > 0) {
|
||||
if (allowWrapNavigation) {
|
||||
selectedIndex = (((selectedIndex - 1) % results.length) + results.length) % results.length;
|
||||
} else {
|
||||
selectPrevious();
|
||||
}
|
||||
}
|
||||
selectedIndex = LauncherNav.selectPreviousWrapped(selectedIndex, results.length, allowWrapNavigation);
|
||||
}
|
||||
|
||||
function selectFirst() {
|
||||
selectedIndex = 0;
|
||||
selectedIndex = LauncherNav.selectFirst();
|
||||
}
|
||||
|
||||
function selectLast() {
|
||||
selectedIndex = results.length > 0 ? results.length - 1 : 0;
|
||||
selectedIndex = LauncherNav.selectLast(results.length);
|
||||
}
|
||||
|
||||
function selectNextPage() {
|
||||
if (results.length > 0) {
|
||||
const page = Math.max(1, Math.floor(600 / entryHeight));
|
||||
selectedIndex = Math.min(selectedIndex + page, results.length - 1);
|
||||
}
|
||||
selectedIndex = LauncherNav.selectNextPage(selectedIndex, results.length, entryHeight);
|
||||
}
|
||||
|
||||
function selectPreviousPage() {
|
||||
if (results.length > 0) {
|
||||
const page = Math.max(1, Math.floor(600 / entryHeight));
|
||||
selectedIndex = Math.max(selectedIndex - page, 0);
|
||||
}
|
||||
selectedIndex = LauncherNav.selectPreviousPage(selectedIndex, results.length, entryHeight);
|
||||
}
|
||||
|
||||
// Grid view navigation functions
|
||||
function selectPreviousRow() {
|
||||
if (results.length > 0 && isGridView && gridColumns > 0) {
|
||||
const currentRow = Math.floor(selectedIndex / gridColumns);
|
||||
const currentCol = selectedIndex % gridColumns;
|
||||
|
||||
if (currentRow > 0) {
|
||||
const targetRow = currentRow - 1;
|
||||
const targetIndex = targetRow * gridColumns + currentCol;
|
||||
const itemsInTargetRow = Math.min(gridColumns, results.length - targetRow * gridColumns);
|
||||
if (currentCol < itemsInTargetRow) {
|
||||
selectedIndex = targetIndex;
|
||||
} else {
|
||||
selectedIndex = targetRow * gridColumns + itemsInTargetRow - 1;
|
||||
}
|
||||
} else {
|
||||
// Wrap to last row, same column
|
||||
const totalRows = Math.ceil(results.length / gridColumns);
|
||||
const lastRow = totalRows - 1;
|
||||
const itemsInLastRow = Math.min(gridColumns, results.length - lastRow * gridColumns);
|
||||
if (currentCol < itemsInLastRow) {
|
||||
selectedIndex = lastRow * gridColumns + currentCol;
|
||||
} else {
|
||||
selectedIndex = results.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
selectedIndex = LauncherNav.selectPreviousRow(selectedIndex, results.length, gridColumns);
|
||||
}
|
||||
|
||||
function selectNextRow() {
|
||||
if (results.length > 0 && isGridView && gridColumns > 0) {
|
||||
const currentRow = Math.floor(selectedIndex / gridColumns);
|
||||
const currentCol = selectedIndex % gridColumns;
|
||||
const totalRows = Math.ceil(results.length / gridColumns);
|
||||
|
||||
if (currentRow < totalRows - 1) {
|
||||
const targetRow = currentRow + 1;
|
||||
const targetIndex = targetRow * gridColumns + currentCol;
|
||||
if (targetIndex < results.length) {
|
||||
selectedIndex = targetIndex;
|
||||
} else {
|
||||
const itemsInTargetRow = results.length - targetRow * gridColumns;
|
||||
if (itemsInTargetRow > 0) {
|
||||
selectedIndex = targetRow * gridColumns + itemsInTargetRow - 1;
|
||||
} else {
|
||||
selectedIndex = Math.min(currentCol, results.length - 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Wrap to first row, same column
|
||||
selectedIndex = Math.min(currentCol, results.length - 1);
|
||||
}
|
||||
}
|
||||
selectedIndex = LauncherNav.selectNextRow(selectedIndex, results.length, gridColumns);
|
||||
}
|
||||
|
||||
function selectPreviousColumn() {
|
||||
if (results.length > 0 && isGridView) {
|
||||
const currentRow = Math.floor(selectedIndex / gridColumns);
|
||||
const currentCol = selectedIndex % gridColumns;
|
||||
if (currentCol > 0) {
|
||||
selectedIndex = currentRow * gridColumns + (currentCol - 1);
|
||||
} else if (currentRow > 0) {
|
||||
selectedIndex = (currentRow - 1) * gridColumns + (gridColumns - 1);
|
||||
} else {
|
||||
const totalRows = Math.ceil(results.length / gridColumns);
|
||||
const lastRowIndex = (totalRows - 1) * gridColumns + (gridColumns - 1);
|
||||
selectedIndex = Math.min(lastRowIndex, results.length - 1);
|
||||
}
|
||||
}
|
||||
selectedIndex = LauncherNav.selectPreviousColumn(selectedIndex, results.length, gridColumns);
|
||||
}
|
||||
|
||||
function selectNextColumn() {
|
||||
if (results.length > 0 && isGridView) {
|
||||
const currentRow = Math.floor(selectedIndex / gridColumns);
|
||||
const currentCol = selectedIndex % gridColumns;
|
||||
const itemsInCurrentRow = Math.min(gridColumns, results.length - currentRow * gridColumns);
|
||||
|
||||
if (currentCol < itemsInCurrentRow - 1) {
|
||||
selectedIndex = currentRow * gridColumns + (currentCol + 1);
|
||||
} else {
|
||||
const totalRows = Math.ceil(results.length / gridColumns);
|
||||
if (currentRow < totalRows - 1) {
|
||||
selectedIndex = (currentRow + 1) * gridColumns;
|
||||
} else {
|
||||
selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
selectedIndex = LauncherNav.selectNextColumn(selectedIndex, results.length, gridColumns);
|
||||
}
|
||||
|
||||
function activate() {
|
||||
@@ -800,7 +697,7 @@ Rectangle {
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AlwaysOff
|
||||
reserveScrollbarSpace: false
|
||||
gradientColor: Settings.data.ui.panelBackgroundOpacity < 1 ? "transparent" : Color.mSurfaceVariant
|
||||
gradientColor: Settings.data.ui.panelBackgroundOpacity < 1 ? "transparent" : Color.mSurface
|
||||
wheelScrollMultiplier: 4.0
|
||||
|
||||
width: parent.width
|
||||
@@ -818,276 +715,8 @@ Rectangle {
|
||||
}
|
||||
onModelChanged: {}
|
||||
|
||||
delegate: NBox {
|
||||
id: entry
|
||||
|
||||
property bool isSelected: (!root.ignoreMouseHover && mouseArea.containsMouse) || (index === root.selectedIndex)
|
||||
|
||||
width: resultsList.availableWidth
|
||||
implicitHeight: root.entryHeight
|
||||
clip: true
|
||||
color: entry.isSelected ? Color.mHover : Color.mSurface
|
||||
forceOpaque: entry.isSelected
|
||||
|
||||
// Prepare item when it becomes visible (e.g., decode images)
|
||||
Component.onCompleted: {
|
||||
var provider = modelData.provider;
|
||||
if (provider && provider.prepareItem) {
|
||||
provider.prepareItem(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCirc
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.isCompactDensity ? Style.marginXS : Style.marginM
|
||||
spacing: root.isCompactDensity ? Style.marginXS : Style.marginM
|
||||
|
||||
// Top row - Main entry content with action buttons
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: root.isCompactDensity ? Style.marginS : Style.marginM
|
||||
|
||||
// Icon badge or Image preview or Emoji
|
||||
Item {
|
||||
visible: !modelData.hideIcon
|
||||
Layout.preferredWidth: modelData.hideIcon ? 0 : root.badgeSize
|
||||
Layout.preferredHeight: modelData.hideIcon ? 0 : root.badgeSize
|
||||
|
||||
// Icon background
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Style.radiusXS
|
||||
color: Color.mSurfaceVariant
|
||||
visible: Settings.data.appLauncher.showIconBackground && !modelData.isImage
|
||||
}
|
||||
|
||||
// Image preview - uses provider's getImageUrl if available
|
||||
NImageRounded {
|
||||
id: imagePreview
|
||||
anchors.fill: parent
|
||||
visible: !!modelData.isImage && !modelData.displayString
|
||||
radius: Style.radiusXS
|
||||
borderColor: Color.mOnSurface
|
||||
borderWidth: Style.borderM
|
||||
imageFillMode: Image.PreserveAspectCrop
|
||||
|
||||
// Use provider's image revision for reactive updates
|
||||
readonly property int _rev: modelData.provider && modelData.provider.imageRevision ? modelData.provider.imageRevision : 0
|
||||
|
||||
// Get image URL from provider
|
||||
imagePath: {
|
||||
_rev;
|
||||
var provider = modelData.provider;
|
||||
if (provider && provider.getImageUrl) {
|
||||
return provider.getImageUrl(modelData);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: parent.status === Image.Loading
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
running: true
|
||||
width: Style.baseWidgetSize * 0.5
|
||||
height: width
|
||||
}
|
||||
}
|
||||
|
||||
onStatusChanged: status => {
|
||||
if (status === Image.Error) {
|
||||
iconLoader.visible = true;
|
||||
imagePreview.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: iconLoader
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS
|
||||
|
||||
visible: (!modelData.isImage && !modelData.displayString) || (!!modelData.isImage && imagePreview.status === Image.Error)
|
||||
active: visible
|
||||
|
||||
sourceComponent: Component {
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: Settings.data.appLauncher.iconMode === "tabler" && modelData.isTablerIcon ? tablerIconComponent : systemIconComponent
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: tablerIconComponent
|
||||
NIcon {
|
||||
icon: modelData.icon
|
||||
pointSize: Style.fontSizeXXXL
|
||||
visible: modelData.icon && !modelData.displayString
|
||||
color: (entry.isSelected && !Settings.data.appLauncher.showIconBackground) ? Color.mOnHover : Color.mOnSurface
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: systemIconComponent
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : ""
|
||||
visible: modelData.icon && source !== "" && !modelData.displayString
|
||||
asynchronous: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String display - takes precedence when displayString is present
|
||||
NText {
|
||||
id: stringDisplay
|
||||
anchors.centerIn: parent
|
||||
visible: !!modelData.displayString || (!imagePreview.visible && !iconLoader.visible)
|
||||
text: modelData.displayString ? modelData.displayString : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?")
|
||||
pointSize: modelData.displayString ? (modelData.displayStringSize || Style.fontSizeXXXL) : Style.fontSizeXXL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: modelData.displayString ? Color.mOnSurface : Color.mOnPrimary
|
||||
}
|
||||
|
||||
// Image type indicator overlay
|
||||
Rectangle {
|
||||
visible: !!modelData.isImage && imagePreview.visible
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 2
|
||||
width: formatLabel.width + Style.marginXS
|
||||
height: formatLabel.height + Style.marginXXS
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusXXS
|
||||
NText {
|
||||
id: formatLabel
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!modelData.isImage)
|
||||
return "";
|
||||
const desc = modelData.description || "";
|
||||
const parts = desc.split(" \u2022 ");
|
||||
return parts[0] || "IMG";
|
||||
}
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
// Badge icon overlay (generic indicator for any provider)
|
||||
Rectangle {
|
||||
visible: !!modelData.badgeIcon
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 2
|
||||
width: height
|
||||
height: Style.fontSizeM + Style.marginXS
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusXXS
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: modelData.badgeIcon || ""
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text content
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
NText {
|
||||
text: modelData.name || "Unknown"
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: entry.isSelected ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
wrapMode: Text.Wrap
|
||||
clip: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.description || ""
|
||||
pointSize: Style.fontSizeS
|
||||
color: entry.isSelected ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
Layout.fillWidth: true
|
||||
visible: text !== "" && !root.isCompactDensity
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons row - dynamically populated from provider
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
spacing: Style.marginXS
|
||||
visible: entry.isSelected && itemActions.length > 0
|
||||
|
||||
property var itemActions: {
|
||||
if (!entry.isSelected)
|
||||
return [];
|
||||
var provider = modelData.provider || root.currentProvider;
|
||||
if (provider && provider.getItemActions) {
|
||||
return provider.getItemActions(modelData);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: parent.itemActions
|
||||
NIconButton {
|
||||
icon: modelData.icon
|
||||
baseSize: Style.baseWidgetSize * 0.75
|
||||
tooltipText: modelData.tooltip
|
||||
z: 1
|
||||
handleWheel: true
|
||||
onClicked: {
|
||||
if (modelData.action) {
|
||||
modelData.action();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: !Settings.data.appLauncher.ignoreMouseInput
|
||||
onEntered: {
|
||||
if (!root.ignoreMouseHover) {
|
||||
root.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.selectedIndex = index;
|
||||
root.activate();
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
acceptedButtons: Qt.LeftButton
|
||||
}
|
||||
delegate: LauncherListDelegate {
|
||||
launcher: root
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1160,7 +789,7 @@ Rectangle {
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AlwaysOff
|
||||
reserveScrollbarSpace: false
|
||||
gradientColor: Settings.data.ui.panelBackgroundOpacity < 1 ? "transparent" : Color.mSurfaceVariant
|
||||
gradientColor: Settings.data.ui.panelBackgroundOpacity < 1 ? "transparent" : Color.mSurface
|
||||
wheelScrollMultiplier: 4.0
|
||||
trackedSelectionIndex: root.selectedIndex
|
||||
|
||||
@@ -1206,263 +835,8 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: gridEntryContainer
|
||||
width: resultsGrid.cellWidth
|
||||
height: resultsGrid.cellHeight
|
||||
|
||||
property bool isSelected: (!root.ignoreMouseHover && mouseArea.containsMouse) || (index === root.selectedIndex)
|
||||
|
||||
// Prepare item when it becomes visible (e.g., decode images)
|
||||
Component.onCompleted: {
|
||||
var provider = modelData.provider;
|
||||
if (provider && provider.prepareItem) {
|
||||
provider.prepareItem(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
NBox {
|
||||
id: gridEntry
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXXS
|
||||
color: gridEntryContainer.isSelected ? Color.mHover : Color.mSurface
|
||||
forceOpaque: gridEntryContainer.isSelected
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCirc
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.isCompactDensity ? Style.marginXS : Style.marginS
|
||||
anchors.bottomMargin: root.isCompactDensity ? Style.marginXS : Style.marginS
|
||||
spacing: root.isCompactDensity ? 0 : Style.marginXXS
|
||||
|
||||
// Icon badge or Image preview or Emoji
|
||||
Item {
|
||||
// Size image at 65% of cell dimensions.
|
||||
Layout.preferredWidth: Math.round(gridEntry.width * 0.65)
|
||||
Layout.preferredHeight: Math.round(gridEntry.height * 0.65)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
// Icon background
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Style.radiusM
|
||||
color: Color.mSurfaceVariant
|
||||
visible: Settings.data.appLauncher.showIconBackground && !modelData.isImage
|
||||
}
|
||||
|
||||
// Image preview - uses provider's getImageUrl if available
|
||||
NImageRounded {
|
||||
id: gridImagePreview
|
||||
anchors.fill: parent
|
||||
visible: !!modelData.isImage && !modelData.displayString
|
||||
radius: Style.radiusM
|
||||
|
||||
// Use provider's image revision for reactive updates
|
||||
readonly property int _rev: modelData.provider && modelData.provider.imageRevision ? modelData.provider.imageRevision : 0
|
||||
|
||||
// Get image URL from provider
|
||||
imagePath: {
|
||||
_rev;
|
||||
var provider = modelData.provider;
|
||||
if (provider && provider.getImageUrl) {
|
||||
return provider.getImageUrl(modelData);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: parent.status === Image.Loading
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
running: true
|
||||
width: Style.baseWidgetSize * 0.5
|
||||
height: width
|
||||
}
|
||||
}
|
||||
|
||||
onStatusChanged: status => {
|
||||
if (status === Image.Error) {
|
||||
gridIconLoader.visible = true;
|
||||
gridImagePreview.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: gridIconLoader
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS
|
||||
|
||||
visible: (!modelData.isImage && !modelData.displayString) || (!!modelData.isImage && gridImagePreview.status === Image.Error)
|
||||
active: visible
|
||||
|
||||
sourceComponent: Settings.data.appLauncher.iconMode === "tabler" && modelData.isTablerIcon ? gridTablerIconComponent : gridSystemIconComponent
|
||||
|
||||
Component {
|
||||
id: gridTablerIconComponent
|
||||
NIcon {
|
||||
icon: modelData.icon
|
||||
pointSize: Style.fontSizeXXXL
|
||||
visible: modelData.icon && !modelData.displayString
|
||||
color: (gridEntryContainer.isSelected && !Settings.data.appLauncher.showIconBackground) ? Color.mOnHover : Color.mOnSurface
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: gridSystemIconComponent
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : ""
|
||||
visible: modelData.icon && source !== "" && !modelData.displayString
|
||||
asynchronous: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String display
|
||||
NText {
|
||||
id: gridStringDisplay
|
||||
anchors.centerIn: parent
|
||||
visible: !!modelData.displayString || (!gridImagePreview.visible && !gridIconLoader.visible)
|
||||
text: modelData.displayString ? modelData.displayString : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?")
|
||||
pointSize: {
|
||||
if (modelData.displayString) {
|
||||
// Use custom size if provided, otherwise default scaling
|
||||
if (modelData.displayStringSize) {
|
||||
return modelData.displayStringSize * Style.uiScaleRatio;
|
||||
}
|
||||
if (root.providerHasDisplayString) {
|
||||
// Scale with cell width but cap at reasonable maximum
|
||||
const cellBasedSize = gridEntry.width * 0.4;
|
||||
const maxSize = Style.fontSizeXXXL * Style.uiScaleRatio;
|
||||
return Math.min(cellBasedSize, maxSize);
|
||||
}
|
||||
return Style.fontSizeXXL * 2 * Style.uiScaleRatio;
|
||||
}
|
||||
// Scale font size relative to cell width for low res, but cap at maximum
|
||||
const cellBasedSize = gridEntry.width * 0.25;
|
||||
const baseSize = Style.fontSizeXL * Style.uiScaleRatio;
|
||||
const maxSize = Style.fontSizeXXL * Style.uiScaleRatio;
|
||||
return Math.min(Math.max(cellBasedSize, baseSize), maxSize);
|
||||
}
|
||||
font.weight: Style.fontWeightBold
|
||||
color: modelData.displayString ? Color.mOnSurface : Color.mOnPrimary
|
||||
}
|
||||
|
||||
// Badge icon overlay (generic indicator for any provider)
|
||||
Rectangle {
|
||||
visible: !!modelData.badgeIcon
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 2
|
||||
width: height
|
||||
height: Style.fontSizeM + Style.marginXS
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusXXS
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: modelData.badgeIcon || ""
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text content (hidden when hideLabel is true)
|
||||
NText {
|
||||
visible: !modelData.hideLabel
|
||||
text: modelData.name || "Unknown"
|
||||
pointSize: {
|
||||
if (root.providerHasDisplayString && modelData.displayString) {
|
||||
return Style.fontSizeS * Style.uiScaleRatio;
|
||||
}
|
||||
// Scale font size relative to cell width for low res, but cap at maximum
|
||||
const cellBasedSize = gridEntry.width * 0.1;
|
||||
const baseSize = Style.fontSizeXS * Style.uiScaleRatio;
|
||||
const maxSize = Style.fontSizeS * Style.uiScaleRatio;
|
||||
return Math.min(Math.max(cellBasedSize, baseSize), maxSize);
|
||||
}
|
||||
font.weight: Style.fontWeightSemiBold
|
||||
color: gridEntryContainer.isSelected ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: gridEntry.width - 8
|
||||
Layout.leftMargin: (root.providerHasDisplayString && modelData.displayString) ? Style.marginS : 0
|
||||
Layout.rightMargin: (root.providerHasDisplayString && modelData.displayString) ? Style.marginS : 0
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.NoWrap
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons (overlay in top-right corner) - dynamically populated from provider
|
||||
Row {
|
||||
visible: gridEntryContainer.isSelected && gridItemActions.length > 0
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Style.marginXS
|
||||
z: 10
|
||||
spacing: Style.marginXXS
|
||||
|
||||
property var gridItemActions: {
|
||||
if (!gridEntryContainer.isSelected)
|
||||
return [];
|
||||
var provider = modelData.provider || root.currentProvider;
|
||||
if (provider && provider.getItemActions) {
|
||||
return provider.getItemActions(modelData);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: parent.gridItemActions
|
||||
NIconButton {
|
||||
icon: modelData.icon
|
||||
baseSize: Style.baseWidgetSize * 0.75
|
||||
tooltipText: modelData.tooltip
|
||||
z: 11
|
||||
handleWheel: true
|
||||
onClicked: {
|
||||
if (modelData.action) {
|
||||
modelData.action();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: !Settings.data.appLauncher.ignoreMouseInput
|
||||
|
||||
onEntered: {
|
||||
if (!root.ignoreMouseHover) {
|
||||
root.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.selectedIndex = index;
|
||||
root.activate();
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
acceptedButtons: Qt.LeftButton
|
||||
}
|
||||
delegate: LauncherGridDelegate {
|
||||
launcher: root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Widgets
|
||||
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: gridEntryContainer
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
required property var launcher
|
||||
|
||||
width: GridView.view.cellWidth
|
||||
height: GridView.view.cellHeight
|
||||
|
||||
property bool isSelected: (!launcher.ignoreMouseHover && mouseArea.containsMouse) || (index === launcher.selectedIndex)
|
||||
|
||||
// Prepare item when it becomes visible (e.g., decode images)
|
||||
Component.onCompleted: {
|
||||
var provider = modelData.provider;
|
||||
if (provider && provider.prepareItem) {
|
||||
provider.prepareItem(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
NBox {
|
||||
id: gridEntry
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXXS
|
||||
color: gridEntryContainer.isSelected ? Color.mHover : Color.mSurfaceVariant
|
||||
forceOpaque: gridEntryContainer.isSelected
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCirc
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: launcher.isCompactDensity ? Style.marginXS : Style.marginS
|
||||
anchors.bottomMargin: launcher.isCompactDensity ? Style.marginXS : Style.marginS
|
||||
spacing: launcher.isCompactDensity ? 0 : Style.marginXXS
|
||||
|
||||
// Icon badge or Image preview or Emoji
|
||||
Item {
|
||||
// Size image at 65% of cell dimensions.
|
||||
Layout.preferredWidth: Math.round(gridEntry.width * 0.65)
|
||||
Layout.preferredHeight: Math.round(gridEntry.height * 0.65)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
// Icon background
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Style.radiusM
|
||||
color: Color.mSurfaceVariant
|
||||
visible: Settings.data.appLauncher.showIconBackground && !modelData.isImage
|
||||
}
|
||||
|
||||
// Image preview - uses provider's getImageUrl if available
|
||||
NImageRounded {
|
||||
id: gridImagePreview
|
||||
anchors.fill: parent
|
||||
visible: !!modelData.isImage && !modelData.displayString
|
||||
radius: Style.radiusM
|
||||
|
||||
// Use provider's image revision for reactive updates
|
||||
readonly property int _rev: modelData.provider && modelData.provider.imageRevision ? modelData.provider.imageRevision : 0
|
||||
|
||||
// Get image URL from provider
|
||||
imagePath: {
|
||||
_rev;
|
||||
var provider = modelData.provider;
|
||||
if (provider && provider.getImageUrl) {
|
||||
return provider.getImageUrl(modelData);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: parent.status === Image.Loading
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
running: true
|
||||
width: Style.baseWidgetSize * 0.5
|
||||
height: width
|
||||
}
|
||||
}
|
||||
|
||||
onStatusChanged: status => {
|
||||
if (status === Image.Error) {
|
||||
gridIconLoader.visible = true;
|
||||
gridImagePreview.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: gridIconLoader
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS
|
||||
|
||||
visible: (!modelData.isImage && !modelData.displayString) || (!!modelData.isImage && gridImagePreview.status === Image.Error)
|
||||
active: visible
|
||||
|
||||
sourceComponent: Settings.data.appLauncher.iconMode === "tabler" && modelData.isTablerIcon ? gridTablerIconComponent : gridSystemIconComponent
|
||||
|
||||
Component {
|
||||
id: gridTablerIconComponent
|
||||
NIcon {
|
||||
icon: modelData.icon
|
||||
pointSize: Style.fontSizeXXXL
|
||||
visible: modelData.icon && !modelData.displayString
|
||||
color: (gridEntryContainer.isSelected && !Settings.data.appLauncher.showIconBackground) ? Color.mOnHover : Color.mOnSurface
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: gridSystemIconComponent
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : ""
|
||||
visible: modelData.icon && source !== "" && !modelData.displayString
|
||||
asynchronous: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String display
|
||||
NText {
|
||||
id: gridStringDisplay
|
||||
anchors.centerIn: parent
|
||||
visible: !!modelData.displayString || (!gridImagePreview.visible && !gridIconLoader.visible)
|
||||
text: modelData.displayString ? modelData.displayString : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?")
|
||||
pointSize: {
|
||||
if (modelData.displayString) {
|
||||
// Use custom size if provided, otherwise default scaling
|
||||
if (modelData.displayStringSize) {
|
||||
return modelData.displayStringSize * Style.uiScaleRatio;
|
||||
}
|
||||
if (launcher.providerHasDisplayString) {
|
||||
// Scale with cell width but cap at reasonable maximum
|
||||
const cellBasedSize = gridEntry.width * 0.4;
|
||||
const maxSize = Style.fontSizeXXXL * Style.uiScaleRatio;
|
||||
return Math.min(cellBasedSize, maxSize);
|
||||
}
|
||||
return Style.fontSizeXXL * 2 * Style.uiScaleRatio;
|
||||
}
|
||||
// Scale font size relative to cell width for low res, but cap at maximum
|
||||
const cellBasedSize = gridEntry.width * 0.25;
|
||||
const baseSize = Style.fontSizeXL * Style.uiScaleRatio;
|
||||
const maxSize = Style.fontSizeXXL * Style.uiScaleRatio;
|
||||
return Math.min(Math.max(cellBasedSize, baseSize), maxSize);
|
||||
}
|
||||
font.weight: Style.fontWeightBold
|
||||
color: modelData.displayString ? Color.mOnSurface : Color.mOnPrimary
|
||||
}
|
||||
|
||||
// Badge icon overlay (generic indicator for any provider)
|
||||
Rectangle {
|
||||
visible: !!modelData.badgeIcon
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 2
|
||||
width: height
|
||||
height: Style.fontSizeM + Style.marginXS
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusXXS
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: modelData.badgeIcon || ""
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text content (hidden when hideLabel is true)
|
||||
NText {
|
||||
visible: !modelData.hideLabel
|
||||
text: modelData.name || "Unknown"
|
||||
pointSize: {
|
||||
if (launcher.providerHasDisplayString && modelData.displayString) {
|
||||
return Style.fontSizeS * Style.uiScaleRatio;
|
||||
}
|
||||
// Scale font size relative to cell width for low res, but cap at maximum
|
||||
const cellBasedSize = gridEntry.width * 0.1;
|
||||
const baseSize = Style.fontSizeXS * Style.uiScaleRatio;
|
||||
const maxSize = Style.fontSizeS * Style.uiScaleRatio;
|
||||
return Math.min(Math.max(cellBasedSize, baseSize), maxSize);
|
||||
}
|
||||
font.weight: Style.fontWeightSemiBold
|
||||
color: gridEntryContainer.isSelected ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: gridEntry.width - 8
|
||||
Layout.leftMargin: (launcher.providerHasDisplayString && modelData.displayString) ? Style.marginS : 0
|
||||
Layout.rightMargin: (launcher.providerHasDisplayString && modelData.displayString) ? Style.marginS : 0
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.NoWrap
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons (overlay in top-right corner) - dynamically populated from provider
|
||||
Row {
|
||||
visible: gridEntryContainer.isSelected && gridItemActions.length > 0
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Style.marginXS
|
||||
z: 10
|
||||
spacing: Style.marginXXS
|
||||
|
||||
property var gridItemActions: {
|
||||
if (!gridEntryContainer.isSelected)
|
||||
return [];
|
||||
var provider = modelData.provider || launcher.currentProvider;
|
||||
if (provider && provider.getItemActions) {
|
||||
return provider.getItemActions(modelData);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: parent.gridItemActions
|
||||
NIconButton {
|
||||
required property var modelData
|
||||
icon: modelData.icon
|
||||
baseSize: Style.baseWidgetSize * 0.75
|
||||
tooltipText: modelData.tooltip
|
||||
z: 11
|
||||
handleWheel: true
|
||||
onClicked: {
|
||||
if (modelData.action) {
|
||||
modelData.action();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: !Settings.data.appLauncher.ignoreMouseInput
|
||||
|
||||
onEntered: {
|
||||
if (!launcher.ignoreMouseHover) {
|
||||
launcher.selectedIndex = gridEntryContainer.index;
|
||||
}
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
launcher.selectedIndex = gridEntryContainer.index;
|
||||
launcher.activate();
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
acceptedButtons: Qt.LeftButton
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Widgets
|
||||
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
NBox {
|
||||
id: entry
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
required property var launcher
|
||||
|
||||
property bool isSelected: (!launcher.ignoreMouseHover && mouseArea.containsMouse) || (index === launcher.selectedIndex)
|
||||
|
||||
width: ListView.view.width
|
||||
implicitHeight: launcher.entryHeight
|
||||
clip: true
|
||||
color: entry.isSelected ? Color.mHover : Color.mSurfaceVariant
|
||||
forceOpaque: entry.isSelected
|
||||
|
||||
// Prepare item when it becomes visible (e.g., decode images)
|
||||
Component.onCompleted: {
|
||||
var provider = modelData.provider;
|
||||
if (provider && provider.prepareItem) {
|
||||
provider.prepareItem(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCirc
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: launcher.isCompactDensity ? Style.marginXS : Style.marginM
|
||||
spacing: launcher.isCompactDensity ? Style.marginXS : Style.marginM
|
||||
|
||||
// Top row - Main entry content with action buttons
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: launcher.isCompactDensity ? Style.marginS : Style.marginM
|
||||
|
||||
// Icon badge or Image preview or Emoji
|
||||
Item {
|
||||
visible: !modelData.hideIcon
|
||||
Layout.preferredWidth: modelData.hideIcon ? 0 : launcher.badgeSize
|
||||
Layout.preferredHeight: modelData.hideIcon ? 0 : launcher.badgeSize
|
||||
|
||||
// Icon background
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Style.radiusXS
|
||||
color: Color.mSurfaceVariant
|
||||
visible: Settings.data.appLauncher.showIconBackground && !modelData.isImage
|
||||
}
|
||||
|
||||
// Image preview - uses provider's getImageUrl if available
|
||||
NImageRounded {
|
||||
id: imagePreview
|
||||
anchors.fill: parent
|
||||
visible: !!modelData.isImage && !modelData.displayString
|
||||
radius: Style.radiusXS
|
||||
borderColor: Color.mOnSurface
|
||||
borderWidth: Style.borderM
|
||||
imageFillMode: Image.PreserveAspectCrop
|
||||
|
||||
// Use provider's image revision for reactive updates
|
||||
readonly property int _rev: modelData.provider && modelData.provider.imageRevision ? modelData.provider.imageRevision : 0
|
||||
|
||||
// Get image URL from provider
|
||||
imagePath: {
|
||||
_rev;
|
||||
var provider = modelData.provider;
|
||||
if (provider && provider.getImageUrl) {
|
||||
return provider.getImageUrl(modelData);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: parent.status === Image.Loading
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
running: true
|
||||
width: Style.baseWidgetSize * 0.5
|
||||
height: width
|
||||
}
|
||||
}
|
||||
|
||||
onStatusChanged: status => {
|
||||
if (status === Image.Error) {
|
||||
iconLoader.visible = true;
|
||||
imagePreview.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: iconLoader
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS
|
||||
|
||||
visible: (!modelData.isImage && !modelData.displayString) || (!!modelData.isImage && imagePreview.status === Image.Error)
|
||||
active: visible
|
||||
|
||||
sourceComponent: Component {
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: Settings.data.appLauncher.iconMode === "tabler" && modelData.isTablerIcon ? tablerIconComponent : systemIconComponent
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: tablerIconComponent
|
||||
NIcon {
|
||||
icon: modelData.icon
|
||||
pointSize: Style.fontSizeXXXL
|
||||
visible: modelData.icon && !modelData.displayString
|
||||
color: (entry.isSelected && !Settings.data.appLauncher.showIconBackground) ? Color.mOnHover : Color.mOnSurface
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: systemIconComponent
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : ""
|
||||
visible: modelData.icon && source !== "" && !modelData.displayString
|
||||
asynchronous: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String display - takes precedence when displayString is present
|
||||
NText {
|
||||
id: stringDisplay
|
||||
anchors.centerIn: parent
|
||||
visible: !!modelData.displayString || (!imagePreview.visible && !iconLoader.visible)
|
||||
text: modelData.displayString ? modelData.displayString : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?")
|
||||
pointSize: modelData.displayString ? (modelData.displayStringSize || Style.fontSizeXXXL) : Style.fontSizeXXL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: modelData.displayString ? Color.mOnSurface : Color.mOnPrimary
|
||||
}
|
||||
|
||||
// Image type indicator overlay
|
||||
Rectangle {
|
||||
visible: !!modelData.isImage && imagePreview.visible
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 2
|
||||
width: formatLabel.width + Style.marginXS
|
||||
height: formatLabel.height + Style.marginXXS
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusXXS
|
||||
NText {
|
||||
id: formatLabel
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!modelData.isImage)
|
||||
return "";
|
||||
const desc = modelData.description || "";
|
||||
const parts = desc.split(" \u2022 ");
|
||||
return parts[0] || "IMG";
|
||||
}
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
// Badge icon overlay (generic indicator for any provider)
|
||||
Rectangle {
|
||||
visible: !!modelData.badgeIcon
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 2
|
||||
width: height
|
||||
height: Style.fontSizeM + Style.marginXS
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusXXS
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: modelData.badgeIcon || ""
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text content
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
NText {
|
||||
text: modelData.name || "Unknown"
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: entry.isSelected ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
wrapMode: Text.Wrap
|
||||
clip: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.description || ""
|
||||
pointSize: Style.fontSizeS
|
||||
color: entry.isSelected ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
Layout.fillWidth: true
|
||||
visible: text !== "" && !launcher.isCompactDensity
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons row - dynamically populated from provider
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
spacing: Style.marginXS
|
||||
visible: entry.isSelected && itemActions.length > 0
|
||||
|
||||
property var itemActions: {
|
||||
if (!entry.isSelected)
|
||||
return [];
|
||||
var provider = modelData.provider || launcher.currentProvider;
|
||||
if (provider && provider.getItemActions) {
|
||||
return provider.getItemActions(modelData);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: parent.itemActions
|
||||
NIconButton {
|
||||
required property var modelData
|
||||
icon: modelData.icon
|
||||
baseSize: Style.baseWidgetSize * 0.75
|
||||
tooltipText: modelData.tooltip
|
||||
z: 1
|
||||
handleWheel: true
|
||||
onClicked: {
|
||||
if (modelData.action) {
|
||||
modelData.action();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: !Settings.data.appLauncher.ignoreMouseInput
|
||||
onEntered: {
|
||||
if (!launcher.ignoreMouseHover) {
|
||||
launcher.selectedIndex = entry.index;
|
||||
}
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
launcher.selectedIndex = entry.index;
|
||||
launcher.activate();
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
acceptedButtons: Qt.LeftButton
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ Item {
|
||||
|
||||
function init() {
|
||||
loadApplications();
|
||||
migrateLegacyUsageKeys();
|
||||
}
|
||||
|
||||
function onOpened() {
|
||||
@@ -639,32 +640,22 @@ Item {
|
||||
return String(app && app.name ? app.name : "unknown");
|
||||
}
|
||||
|
||||
// Returns the usage count for an app, checking both the canonical key (app.id)
|
||||
// and the legacy command-based key. If a legacy key has usage but the canonical
|
||||
// key doesn't, the counts are migrated automatically.
|
||||
function getUsageCount(app) {
|
||||
const key = getAppKey(app);
|
||||
let count = ShellState.getLauncherUsageCount(key);
|
||||
return ShellState.getLauncherUsageCount(getAppKey(app));
|
||||
}
|
||||
|
||||
// Check for legacy command-based key if the primary key is the app ID
|
||||
if (app && app.id && app.command && app.command.join) {
|
||||
const legacyKey = app.command.join(" ");
|
||||
if (legacyKey !== key) {
|
||||
const legacyCount = ShellState.getLauncherUsageCount(legacyKey);
|
||||
if (legacyCount > 0) {
|
||||
// Migrate: merge legacy count into the canonical key
|
||||
count += legacyCount;
|
||||
ShellState.recordLauncherUsageMerge(key, count);
|
||||
ShellState.clearLauncherUsage(legacyKey);
|
||||
Logger.d("ApplicationsProvider", `Migrated usage: "${legacyKey}" (${legacyCount}) → "${key}" (${count})`);
|
||||
// Migrate legacy command-based usage keys to canonical app-id keys at startup
|
||||
function migrateLegacyUsageKeys() {
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const app = entries[i];
|
||||
if (app && app.id && app.command && app.command.join) {
|
||||
const key = getAppKey(app);
|
||||
const legacyKey = app.command.join(" ");
|
||||
if (legacyKey !== key && ShellState.getLauncherUsageCount(legacyKey) > 0) {
|
||||
ShellState.migrateLauncherUsage(legacyKey, key);
|
||||
Logger.d("ApplicationsProvider", `Migrated usage: "${legacyKey}" → "${key}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
function recordUsage(app) {
|
||||
ShellState.recordLauncherUsage(getAppKey(app));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ NBox {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: updateAvailableWidgetsModel()
|
||||
Component.onCompleted: Qt.callLater(updateAvailableWidgetsModel)
|
||||
|
||||
ListModel {
|
||||
id: availableWidgetsModel
|
||||
|
||||
@@ -616,6 +616,14 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.about.changelog-on-startup")
|
||||
description: I18n.tr("panels.about.changelog-on-startup-desc")
|
||||
checked: Settings.data.general.showChangelogOnStartup
|
||||
onToggled: checked => Settings.data.general.showChangelogOnStartup = checked
|
||||
}
|
||||
|
||||
// System Information Section
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -154,7 +154,7 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateAvailableWidgetsModel();
|
||||
Qt.callLater(updateAvailableWidgetsModel);
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -279,7 +279,7 @@ ColumnLayout {
|
||||
onClicked: root.toggleTemplate(chip.modelData.id)
|
||||
onEntered: {
|
||||
if (chip.modelData.tooltip) {
|
||||
TooltipService.show(chip, chip.modelData.tooltip, "auto");
|
||||
TooltipService.show(chip, chip.modelData.tooltip, "bottom");
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
|
||||
@@ -58,7 +58,7 @@ ColumnLayout {
|
||||
_saveFromModel();
|
||||
}
|
||||
|
||||
Component.onCompleted: _loadToModel()
|
||||
Component.onCompleted: Qt.callLater(_loadToModel)
|
||||
|
||||
Connections {
|
||||
target: Settings.data.idle
|
||||
|
||||
@@ -49,6 +49,14 @@ ColumnLayout {
|
||||
onToggled: checked => Settings.data.general.enableBlurBehind = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.user-interface.translucent-widgets-label")
|
||||
description: I18n.tr("panels.user-interface.translucent-widgets-description")
|
||||
checked: Settings.data.ui.translucentWidgets
|
||||
defaultValue: Settings.getDefaultValue("ui.translucentWidgets")
|
||||
onToggled: checked => Settings.data.ui.translucentWidgets = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
visible: Settings.data.general.enableShadows
|
||||
label: I18n.tr("panels.user-interface.shadows-direction-label")
|
||||
|
||||
@@ -11,7 +11,7 @@ Singleton {
|
||||
id: root
|
||||
|
||||
// Version properties
|
||||
readonly property string baseVersion: "4.6.6"
|
||||
readonly property string baseVersion: "4.6.7"
|
||||
readonly property bool isDevelopment: true
|
||||
readonly property string developmentSuffix: "-git"
|
||||
readonly property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + developmentSuffix}`
|
||||
|
||||
+1
-4
@@ -24,10 +24,7 @@ Item {
|
||||
return root.color;
|
||||
}
|
||||
|
||||
// Reuse panel opacity, but limit it to 0.4
|
||||
let alpha = Math.max(Settings.data.ui.panelBackgroundOpacity, 0.4);
|
||||
alpha = Math.max(0, root.color.a - (1.0 - alpha));
|
||||
return Qt.alpha(root.color, alpha);
|
||||
return Color.smartAlpha(root.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ RowLayout {
|
||||
Rectangle {
|
||||
id: box
|
||||
|
||||
Layout.margins: Style.borderS
|
||||
implicitWidth: Math.round(root.baseSize)
|
||||
implicitHeight: Math.round(root.baseSize)
|
||||
radius: Style.iRadiusXS
|
||||
|
||||
@@ -12,6 +12,7 @@ Rectangle {
|
||||
|
||||
signal colorSelected(color color)
|
||||
|
||||
Layout.margins: Style.borderS
|
||||
implicitWidth: 150
|
||||
implicitHeight: Math.round(Style.baseWidgetSize * 1.1)
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ Slider {
|
||||
height: bgContainer.height
|
||||
visible: root.trackWidth > 0 && bgContainer.height > 0
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
asynchronous: true
|
||||
|
||||
ShapePath {
|
||||
id: trackPath
|
||||
|
||||
@@ -136,6 +136,7 @@ RowLayout {
|
||||
ComboBox {
|
||||
id: combo
|
||||
|
||||
Layout.margins: Style.borderS
|
||||
Layout.minimumWidth: Math.round(root.minimumWidth * Style.uiScaleRatio)
|
||||
Layout.preferredHeight: Math.round(root.preferredHeight * Style.uiScaleRatio)
|
||||
implicitWidth: Layout.minimumWidth
|
||||
|
||||
@@ -10,6 +10,7 @@ Rectangle {
|
||||
|
||||
signal tokenClicked(string token)
|
||||
|
||||
Layout.margins: Style.borderS
|
||||
color: Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
@@ -17,7 +17,7 @@ Item {
|
||||
property bool handleWheel: false
|
||||
property bool hovering: false
|
||||
|
||||
property color colorBg: Color.mSurfaceVariant
|
||||
property color colorBg: Color.smartAlpha(Color.mSurfaceVariant)
|
||||
property color colorFg: Color.mPrimary
|
||||
property color colorBgHover: Color.mHover
|
||||
property color colorFgHover: Color.mOnHover
|
||||
|
||||
@@ -22,7 +22,7 @@ Rectangle {
|
||||
property bool pressed: false
|
||||
|
||||
// Color properties
|
||||
property color colorBg: Color.mSurfaceVariant
|
||||
property color colorBg: Color.smartAlpha(Color.mSurfaceVariant)
|
||||
property color colorFg: Color.mPrimary
|
||||
property color colorBgHover: Color.mHover
|
||||
property color colorFgHover: Color.mOnHover
|
||||
|
||||
@@ -168,6 +168,7 @@ RowLayout {
|
||||
ComboBox {
|
||||
id: combo
|
||||
|
||||
Layout.margins: Style.borderS
|
||||
Layout.minimumWidth: Math.round(root.minimumWidth * Style.uiScaleRatio)
|
||||
Layout.preferredHeight: Math.round(root.preferredHeight * Style.uiScaleRatio)
|
||||
implicitWidth: Layout.minimumWidth
|
||||
|
||||
@@ -43,6 +43,7 @@ Slider {
|
||||
anchors.fill: parent
|
||||
visible: bgContainer.width > 0 && bgContainer.height > 0
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
asynchronous: true
|
||||
|
||||
ShapePath {
|
||||
id: bgPath
|
||||
@@ -122,6 +123,7 @@ Slider {
|
||||
height: bgContainer.height
|
||||
visible: bgContainer.fillWidth > 0 && bgContainer.height > 0
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
asynchronous: true
|
||||
clip: true
|
||||
|
||||
ShapePath {
|
||||
|
||||
@@ -91,6 +91,7 @@ RowLayout {
|
||||
// Main spinbox container
|
||||
Rectangle {
|
||||
id: spinBoxContainer
|
||||
Layout.margins: Style.borderS
|
||||
implicitWidth: 120
|
||||
implicitHeight: Math.round((root.baseSize - 4) / 2) * 2
|
||||
radius: Style.iRadiusS
|
||||
|
||||
+2
-1
@@ -66,9 +66,10 @@ Rectangle {
|
||||
}
|
||||
|
||||
// Styling
|
||||
Layout.margins: Style.borderS
|
||||
implicitWidth: tabRow.implicitWidth + (margins * 2)
|
||||
implicitHeight: tabHeight + (margins * 2)
|
||||
color: Color.mSurfaceVariant
|
||||
color: Color.smartAlpha(Color.mSurfaceVariant)
|
||||
radius: Style.iRadiusM
|
||||
|
||||
RowLayout {
|
||||
|
||||
@@ -33,8 +33,8 @@ Rectangle {
|
||||
topRightRadius: isLast ? Style.iRadiusM : Style.iRadiusXXXS
|
||||
bottomRightRadius: isLast ? Style.iRadiusM : Style.iRadiusXXXS
|
||||
|
||||
color: root.isHovered ? Color.mHover : (root.checked ? Color.mPrimary : Color.mSurface)
|
||||
border.color: Color.mOutline
|
||||
color: root.isHovered ? Color.mHover : (root.checked ? Color.mPrimary : Color.smartAlpha(Color.mSurface))
|
||||
border.color: root.checked ? Color.mPrimary : Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on color {
|
||||
|
||||
@@ -56,7 +56,7 @@ ColumnLayout {
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: root.minimumInputWidth
|
||||
Layout.margins: Math.ceil(Style.borderS)
|
||||
Layout.margins: Style.borderS
|
||||
implicitHeight: Style.baseWidgetSize * 1.1 * Style.uiScaleRatio
|
||||
|
||||
// This is important - makes the control accept focus
|
||||
|
||||
@@ -45,6 +45,7 @@ RowLayout {
|
||||
id: switcher
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.margins: Style.borderS
|
||||
|
||||
implicitWidth: Math.round(root.baseSize * .85) * 2
|
||||
implicitHeight: Math.round(root.baseSize * .5) * 2
|
||||
|
||||
@@ -107,7 +107,6 @@ ShellRoot {
|
||||
Qt.callLater(function () {
|
||||
LocationService.init();
|
||||
NightLightService.apply();
|
||||
HooksService.init();
|
||||
BluetoothService.init();
|
||||
IdleInhibitorService.init();
|
||||
IdleService.init();
|
||||
@@ -179,6 +178,7 @@ ShellRoot {
|
||||
running: false
|
||||
interval: 1500
|
||||
onTriggered: {
|
||||
HooksService.init();
|
||||
FontService.init();
|
||||
UpdateService.init();
|
||||
showWizardOrChangelog();
|
||||
|
||||
Reference in New Issue
Block a user