diff --git a/.gitignore b/.gitignore index dfbea23a..e69de29b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +0,0 @@ -.qmlls.ini diff --git a/Assets/Matugen/Matugen.qml b/Assets/Matugen/Matugen.qml index 009349e6..82a804cd 100644 --- a/Assets/Matugen/Matugen.qml +++ b/Assets/Matugen/Matugen.qml @@ -51,7 +51,7 @@ Singleton { lines.push("\n[templates.ghostty]") lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/ghostty.conf"') lines.push('output_path = "~/.config/ghostty/themes/noctalia"') - lines.push("post_hook = \"grep -q '^theme *= *' ~/.config/ghostty/config; and sed -i 's/^theme *= *.*/theme = noctalia/' ~/.config/ghostty/config; or echo 'theme = noctalia' >> ~/.config/ghostty/config\"") + lines.push("post_hook = \"grep -q '^theme *= *' ~/.config/ghostty/config; and sed -i 's/^theme *= *.*/theme = noctalia/' ~/.config/ghostty/config; or echo 'theme = noctalia' >> ~/.config/ghostty/config; and pkill -SIGUSR2 ghostty\"") } if (Settings.data.matugen.foot) { lines.push("\n[templates.foot]") diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 0ca85261..04cb4be0 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -2,7 +2,7 @@ "settings": { "general": { "title": "Allgemein", - + "profile": { "section": { "label": "Profil", @@ -71,12 +71,6 @@ "description": "Monospace-Schriftart für Zahlen und Statistik-Anzeigen.", "placeholder": "Monospace-Schriftart auswählen...", "search-placeholder": "Monospace-Schriftarten suchen..." - }, - "accent": { - "label": "Akzent-Schriftart", - "description": "Große Schriftart für hervorgehobene Anzeigen.", - "placeholder": "Display-Schriftart auswählen...", - "search-placeholder": "Display-Schriftarten suchen..." } } }, @@ -251,6 +245,10 @@ "section": { "label": "Monitor-Anzeige", "description": "Statusleiste auf bestimmten Monitoren anzeigen. Standard ist alle, wenn keine ausgewählt sind." + }, + "only-same-output": { + "label": "Nur Apps vom gleichen Bildschirm", + "description": "Zeige nur Apps vom dem Bildschirm an, wo sich das Dock befindet." } } }, @@ -997,7 +995,8 @@ "scrolling-mode": { "label": "Scrollmodus", "description": "Steuern, wann Textscrolling für lange Track-Titel aktiviert ist." - } + }, + "no-active-player": "Kein aktiver Player" }, "clock": { "use-primary-color": { @@ -1021,6 +1020,16 @@ "description": "Verwenden Sie ein Leerzeichen, um jeden Teil in eine neue Zeile zu trennen." }, "preview": "Vorschau" + }, + "taskbar": { + "only-active-workspaces": { + "label": "Nur von aktiven Arbeitsbereichen", + "description": "Zeige nur Apps von aktiven Arbeitsbereichen an." + }, + "only-same-output": { + "label": "Nur vom gleichen Bildschirm", + "description": "Zeige nur Apps vom dem Bildschirm an, wo sich die Taskbar befindet." + } } } }, @@ -1159,13 +1168,17 @@ "close": "Schließen" } }, + "launcher": { + "pin": "An das Dock anheften", + "unpin": "Vom Dock lösen" + }, "placeholders": { "search-icons": "z.B. noctalia, niri, battery, cloud", "profile-picture-path": "/home/benutzer/.face", "enter-width-pixels": "Breite in Pixeln eingeben", "enter-command": "Befehl eingeben (App oder benutzerdefiniertes Skript)", "command-example": "echo \"Hallo Welt\"", - + "search-wallpapers": "Zum Filtern von Hintergrundbildern eingeben...", "search-launcher": "Einträge suchen... oder > für Befehle verwenden", "search": "Suchen...", @@ -1237,7 +1250,7 @@ "frame-rates": { "fps": "{fps} FPS" }, - + "screen-recording": { "sources": { "portal": "Portal", @@ -1264,14 +1277,14 @@ "title": "Sitzungsmenü", "click-again": "Erneut klicken für sofortige Ausführung", "action-in-seconds": "{action} in {seconds} Sekunden...", + "lock": "Sperren", "lock-subtitle": "Sitzung sperren", "lock-and-suspend": "Sperren und Ruhezustand", "lock-and-suspend-subtitle": "Sitzung sperren und System in den Ruhezustand versetzen", - "end-subtitle": "Sitzung beenden", - "lock": "Sperren", "suspend": "Ruhezustand", "reboot": "Neu starten", "logout": "Abmelden", + "logout-subtitle": "Sitzung beenden", "shutdown": "Herunterfahren" }, "plugins": { @@ -1395,4 +1408,4 @@ "description": "Das Control-Center-Widget wurde aus der Leiste entfernt. Um es erneut über die Leiste zu öffnen, fügen Sie das Widget wieder hinzu." } } -} \ No newline at end of file +} diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 46d776ba..066f2dba 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -56,6 +56,7 @@ } }, "fonts": { + "reset-scaling": "Reset scaling", "section": { "label": "Fonts", "description": "Choose the fonts used throughout the interface." @@ -64,19 +65,21 @@ "label": "Default font", "description": "Main font used throughout the interface.", "placeholder": "Select default font...", - "search-placeholder": "Search fonts..." + "search-placeholder": "Search font...", + "scale": { + "label": "Default font size", + "description": "Increase or decrease the size of the standard text." + } }, "monospace": { "label": "Monospaced font", "description": "Monospaced font used for numbers and stats display.", "placeholder": "Select monospace font...", - "search-placeholder": "Search monospace fonts..." - }, - "accent": { - "label": "Accent font", - "description": "Large font used for prominent displays.", - "placeholder": "Select display font...", - "search-placeholder": "Search display fonts..." + "search-placeholder": "Search monospace font...", + "scale": { + "label": "Monospaced font size", + "description": "Increase or decrease the size of the monospaced text." + } } } }, @@ -282,6 +285,10 @@ "section": { "label": "Monitor display", "description": "Choose which monitor to display the dock on." + }, + "only-same-output": { + "label": "Only apps from same output", + "description": "Show only apps from the output where the dock is located." } } }, @@ -980,7 +987,8 @@ "scrolling-mode": { "label": "Scrolling mode", "description": "Control when text scrolling is enabled for long track titles." - } + }, + "no-active-player": "No active player" }, "clock": { "use-primary-color": { @@ -1004,6 +1012,16 @@ "description": "Use a space to separate each part onto a new line." }, "preview": "Preview" + }, + "taskbar": { + "only-active-workspaces": { + "label": "Only from active workspaces", + "description": "Show only apps from active workspaces." + }, + "only-same-output": { + "label": "Only from same output", + "description": "Show only apps from the output where the bar is located." + } } } }, @@ -1142,6 +1160,10 @@ "close": "Close" } }, + "launcher": { + "pin": "Pin to dock", + "unpin": "Unpin from dock" + }, "placeholders": { "search-icons": "e.g., noctalia, niri, battery, cloud", "profile-picture-path": "/home/user/.face", @@ -1246,15 +1268,18 @@ "title": "Session Menu", "click-again": "Click again to execute immediately", "action-in-seconds": "{action} in {seconds} seconds...", + "lock": "Lock", "lock-subtitle": "Lock your session", "lock-and-suspend": "Lock and Suspend", "lock-and-suspend-subtitle": "Lock session and put system to sleep", - "end-subtitle": "End your session", - "lock": "Lock", "suspend": "Suspend", + "suspend-subtitle": "Put system to sleep", "reboot": "Reboot", + "reboot-subtitle": "Restart your computer", "logout": "Logout", - "shutdown": "Shutdown" + "logout-subtitle": "End your session", + "shutdown": "Shutdown", + "shutdown-subtitle": "Turn off your computer" }, "plugins": { "applications": "Applications", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index c41cb0d4..e86685dc 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -69,12 +69,6 @@ "description": "Fuente monoespaciada utilizada para la visualización de números y estadísticas.", "placeholder": "Seleccionar fuente monoespaciada...", "search-placeholder": "Buscar fuentes monoespaciadas..." - }, - "accent": { - "label": "Fuente de acento", - "description": "Fuente grande utilizada para visualizaciones destacadas.", - "placeholder": "Seleccionar fuente de visualización...", - "search-placeholder": "Buscar fuentes de visualización..." } } }, @@ -978,7 +972,8 @@ "scrolling-mode": { "label": "Modo de desplazamiento", "description": "Controlar cuándo está habilitado el desplazamiento de texto para títulos de pista largos." - } + }, + "no-active-player": "Sin reproductor activo" }, "clock": { "use-primary-color": { @@ -1140,6 +1135,10 @@ "close": "Cerrar" } }, + "launcher": { + "pin": "Anclar al dock", + "unpin": "Desanclar del dock" + }, "placeholders": { "search-icons": "ej., noctalia, niri, battery, cloud", "profile-picture-path": "/home/usuario/.face", @@ -1243,14 +1242,14 @@ "title": "Menú de sesión", "click-again": "Haz clic de nuevo para ejecutar inmediatamente", "action-in-seconds": "{action} en {seconds} segundos...", + "lock": "Bloquear", "lock-subtitle": "Bloquea tu sesión", "lock-and-suspend": "Bloquear y Suspender", "lock-and-suspend-subtitle": "Bloquear sesión y poner el sistema en suspensión", - "end-subtitle": "Finaliza tu sesión", - "lock": "Bloquear", "suspend": "Suspender", "reboot": "Reiniciar", "logout": "Cerrar sesión", + "logout-subtitle": "Finaliza tu sesión", "shutdown": "Apagar" }, "plugins": { @@ -1391,4 +1390,4 @@ "charging": "Cargando.", "discharging": "Descargando." } -} \ No newline at end of file +} diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 98a9a8b3..3f7d0c5b 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -69,12 +69,6 @@ "description": "Police à chasse fixe utilisée pour l'affichage des chiffres et des statistiques.", "placeholder": "Sélectionner la police à chasse fixe...", "search-placeholder": "Rechercher des polices à chasse fixe..." - }, - "accent": { - "label": "Police d'accentuation", - "description": "Grande police utilisée pour les affichages proéminents.", - "placeholder": "Sélectionner la police d'affichage...", - "search-placeholder": "Rechercher des polices d'affichage..." } } }, @@ -978,7 +972,8 @@ "auto-hide": { "label": "Masquer automatiquement", "description": "Masquer automatiquement le widget quand aucun média n'est en cours de lecture." - } + }, + "no-active-player": "Aucun lecteur actif" }, "clock": { "use-primary-color": { @@ -1140,6 +1135,10 @@ "close": "Fermer" } }, + "launcher": { + "pin": "Épingler au dock", + "unpin": "Retirer du dock" + }, "placeholders": { "search-icons": "ex: noctalia, niri, batterie, nuage", "profile-picture-path": "/home/user/.face", @@ -1243,14 +1242,14 @@ "title": "Menu de session", "click-again": "Cliquez à nouveau pour exécuter immédiatement", "action-in-seconds": "{action} dans {seconds} secondes...", + "lock": "Verrouiller", "lock-subtitle": "Verrouiller votre session", "lock-and-suspend": "Verrouiller et Mettre en veille", "lock-and-suspend-subtitle": "Verrouiller la session et mettre le système en veille", - "end-subtitle": "Terminer votre session", - "lock": "Verrouiller", "suspend": "Mettre en veille", "reboot": "Redémarrer", "logout": "Déconnexion", + "logout-subtitle": "Terminer votre session", "shutdown": "Éteindre" }, "plugins": { @@ -1391,4 +1390,4 @@ "charging": "En charge.", "discharging": "En décharge." } -} \ No newline at end of file +} diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 4714700f..88300be7 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -69,12 +69,6 @@ "description": "Fonte monoespaçada usada para exibição de números e estatísticas.", "placeholder": "Selecione a fonte monoespaçada...", "search-placeholder": "Pesquisar fontes monoespaçadas..." - }, - "accent": { - "label": "Fonte de destaque", - "description": "Fonte grande usada para exibições proeminentes.", - "placeholder": "Selecione a fonte de exibição...", - "search-placeholder": "Pesquisar fontes de exibição..." } } }, @@ -978,7 +972,8 @@ "scrolling-mode": { "label": "Modo de rolagem", "description": "Controlar quando a rolagem de texto está habilitada para títulos de faixa longos." - } + }, + "no-active-player": "Nenhum player ativo" }, "clock": { "use-primary-color": { @@ -1140,6 +1135,10 @@ "close": "Fechar" } }, + "launcher": { + "pin": "Fixar no dock", + "unpin": "Desafixar do dock" + }, "placeholders": { "search-icons": "ex., noctalia, niri, battery, cloud", "profile-picture-path": "/home/usuario/.face", @@ -1243,14 +1242,14 @@ "title": "Menu da Sessão", "click-again": "Clique novamente para executar imediatamente", "action-in-seconds": "{action} em {seconds} segundos...", + "lock": "Bloquear", "lock-subtitle": "Bloqueie sua sessão", "lock-and-suspend": "Bloquear e Suspender", "lock-and-suspend-subtitle": "Bloquear sessão e colocar o sistema em suspensão", - "end-subtitle": "Encerre sua sessão", - "lock": "Bloquear", "suspend": "Suspender", "reboot": "Reiniciar", "logout": "Sair", + "logout-subtitle": "Encerre sua sessão", "shutdown": "Desligar" }, "plugins": { @@ -1391,4 +1390,4 @@ "charging": "Carregando.", "discharging": "Descarregando." } -} \ No newline at end of file +} diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index d88f4eba..562e4cda 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -54,6 +54,7 @@ } }, "fonts": { + "reset-scaling": "恢复默认缩放", "section": { "label": "字体", "description": "选择整个界面中使用的字体。" @@ -62,19 +63,21 @@ "label": "默认字体", "description": "整个界面使用的主要字体。", "placeholder": "选择默认字体...", - "search-placeholder": "搜索字体..." + "search-placeholder": "搜索字体...", + "scale": { + "label": "默认字体大小", + "description": "增大或减小标准文本的尺寸" + } }, "monospace": { "label": "等宽字体", "description": "用于数字和统计信息显示的等宽字体。", "placeholder": "选择等宽字体...", - "search-placeholder": "搜索等宽字体..." - }, - "accent": { - "label": "强调字体", - "description": "用于突出显示内容的大字体。", - "placeholder": "选择标题字体...", - "search-placeholder": "搜索标题字体..." + "search-placeholder": "搜索等宽字体...", + "scale": { + "label": "默认等宽字体大小", + "description": "增大或减小等宽文本的尺寸" + } } } }, @@ -161,7 +164,7 @@ "title": "显示", "monitors": { "section": { - "label": "每显示器设置", + "label": "显示器设置", "description": "调整每个显示器的缩放比例和亮度。" }, "scale": "缩放比例", @@ -225,8 +228,8 @@ "description": "调整状态栏的背景不透明度。" }, "show-capsule": { - "label": "显示胶囊", - "description": "显示小部件背景。" + "label": "显示组件背景", + "description": "为每个小部件添加背景。" }, "floating": { "label": "浮动状态栏", @@ -974,7 +977,8 @@ "scrolling-mode": { "label": "滚动模式", "description": "控制何时为长曲目标题启用文本滚动。" - } + }, + "no-active-player": "无活动播放器" }, "clock": { "use-primary-color": { @@ -998,6 +1002,16 @@ "description": "使用空格将每个部分分隔到新行。" }, "preview": "预览" + }, + "taskbar": { + "only-active-workspaces": { + "label": "仅显示活动工作区", + "description": "仅显示来自活动工作区的应用程序" + }, + "only-same-output": { + "label": "仅显示同屏幕", + "description": "仅显示任务栏所在屏幕上的应用程序" + } } } }, @@ -1136,6 +1150,10 @@ "close": "关闭" } }, + "launcher": { + "pin": "固定到 Dock", + "unpin": "从 Dock 取消固定" + }, "placeholders": { "search-icons": "例如:noctalia, niri, battery, cloud", "profile-picture-path": "/home/user/.face", @@ -1239,15 +1257,18 @@ "title": "会话菜单", "click-again": "再次点击立即执行", "action-in-seconds": "{seconds} 秒后{action}...", + "lock": "锁定", "lock-subtitle": "锁定您的会话", "lock-and-suspend": "锁定并挂起", - "lock-and-suspend-subtitle": "锁定会话并将系统挂起", - "end-subtitle": "结束您的会话", - "lock": "锁定", + "lock-and-suspend-subtitle": "锁定会话并使系统进入睡眠状态", "suspend": "挂起", + "suspend-subtitle": "使系统进入睡眠状态", "reboot": "重启", + "reboot-subtitle": "重新启动计算机", "logout": "注销", - "shutdown": "关机" + "logout-subtitle": "结束您的会话", + "shutdown": "关机", + "shutdown-subtitle": "关闭计算机" }, "plugins": { "applications": "应用程序", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 4a8c2d2a..56a8974e 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -115,6 +115,7 @@ "exclusive": false, "backgroundOpacity": 1, "floatingRatio": 1, + "onlySameOutput": true, "monitors": [], "pinnedApps": [] }, @@ -149,7 +150,8 @@ "ui": { "fontDefault": "Roboto", "fontFixed": "DejaVu Sans Mono", - "fontBillboard": "Inter", + "fontDefaultScale": 1, + "fontFixedScale": 1, "monitorsScaling": [], "idleInhibitorEnabled": false }, diff --git a/Commons/I18n.qml b/Commons/I18n.qml index f55b3310..3a013c57 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -53,9 +53,10 @@ Singleton { try { var data = JSON.parse(text()) root.translations = data + Logger.log("I18n", `Loaded translations for "${root.langCode}"`) + root.isLoaded = true root.translationsLoaded() - Logger.log("I18n", `Loaded translations for "${root.langCode}"`) } catch (e) { Logger.error("I18n", `Failed to parse translation file: ${e}`) setLanguage("en") @@ -215,7 +216,7 @@ Singleton { const filePath = `file://${Quickshell.shellDir}/Assets/Translations/${langCode}.json` fileView.path = filePath isLoaded = false - Logger.log("I18n", `Loading translations from: ${filePath}`) + Logger.log("I18n", `Loading translations: ${langCode}`) // Only load fallback translations if we are not using english and english is available if (langCode !== "en" && availableLanguages.includes("en")) { diff --git a/Commons/Icons.qml b/Commons/Icons.qml index 3fca6f5f..88b9b35b 100644 --- a/Commons/Icons.qml +++ b/Commons/Icons.qml @@ -50,7 +50,7 @@ Singleton { } function loadFontWithCacheBusting() { - Logger.log("Icons", "Loading font with cache busting:", cacheBustingPath) + Logger.log("Icons", "Loading font with cache busting") // Destroy old loader first if (currentFontLoader) { diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 59900830..0f19afbb 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -71,7 +71,13 @@ Singleton { id: saveTimer running: false interval: 1000 - onTriggered: settingsFileView.writeAdapter() + onTriggered: { + settingsFileView.writeAdapter() + // Write to fallback location if set + if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) { + settingsFallbackFileView.writeAdapter() + } + } } FileView { @@ -90,15 +96,10 @@ Singleton { } onLoaded: function () { if (!isLoaded) { - Logger.log("Settings", "----------------------------") - Logger.log("Settings", "Settings loaded successfully") + Logger.log("Settings", "Settings loaded") upgradeSettingsData() - validateMonitorConfigurations() - - kickOffServices() - isLoaded = true // Emit the signal @@ -106,11 +107,25 @@ Singleton { } } onLoadFailed: function (error) { - if (error.toString().includes("No such file") || error === 2) + if (error.toString().includes("No such file") || error === 2) { // File doesn't exist, create it with default values writeAdapter() + // Also write to fallback if set + if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) { + settingsFallbackFileView.writeAdapter() + } + } } } + + // Fallback FileView for writing settings to alternate location + FileView { + id: settingsFallbackFileView + path: Quickshell.env("NOCTALIA_SETTINGS_FALLBACK") || "" + adapter: Quickshell.env("NOCTALIA_SETTINGS_FALLBACK") ? adapter : null + printErrors: false + watchChanges: false + } JsonAdapter { id: adapter @@ -233,6 +248,7 @@ Singleton { property bool exclusive: false property real backgroundOpacity: 1.0 property real floatingRatio: 1.0 + property bool onlySameOutput: true property list monitors: [] // Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop") property list pinnedApps: [] @@ -278,7 +294,8 @@ Singleton { property JsonObject ui: JsonObject { property string fontDefault: "Roboto" property string fontFixed: "DejaVu Sans Mono" - property string fontBillboard: "Inter" + property real fontDefaultScale: 1.0 + property real fontFixedScale: 1.0 property list monitorsScaling: [] property bool idleInhibitorEnabled: false } @@ -516,17 +533,4 @@ Singleton { const widgetAfter = JSON.stringify(widget) return (widgetAfter !== widgetBefore) } - - // ----------------------------------------------------- - // Kickoff essential services - function kickOffServices() { - LocationService.init() - NightLightService.apply() - ColorSchemeService.init() - MatugenService.init() - WallpaperService.init() - FontService.init() - HooksService.init() - BluetoothService.init() - } } diff --git a/Commons/TablerIcons.qml b/Commons/TablerIcons.qml index b10e66f8..39743adb 100644 --- a/Commons/TablerIcons.qml +++ b/Commons/TablerIcons.qml @@ -71,6 +71,8 @@ Singleton { "chevron-down": "chevron-down", "caret-up": "caret-up-filled", "caret-down": "caret-down-filled", + "star": "star", + "star-off": "star-off", "battery-exclamation": "battery-exclamation", "battery-charging": "battery-charging", "battery-4": "battery-4", diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 4dd653b2..f40bcd8d 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -13,7 +13,7 @@ Variants { required property ShellScreen modelData - active: Settings.isLoaded && modelData && Settings.data.wallpaper.enabled + active: modelData && Settings.data.wallpaper.enabled sourceComponent: PanelWindow { id: root @@ -41,16 +41,10 @@ Variants { property string futureWallpaper: "" // Fillmode default is "crop" - property real fillMode: 1.0 + property real fillMode: WallpaperService.getFillModeUniform() property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r, Settings.data.wallpaper.fillColor.g, Settings.data.wallpaper.fillColor.b, 1.0) - // On startup assign wallpaper immediately - Component.onCompleted: { - fillMode = WallpaperService.getFillModeUniform() - - var path = modelData ? WallpaperService.getWallpaper(modelData.name) : "" - setWallpaperImmediate(path) - } + Component.onCompleted: setWallpaperInitial() Connections { target: Settings.data.wallpaper @@ -64,7 +58,6 @@ Variants { target: WallpaperService function onWallpaperChanged(screenName, path) { if (screenName === modelData.name) { - // Update wallpaper display // Set wallpaper immediately on startup futureWallpaper = path @@ -230,6 +223,7 @@ Variants { easing.type: Easing.InOutCubic onFinished: { // Swap images after transition completes + currentWallpaper.source = "" currentWallpaper.source = nextWallpaper.source nextWallpaper.source = "" transitionProgress = 0.0 @@ -239,9 +233,20 @@ Variants { } } + function setWallpaperInitial() { + // On startup, defer assigning wallpaper until the service cache is ready, retries every tick + if (!WallpaperService || !WallpaperService.isInitialized) { + Qt.callLater(setWallpaperInitial) + return + } + + setWallpaperImmediate(WallpaperService.getWallpaper(modelData.name)) + } + function setWallpaperImmediate(source) { transitionAnimation.stop() transitionProgress = 0.0 + currentWallpaper.source = "" currentWallpaper.source = source nextWallpaper.source = "" } @@ -255,8 +260,12 @@ Variants { // We are interrupting a transition transitionAnimation.stop() transitionProgress = 0 - currentWallpaper.source = nextWallpaper.source + + const newCurrentSource = nextWallpaper.source + currentWallpaper.source = "" nextWallpaper.source = "" + + currentWallpaper.source = newCurrentSource } nextWallpaper.source = source diff --git a/Modules/Background/Overview.qml b/Modules/Background/Overview.qml index 0828be9a..7aa67240 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -12,7 +12,7 @@ Variants { delegate: Loader { required property ShellScreen modelData - active: Settings.isLoaded && CompositorService.isNiri && modelData && Settings.data.wallpaper.enabled + active: CompositorService.isNiri && modelData && Settings.data.wallpaper.enabled property string wallpaper: "" diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 08c1c202..f1140d8e 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -28,7 +28,7 @@ Variants { } } - active: Settings.isLoaded && BarService.isVisible && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false + active: BarService.isVisible && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false sourceComponent: PanelWindow { screen: modelData || null @@ -55,6 +55,12 @@ Variants { right: Settings.data.bar.floating && Settings.data.bar.position !== "left" ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0 } + Component.onCompleted: { + if (modelData && modelData.name) { + BarService.registerBar(modelData.name) + } + } + Item { anchors.fill: parent clip: true diff --git a/Modules/Bar/Bluetooth/BluetoothDevicesList.qml b/Modules/Bar/Bluetooth/BluetoothDevicesList.qml index a7895e47..f40b13e1 100644 --- a/Modules/Bar/Bluetooth/BluetoothDevicesList.qml +++ b/Modules/Bar/Bluetooth/BluetoothDevicesList.qml @@ -22,7 +22,7 @@ ColumnLayout { NText { text: root.label - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: Color.mSecondary font.weight: Style.fontWeightMedium Layout.fillWidth: true @@ -67,7 +67,7 @@ ColumnLayout { // One device BT icon NIcon { icon: BluetoothService.getDeviceIcon(modelData) - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling color: getContentColor(Color.mOnSurface) Layout.alignment: Qt.AlignVCenter } @@ -79,7 +79,7 @@ ColumnLayout { // Device name NText { text: modelData.name || modelData.deviceName - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightMedium elide: Text.ElideRight color: getContentColor(Color.mOnSurface) @@ -90,7 +90,7 @@ ColumnLayout { NText { text: BluetoothService.getStatusString(modelData) visible: text !== "" - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: getContentColor(Color.mOnSurfaceVariant) } @@ -103,21 +103,21 @@ ColumnLayout { // Device signal strength - "Unknown" when not connected NText { text: BluetoothService.getSignalStrength(modelData) - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: getContentColor(Color.mOnSurfaceVariant) } NIcon { visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked text: BluetoothService.getSignalIcon(modelData) - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: getContentColor(Color.mOnSurface) } NText { visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked text: (modelData.signalStrength !== undefined && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : "" - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: getContentColor(Color.mOnSurface) } } @@ -126,7 +126,7 @@ ColumnLayout { NText { visible: modelData.batteryAvailable text: BluetoothService.getBattery(modelData) - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: getContentColor(Color.mOnSurfaceVariant) } } diff --git a/Modules/Bar/Bluetooth/BluetoothPanel.qml b/Modules/Bar/Bluetooth/BluetoothPanel.qml index c625f1f1..c11d38eb 100644 --- a/Modules/Bar/Bluetooth/BluetoothPanel.qml +++ b/Modules/Bar/Bluetooth/BluetoothPanel.qml @@ -30,13 +30,13 @@ NPanel { NIcon { icon: "bluetooth" - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling color: Color.mPrimary } NText { text: I18n.tr("bluetooth.panel.title") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface Layout.fillWidth: true @@ -88,21 +88,21 @@ NPanel { NIcon { icon: "bluetooth-off" - font.pointSize: 64 * scaling + pointSize: 64 * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: I18n.tr("bluetooth.panel.disabled") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: I18n.tr("bluetooth.panel.enable-message") - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } @@ -186,7 +186,7 @@ NPanel { NIcon { icon: "refresh" - font.pointSize: Style.fontSizeXXL * 1.5 * scaling + pointSize: Style.fontSizeXXL * 1.5 * scaling color: Color.mPrimary RotationAnimation on rotation { @@ -200,14 +200,14 @@ NPanel { NText { text: I18n.tr("bluetooth.panel.scanning") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: Color.mOnSurface } } NText { text: I18n.tr("bluetooth.panel.pairing-mode") - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index ac70310c..8fedb3b9 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -43,7 +43,7 @@ NPanel { text: grid.title Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold color: Color.mPrimary } @@ -83,7 +83,7 @@ NPanel { anchors.centerIn: parent text: I18n.tr("calendar.panel.week") color: Color.mOutline - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling font.weight: Style.fontWeightRegular horizontalAlignment: Text.AlignHCenter } @@ -113,7 +113,7 @@ NPanel { return Qt.locale().dayName(dayIndex, Locale.ShortFormat) } color: Color.mSecondary - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold horizontalAlignment: Text.AlignHCenter } @@ -148,7 +148,7 @@ NPanel { NText { anchors.centerIn: parent color: Color.mOutline - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling font.weight: Style.fontWeightBold text: { // Calculate the date shown in the first column of this row @@ -229,7 +229,7 @@ NPanel { text: model.day color: model.today ? Color.mOnPrimary : Color.mOnSurface opacity: model.month === grid.month ? Style.opacityHeavy : Style.opacityLight - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling font.weight: model.today ? Style.fontWeightBold : Style.fontWeightRegular } diff --git a/Modules/Bar/Extras/BarPillHorizontal.qml b/Modules/Bar/Extras/BarPillHorizontal.qml index f3dac570..95f95b15 100644 --- a/Modules/Bar/Extras/BarPillHorizontal.qml +++ b/Modules/Bar/Extras/BarPillHorizontal.qml @@ -84,8 +84,8 @@ Item { return centerX + offset } text: root.text + root.suffix - font.family: Settings.data.ui.fontFixed - font.pointSize: textSize + family: Settings.data.ui.fontFixed + pointSize: textSize font.weight: Style.fontWeightBold color: forceOpen ? Color.mOnSurface : Color.mPrimary visible: revealed @@ -126,7 +126,7 @@ Item { NIcon { icon: root.icon - font.pointSize: iconSize + pointSize: iconSize color: hovered ? Color.mOnTertiary : Color.mOnSurface // Center horizontally x: (iconCircle.width - width) / 2 diff --git a/Modules/Bar/Extras/BarPillVertical.qml b/Modules/Bar/Extras/BarPillVertical.qml index aeb89f49..d37f454d 100644 --- a/Modules/Bar/Extras/BarPillVertical.qml +++ b/Modules/Bar/Extras/BarPillVertical.qml @@ -98,8 +98,8 @@ Item { return offset } text: root.text + root.suffix - font.family: Settings.data.ui.fontFixed - font.pointSize: textSize + family: Settings.data.ui.fontFixed + pointSize: textSize font.weight: Style.fontWeightMedium horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -151,7 +151,7 @@ Item { NIcon { icon: root.icon - font.pointSize: iconSize + pointSize: iconSize color: hovered ? Color.mOnTertiary : Color.mOnSurface // Center horizontally x: (iconCircle.width - width) / 2 diff --git a/Modules/Bar/Extras/BarWidgetLoader.qml b/Modules/Bar/Extras/BarWidgetLoader.qml index 3aa5911f..358a42ef 100644 --- a/Modules/Bar/Extras/BarWidgetLoader.qml +++ b/Modules/Bar/Extras/BarWidgetLoader.qml @@ -29,7 +29,7 @@ Item { id: loader anchors.fill: parent - active: Settings.isLoaded && widgetId !== "" + active: widgetId !== "" sourceComponent: { if (!active) { return null diff --git a/Modules/Bar/Extras/TrayMenu.qml b/Modules/Bar/Extras/TrayMenu.qml index 8753deb8..415333fd 100644 --- a/Modules/Bar/Extras/TrayMenu.qml +++ b/Modules/Bar/Extras/TrayMenu.qml @@ -160,7 +160,7 @@ PopupWindow { Layout.fillWidth: true color: (modelData?.enabled ?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..." - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap } @@ -175,7 +175,7 @@ PopupWindow { NIcon { icon: modelData?.hasChildren ? "menu" : "" - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling verticalAlignment: Text.AlignVCenter visible: modelData?.hasChildren ?? false color: (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) diff --git a/Modules/Bar/WiFi/WiFiPanel.qml b/Modules/Bar/WiFi/WiFiPanel.qml index ebb8a0b7..4bd32b47 100644 --- a/Modules/Bar/WiFi/WiFiPanel.qml +++ b/Modules/Bar/WiFi/WiFiPanel.qml @@ -35,13 +35,13 @@ NPanel { NIcon { icon: Settings.data.network.wifiEnabled ? "wifi" : "wifi-off" - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling color: Settings.data.network.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant } NText { text: I18n.tr("wifi.panel.title") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface Layout.fillWidth: true @@ -92,14 +92,14 @@ NPanel { NIcon { icon: "warning" - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: Color.mError } NText { text: NetworkService.lastError color: Color.mError - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling wrapMode: Text.Wrap Layout.fillWidth: true } @@ -130,21 +130,21 @@ NPanel { NIcon { icon: "wifi-off" - font.pointSize: 64 * scaling + pointSize: 64 * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: I18n.tr("wifi.panel.disabled") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: I18n.tr("wifi.panel.enable-message") - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } @@ -173,7 +173,7 @@ NPanel { NText { text: I18n.tr("wifi.panel.searching") - font.pointSize: Style.fontSizeNormal * scaling + pointSize: Style.fontSizeNormal * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } @@ -242,7 +242,7 @@ NPanel { NIcon { icon: NetworkService.signalIcon(modelData.signal) - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling color: modelData.connected ? Color.mPrimary : Color.mOnSurface } @@ -252,7 +252,7 @@ NPanel { NText { text: modelData.ssid - font.pointSize: Style.fontSizeNormal * scaling + pointSize: Style.fontSizeNormal * scaling font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium color: Color.mOnSurface elide: Text.ElideRight @@ -266,19 +266,19 @@ NPanel { text: I18n.tr("system.signal-strength", { "signal": modelData.signal }) - font.pointSize: Style.fontSizeXXS * scaling + pointSize: Style.fontSizeXXS * scaling color: Color.mOnSurfaceVariant } NText { text: "•" - font.pointSize: Style.fontSizeXXS * scaling + pointSize: Style.fontSizeXXS * scaling color: Color.mOnSurfaceVariant } NText { text: NetworkService.isSecured(modelData.security) ? modelData.security : "Open" - font.pointSize: Style.fontSizeXXS * scaling + pointSize: Style.fontSizeXXS * scaling color: Color.mOnSurfaceVariant } @@ -298,7 +298,7 @@ NPanel { id: connectedText anchors.centerIn: parent text: I18n.tr("wifi.panel.connected") - font.pointSize: Style.fontSizeXXS * scaling + pointSize: Style.fontSizeXXS * scaling color: Color.mOnPrimary } } @@ -314,7 +314,7 @@ NPanel { id: disconnectingText anchors.centerIn: parent text: I18n.tr("wifi.panel.disconnecting") - font.pointSize: Style.fontSizeXXS * scaling + pointSize: Style.fontSizeXXS * scaling color: Color.mOnPrimary } } @@ -330,7 +330,7 @@ NPanel { id: forgettingText anchors.centerIn: parent text: I18n.tr("wifi.panel.forgetting") - font.pointSize: Style.fontSizeXXS * scaling + pointSize: Style.fontSizeXXS * scaling color: Color.mOnPrimary } } @@ -348,7 +348,7 @@ NPanel { id: savedText anchors.centerIn: parent text: I18n.tr("wifi.panel.saved") - font.pointSize: Style.fontSizeXXS * scaling + pointSize: Style.fontSizeXXS * scaling color: Color.mOnSurfaceVariant } } @@ -439,6 +439,7 @@ NPanel { anchors.verticalCenter: parent.verticalCenter anchors.margins: Style.marginS * scaling text: passwordInput + font.family: Settings.data.ui.fontFixed font.pointSize: Style.fontSizeS * scaling color: Color.mOnSurface echoMode: TextInput.Password @@ -456,12 +457,12 @@ NPanel { } } - Text { + NText { visible: parent.text.length === 0 anchors.verticalCenter: parent.verticalCenter text: I18n.tr("wifi.panel.enter-password") color: Color.mOnSurfaceVariant - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling } } } @@ -508,13 +509,13 @@ NPanel { RowLayout { NIcon { icon: "trash" - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: Color.mError } NText { text: I18n.tr("wifi.panel.forget-network") - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: Color.mError Layout.fillWidth: true } @@ -557,14 +558,14 @@ NPanel { NIcon { icon: "search" - font.pointSize: 64 * scaling + pointSize: 64 * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: I18n.tr("wifi.panel.no-networks") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 6f20c335..711ab18c 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -108,7 +108,7 @@ Item { id: fullTitleMetrics visible: false text: windowTitle - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightMedium } @@ -239,13 +239,13 @@ Item { property real scrollX: 0 x: scrollX - Row { + RowLayout { spacing: 50 * scaling // Gap between text copies NText { id: titleText text: windowTitle - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightMedium verticalAlignment: Text.AlignVCenter color: Color.mOnSurface diff --git a/Modules/Bar/Widgets/Clock.qml b/Modules/Bar/Widgets/Clock.qml index ae179ebe..810d2b9f 100644 --- a/Modules/Bar/Widgets/Clock.qml +++ b/Modules/Bar/Widgets/Clock.qml @@ -61,12 +61,12 @@ Rectangle { spacing: Settings.data.bar.showCapsule ? -4 * scaling : -2 * scaling Repeater { id: repeater - model: Qt.formatDateTime(now, formatHorizontal.trim()).split("\\n") + model: Qt.locale().toString(now, formatHorizontal.trim()).split("\\n") NText { visible: text !== "" text: modelData - font.family: useMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault - font.pointSize: { + family: useMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault + pointSize: { if (repeater.model.length == 1) { return Style.fontSizeS * scaling } else { @@ -91,12 +91,12 @@ Rectangle { anchors.centerIn: parent spacing: -2 * scaling Repeater { - model: Qt.formatDateTime(now, formatVertical.trim()).split(" ") + model: Qt.locale().toString(now, formatVertical.trim()).split(" ") delegate: NText { visible: text !== "" text: modelData - font.family: useMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault - font.pointSize: Style.fontSizeS * scaling + family: useMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault + pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightBold color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface wrapMode: Text.WordWrap diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index 69c1b39b..65eb3b8c 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -54,7 +54,7 @@ NIconButton { height: width source: { if (customIconPath !== "") - return customIconPath + return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath if (useDistroLogo) return DistroLogoService.osLogo return "" diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index b9e23efd..ae960cd5 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -42,6 +42,9 @@ Item { // Fixed width - no expansion readonly property real widgetWidth: Math.max(1, screen.width * 0.06) + readonly property bool hasActivePlayer: MediaService.currentPlayer !== null && getTitle() !== "" + readonly property string placeholderText: I18n.tr("bar.widget-settings.media-mini.no-active-player") + readonly property string tooltipText: { var title = getTitle() var controls = "" @@ -60,7 +63,7 @@ Item { implicitHeight: visible ? ((barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)) : 0 implicitWidth: visible ? ((barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)) : 0 - opacity: !autoHide || getTitle() !== "" ? 1.0 : 0 + opacity: !autoHide || hasActivePlayer || (!hasActivePlayer && !autoHide) ? 1.0 : 0 Behavior on opacity { NumberAnimation { duration: Style.animationNormal @@ -151,21 +154,22 @@ Item { anchors.verticalCenter: parent.verticalCenter spacing: Style.marginS * scaling - visible: (barPosition === "top" || barPosition === "bottom") && getTitle() !== "" + visible: (barPosition === "top" || barPosition === "bottom") z: 1 // Above the visualizer NIcon { id: windowIcon - icon: MediaService.isPlaying ? "media-pause" : "media-play" - font.pointSize: Style.fontSizeL * scaling + icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc" + color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant + pointSize: Style.fontSizeL * scaling verticalAlignment: Text.AlignVCenter Layout.alignment: Qt.AlignVCenter - visible: !showAlbumArt && getTitle() !== "" && !trackArt.visible + visible: !hasActivePlayer || (!showAlbumArt && !trackArt.visible) } ColumnLayout { Layout.alignment: Qt.AlignVCenter - visible: showAlbumArt + visible: showAlbumArt && hasActivePlayer spacing: 0 Item { @@ -187,6 +191,10 @@ Item { Item { id: titleContainer Layout.preferredWidth: { + if (!hasActivePlayer) { + // When no active player, take full width to center the placeholder + return mainContainer.width - Style.marginXXS * scaling * 2 + } // Calculate available width based on other elements in the row var iconWidth = (windowIcon.visible ? (Style.fontSizeL * scaling + Style.marginS * scaling) : 0) var albumArtWidth = (showAlbumArt ? (18 * scaling + Style.marginS * scaling) : 0) @@ -195,7 +203,7 @@ Item { return Math.max(20 * scaling, availableWidth) } Layout.maximumWidth: Layout.preferredWidth - Layout.alignment: Qt.AlignVCenter + Layout.alignment: hasActivePlayer ? Qt.AlignVCenter : Qt.AlignCenter Layout.preferredHeight: titleText.height clip: true @@ -203,7 +211,7 @@ Item { property bool isScrolling: false property bool isResetting: false property real textWidth: fullTitleMetrics.contentWidth - property real containerWidth: width + property real containerWidth: 0 property bool needsScrolling: textWidth > containerWidth // Timer for "always" mode with delay @@ -250,8 +258,15 @@ Item { } } - onWidthChanged: updateScrollingState() - Component.onCompleted: updateScrollingState() + onWidthChanged: { + containerWidth = width + updateScrollingState() + } + + Component.onCompleted: { + containerWidth = width + updateScrollingState() + } Connections { target: mouseArea @@ -264,29 +279,31 @@ Item { Item { id: scrollContainer height: parent.height - width: childrenRect.width + width: parent.width property real scrollX: 0 - x: scrollX + x: hasActivePlayer ? scrollX : 0 - Row { + RowLayout { spacing: 50 * scaling // Gap between text copies NText { id: titleText - text: getTitle() - font.pointSize: Style.fontSizeS * scaling + text: hasActivePlayer ? getTitle() : placeholderText + pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightMedium verticalAlignment: Text.AlignVCenter - color: Color.mOnSurface + horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter + color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant } NText { - text: getTitle() + text: hasActivePlayer ? getTitle() : placeholderText font: titleText.font verticalAlignment: Text.AlignVCenter - color: Color.mOnSurface - visible: titleContainer.needsScrolling && titleContainer.isScrolling + horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter + color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant + visible: hasActivePlayer && titleContainer.needsScrolling && titleContainer.isScrolling } } @@ -336,13 +353,13 @@ Item { width: Style.baseWidgetSize * 0.5 * scaling height: Style.baseWidgetSize * 0.5 * scaling anchors.centerIn: parent - visible: getTitle() !== "" NIcon { id: mediaIconVertical anchors.fill: parent - icon: MediaService.isPlaying ? "media-pause" : "media-play" - font.pointSize: Style.fontSizeL * scaling + icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc" + color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant + pointSize: Style.fontSizeL * scaling verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } @@ -354,10 +371,10 @@ Item { id: mouseArea anchors.fill: parent hoverEnabled: true - cursorShape: Qt.PointingHandCursor + cursorShape: hasActivePlayer ? Qt.PointingHandCursor : Qt.ArrowCursor acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton onClicked: mouse => { - if (!MediaService.currentPlayer || !MediaService.canPlay) { + if (!hasActivePlayer || !MediaService.currentPlayer || !MediaService.canPlay) { return } @@ -375,8 +392,9 @@ Item { } onEntered: { - if ((tooltipText !== "") && (barPosition === "left" || barPosition === "right") || (scrollingMode === "never")) { - TooltipService.show(root, tooltipText, BarService.getTooltipDirection()) + var textToShow = hasActivePlayer ? tooltipText : placeholderText + if ((textToShow !== "") && (barPosition === "left" || barPosition === "right") || (scrollingMode === "never")) { + TooltipService.show(root, textToShow, BarService.getTooltipDirection()) } } onExited: { diff --git a/Modules/Bar/Widgets/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml index 2376af86..2d18b8b5 100644 --- a/Modules/Bar/Widgets/SystemMonitor.qml +++ b/Modules/Bar/Widgets/SystemMonitor.qml @@ -53,7 +53,7 @@ Rectangle { id: percentMetrics font.family: Settings.data.ui.fontFixed font.weight: Style.fontWeightMedium - font.pointSize: textSize + font.pointSize: textSize * Settings.data.ui.fontFixedScale text: "99%" // Use the longest possible string for measurement } @@ -61,7 +61,7 @@ Rectangle { id: tempMetrics font.family: Settings.data.ui.fontFixed font.weight: Style.fontWeightMedium - font.pointSize: textSize + font.pointSize: textSize * Settings.data.ui.fontFixedScale text: "99°" // Use the longest possible string for measurement } @@ -69,7 +69,7 @@ Rectangle { id: memMetrics font.family: Settings.data.ui.fontFixed font.weight: Style.fontWeightMedium - font.pointSize: textSize + font.pointSize: textSize * Settings.data.ui.fontFixedScale text: "99.9K" // Longest value part of network speed } @@ -106,7 +106,7 @@ Rectangle { NIcon { icon: "cpu-usage" - font.pointSize: iconSize + pointSize: iconSize Layout.alignment: Qt.AlignCenter Layout.row: isVertical ? 1 : 0 Layout.column: 0 @@ -114,8 +114,8 @@ Rectangle { NText { text: `${Math.round(SystemStatService.cpuUsage)}%` - font.family: Settings.data.ui.fontFixed - font.pointSize: textSize + family: Settings.data.ui.fontFixed + pointSize: textSize font.weight: Style.fontWeightMedium Layout.alignment: Qt.AlignCenter Layout.preferredWidth: isVertical ? -1 : percentTextWidth @@ -147,7 +147,7 @@ Rectangle { NIcon { icon: "cpu-temperature" - font.pointSize: iconSize + pointSize: iconSize Layout.alignment: Qt.AlignCenter Layout.row: isVertical ? 1 : 0 Layout.column: 0 @@ -155,8 +155,8 @@ Rectangle { NText { text: `${Math.round(SystemStatService.cpuTemp)}°` - font.family: Settings.data.ui.fontFixed - font.pointSize: textSize + family: Settings.data.ui.fontFixed + pointSize: textSize font.weight: Style.fontWeightMedium Layout.alignment: Qt.AlignCenter Layout.preferredWidth: isVertical ? -1 : tempTextWidth @@ -188,7 +188,7 @@ Rectangle { NIcon { icon: "memory" - font.pointSize: iconSize + pointSize: iconSize Layout.alignment: Qt.AlignCenter Layout.row: isVertical ? 1 : 0 Layout.column: 0 @@ -196,8 +196,8 @@ Rectangle { NText { text: showMemoryAsPercent ? `${Math.round(SystemStatService.memPercent)}%` : `${SystemStatService.memGb.toFixed(1)}G` - font.family: Settings.data.ui.fontFixed - font.pointSize: textSize + family: Settings.data.ui.fontFixed + pointSize: textSize font.weight: Style.fontWeightMedium Layout.alignment: Qt.AlignCenter Layout.preferredWidth: isVertical ? -1 : (showMemoryAsPercent ? percentTextWidth : memTextWidth) @@ -229,7 +229,7 @@ Rectangle { NIcon { icon: "download-speed" - font.pointSize: iconSize + pointSize: iconSize Layout.alignment: Qt.AlignCenter Layout.row: isVertical ? 1 : 0 Layout.column: 0 @@ -237,8 +237,8 @@ Rectangle { NText { text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.rxSpeed) : SystemStatService.formatSpeed(SystemStatService.rxSpeed) - font.family: Settings.data.ui.fontFixed - font.pointSize: textSize + family: Settings.data.ui.fontFixed + pointSize: textSize font.weight: Style.fontWeightMedium Layout.alignment: Qt.AlignCenter Layout.preferredWidth: isVertical ? -1 : memTextWidth @@ -270,7 +270,7 @@ Rectangle { NIcon { icon: "upload-speed" - font.pointSize: iconSize + pointSize: iconSize Layout.alignment: Qt.AlignCenter Layout.row: isVertical ? 1 : 0 Layout.column: 0 @@ -278,8 +278,8 @@ Rectangle { NText { text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.txSpeed) : SystemStatService.formatSpeed(SystemStatService.txSpeed) - font.family: Settings.data.ui.fontFixed - font.pointSize: textSize + family: Settings.data.ui.fontFixed + pointSize: textSize font.weight: Style.fontWeightMedium Layout.alignment: Qt.AlignCenter Layout.preferredWidth: isVertical ? -1 : memTextWidth @@ -311,7 +311,7 @@ Rectangle { NIcon { icon: "storage" - font.pointSize: iconSize + pointSize: iconSize Layout.alignment: Qt.AlignCenter Layout.row: isVertical ? 1 : 0 Layout.column: 0 @@ -319,8 +319,8 @@ Rectangle { NText { text: `${SystemStatService.diskPercent}%` - font.family: Settings.data.ui.fontFixed - font.pointSize: textSize + family: Settings.data.ui.fontFixed + pointSize: textSize font.weight: Style.fontWeightMedium Layout.alignment: Qt.AlignCenter Layout.preferredWidth: isVertical ? -1 : percentTextWidth diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml index e578622f..12ade07d 100644 --- a/Modules/Bar/Widgets/Taskbar.qml +++ b/Modules/Bar/Widgets/Taskbar.qml @@ -13,10 +13,27 @@ Rectangle { property ShellScreen screen property real scaling: 1.0 + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" readonly property bool compact: (Settings.data.bar.density === "compact") readonly property real itemSize: compact ? Style.capsuleHeight * 0.9 * scaling : Style.capsuleHeight * 0.8 * scaling + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + // Always visible when there are toplevels implicitWidth: isVerticalBar ? Math.round(Style.capsuleHeight * scaling) : taskbarLayout.implicitWidth + Style.marginM * scaling * 2 implicitHeight: isVerticalBar ? taskbarLayout.implicitHeight + Style.marginM * scaling * 2 : Math.round(Style.capsuleHeight * scaling) @@ -41,36 +58,36 @@ Rectangle { columnSpacing: isVerticalBar ? 0 : Style.marginXXS * root.scaling Repeater { - model: ToplevelManager && ToplevelManager.toplevels ? ToplevelManager.toplevels : [] + model: CompositorService.windows delegate: Item { id: taskbarItem - required property Toplevel modelData - property Toplevel toplevel: modelData - property bool isActive: ToplevelManager.activeToplevel === modelData + required property var modelData + + visible: (!widgetSettings.onlySameOutput || modelData.output == screen.name) && (!widgetSettings.onlyActiveWorkspaces || CompositorService.getActiveWorkspaces().map(ws => ws.id).includes(modelData.workspaceId)) Layout.preferredWidth: root.itemSize Layout.preferredHeight: root.itemSize Layout.alignment: Qt.AlignCenter - Rectangle { - id: iconBackground - anchors.centerIn: parent + IconImage { + + id: appIcon width: parent.width height: parent.height - color: taskbarItem.isActive ? Color.mPrimary : root.color - border.width: 0 - radius: Math.round(Style.radiusXS * root.scaling) - border.color: "transparent" - z: -1 + source: ThemeIcons.iconForAppId(taskbarItem.modelData.appId) + smooth: true + asynchronous: true + opacity: modelData.isFocused ? 1.0 : 0.5 - IconImage { - id: appIcon - anchors.centerIn: parent - width: parent.width - height: parent.height - source: ThemeIcons.iconForAppId(taskbarItem.modelData.appId) - smooth: true - asynchronous: true + Rectangle { + anchors.bottomMargin: -2 * scaling + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + id: iconBackground + width: 4 * scaling + height: 4 * scaling + color: modelData.isFocused ? Color.mPrimary : Color.transparent + radius: width * 0.5 } } @@ -86,13 +103,13 @@ Rectangle { if (mouse.button === Qt.LeftButton) { try { - taskbarItem.modelData.activate() + CompositorService.focusWindow(taskbarItem.modelData.id) } catch (error) { Logger.error("Taskbar", "Failed to activate toplevel: " + error) } } else if (mouse.button === Qt.RightButton) { try { - taskbarItem.modelData.close() + CompositorService.closeWindow(taskbarItem.modelData.id) } catch (error) { Logger.error("Taskbar", "Failed to close toplevel: " + error) } diff --git a/Modules/Bar/Widgets/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml index 33c1f580..689b962f 100644 --- a/Modules/Bar/Widgets/Workspace.qml +++ b/Modules/Bar/Widgets/Workspace.qml @@ -7,6 +7,7 @@ import Quickshell import Quickshell.Io import qs.Commons import qs.Services +import qs.Widgets Item { id: root @@ -274,7 +275,7 @@ Item { Loader { active: (labelMode !== "none") sourceComponent: Component { - Text { + NText { x: (pill.width - width) / 2 y: (pill.height - height) / 2 + (height - contentHeight) / 2 text: { @@ -284,9 +285,9 @@ Item { return model.idx.toString() } } - font.pointSize: model.isFocused ? workspacePillContainer.height * 0.45 : workspacePillContainer.height * 0.42 + family: Settings.data.ui.fontFixed + pointSize: model.isFocused ? workspacePillContainer.height * 0.45 : workspacePillContainer.height * 0.42 font.capitalization: Font.AllUppercase - font.family: Settings.data.ui.fontFixed font.weight: Style.fontWeightBold wrapMode: Text.Wrap color: { @@ -418,7 +419,7 @@ Item { Loader { active: (labelMode !== "none") sourceComponent: Component { - Text { + NText { x: (pillVertical.width - width) / 2 y: (pillVertical.height - height) / 2 + (height - contentHeight) / 2 text: { @@ -428,9 +429,9 @@ Item { return model.idx.toString() } } - font.pointSize: model.isFocused ? workspacePillContainerVertical.width * 0.45 : workspacePillContainerVertical.width * 0.42 + family: Settings.data.ui.fontFixed + pointSize: model.isFocused ? workspacePillContainerVertical.width * 0.45 : workspacePillContainerVertical.width * 0.42 font.capitalization: Font.AllUppercase - font.family: Settings.data.ui.fontFixed font.weight: Style.fontWeightBold wrapMode: Text.Wrap color: { diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml index 032eb106..be99ef13 100644 --- a/Modules/ControlCenter/Cards/MediaCard.qml +++ b/Modules/ControlCenter/Cards/MediaCard.qml @@ -28,7 +28,7 @@ NBox { NIcon { icon: "disc" - font.pointSize: Style.fontSizeXXXL * 3 * scaling + pointSize: Style.fontSizeXXXL * 3 * scaling color: Color.mPrimary Layout.alignment: Qt.AlignHCenter } @@ -68,13 +68,13 @@ NBox { NIcon { icon: "caret-down" - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling color: Color.mOnSurfaceVariant } NText { text: playerSelectorButton.currentPlayer ? playerSelectorButton.currentPlayer.identity : "" - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: Color.mOnSurfaceVariant Layout.fillWidth: true } @@ -147,7 +147,7 @@ NBox { NIcon { icon: "disc" color: Color.mPrimary - font.pointSize: Style.fontSizeXXXL * 3 * scaling + pointSize: Style.fontSizeXXXL * 3 * scaling visible: !trackArt.visible anchors.centerIn: parent } @@ -162,7 +162,7 @@ NBox { NText { visible: MediaService.trackTitle !== "" text: MediaService.trackTitle - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold elide: Text.ElideRight wrapMode: Text.Wrap @@ -174,7 +174,7 @@ NBox { visible: MediaService.trackArtist !== "" text: MediaService.trackArtist color: Color.mPrimary - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling elide: Text.ElideRight Layout.fillWidth: true } @@ -183,7 +183,7 @@ NBox { visible: MediaService.trackAlbum !== "" text: MediaService.trackAlbum color: Color.mOnSurface - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling elide: Text.ElideRight Layout.fillWidth: true } diff --git a/Modules/ControlCenter/Cards/ProfileCard.qml b/Modules/ControlCenter/Cards/ProfileCard.qml index 9b619edf..6728ef7f 100644 --- a/Modules/ControlCenter/Cards/ProfileCard.qml +++ b/Modules/ControlCenter/Cards/ProfileCard.qml @@ -45,7 +45,7 @@ NBox { text: I18n.tr("system.uptime", { "uptime": uptimeText }) - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: Color.mOnSurfaceVariant } } diff --git a/Modules/ControlCenter/Cards/WeatherCard.qml b/Modules/ControlCenter/Cards/WeatherCard.qml index 160c48cc..778d277b 100644 --- a/Modules/ControlCenter/Cards/WeatherCard.qml +++ b/Modules/ControlCenter/Cards/WeatherCard.qml @@ -24,7 +24,7 @@ NBox { NIcon { Layout.alignment: Qt.AlignVCenter icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : "" - font.pointSize: Style.fontSizeXXXL * 1.75 * scaling + pointSize: Style.fontSizeXXXL * 1.75 * scaling color: Color.mPrimary } @@ -36,7 +36,7 @@ NBox { const chunks = Settings.data.location.name.split(",") return chunks[0] } - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold } @@ -56,13 +56,13 @@ NBox { temp = Math.round(temp) return `${temp}°${suffix}` } - font.pointSize: Style.fontSizeXL * scaling + pointSize: Style.fontSizeXL * scaling font.weight: Style.fontWeightBold } NText { text: weatherReady ? `(${LocationService.data.weather.timezone_abbreviation})` : "" - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: Color.mOnSurfaceVariant visible: LocationService.data.weather } @@ -88,7 +88,7 @@ NBox { NText { text: { var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")) - return Qt.formatDateTime(weatherDate, "ddd") + return Qt.locale().toString(weatherDate, "ddd") } color: Color.mOnSurface Layout.alignment: Qt.AlignHCenter @@ -96,7 +96,7 @@ NBox { NIcon { Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index]) - font.pointSize: Style.fontSizeXXL * 1.6 * scaling + pointSize: Style.fontSizeXXL * 1.6 * scaling color: Color.mPrimary } NText { @@ -112,7 +112,7 @@ NBox { min = Math.round(min) return `${max}°/${min}°` } - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: Color.mOnSurfaceVariant } } diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index f02f8040..5652deae 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -14,8 +14,19 @@ Variants { delegate: Item { id: root + required property ShellScreen modelData property real scaling: ScalingService.getScreenScale(modelData) + property bool barIsReady: BarService.isBarReady(modelData.name) + + Connections { + target: BarService + function onBarReadyChanged(screenName) { + if (screenName === modelData.name) { + barIsReady = true + } + } + } Connections { target: ScalingService @@ -40,19 +51,14 @@ Variants { function onPinnedAppsChanged() { updateDockApps() } - } - - // Initial update when component is ready - Component.onCompleted: { - if (Settings.isLoaded && ToplevelManager) { + function onOnlySameOutputChanged() { updateDockApps() } } - // Update when Settings are loaded - Connections { - target: Settings - function onSettingsLoaded() { + // Initial update when component is ready + Component.onCompleted: { + if (ToplevelManager) { updateDockApps() } } @@ -105,7 +111,7 @@ Variants { // Strategy: Maintain app positions as much as possible // 1. First pass: Add all running apps (both pinned and non-pinned) in their current order runningApps.forEach(toplevel => { - if (toplevel && toplevel.appId) { + if (toplevel && toplevel.appId && !(Settings.data.dock.onlySameOutput && toplevel.screens && !toplevel.screens.includes(modelData))) { const isPinned = pinnedApps.includes(toplevel.appId) const appType = isPinned ? "pinned-running" : "running" @@ -187,7 +193,7 @@ Variants { // PEEK WINDOW - Always visible when auto-hide is enabled Loader { - active: Settings.isLoaded && modelData && Settings.data.dock.monitors.includes(modelData.name) && autoHide + active: (barIsReady || !hasBar) && modelData && Settings.data.dock.monitors.includes(modelData.name) && autoHide sourceComponent: PanelWindow { id: peekWindow @@ -233,7 +239,7 @@ Variants { // DOCK WINDOW Loader { - active: Settings.isLoaded && modelData && Settings.data.dock.monitors.includes(modelData.name) && dockLoaded && ToplevelManager && (dockApps.length > 0) + active: (barIsReady || !hasBar) && modelData && Settings.data.dock.monitors.includes(modelData.name) && dockLoaded && ToplevelManager && (dockApps.length > 0) sourceComponent: PanelWindow { id: dockWindow @@ -414,7 +420,7 @@ Variants { anchors.centerIn: parent visible: !appIcon.visible icon: "question-mark" - font.pointSize: iconSize * 0.7 + pointSize: iconSize * 0.7 color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant opacity: appButton.isRunning ? 1.0 : 0.6 scale: appButton.hovered ? 1.15 : 1.0 diff --git a/Modules/Dock/DockMenu.qml b/Modules/Dock/DockMenu.qml index a30ff193..30de31e4 100644 --- a/Modules/Dock/DockMenu.qml +++ b/Modules/Dock/DockMenu.qml @@ -168,7 +168,7 @@ PopupWindow { } } - Column { + ColumnLayout { id: contextMenuColumn anchors.fill: parent anchors.margins: Style.marginM * scaling @@ -176,12 +176,12 @@ PopupWindow { // Focus item Rectangle { - width: parent.width + Layout.fillWidth: true height: 32 * scaling color: root.hoveredItem === 0 ? Color.mTertiary : Color.transparent radius: Style.radiusXS * scaling - Row { + RowLayout { anchors.left: parent.left anchors.leftMargin: Style.marginS * scaling anchors.verticalCenter: parent.verticalCenter @@ -189,28 +189,28 @@ PopupWindow { NIcon { icon: "eye" - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } NText { text: I18n.tr("dock.menu.focus") - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } } } // Pin/Unpin item Rectangle { - width: parent.width + Layout.fillWidth: true height: 32 * scaling color: root.hoveredItem === 1 ? Color.mTertiary : Color.transparent radius: Style.radiusXS * scaling - Row { + RowLayout { anchors.left: parent.left anchors.leftMargin: Style.marginS * scaling anchors.verticalCenter: parent.verticalCenter @@ -222,9 +222,9 @@ PopupWindow { return "pin" return root.isAppPinned(root.toplevel.appId) ? "unpin" : "pin" } - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } NText { @@ -233,21 +233,21 @@ PopupWindow { return I18n.tr("dock.menu.pin") return root.isAppPinned(root.toplevel.appId) ? I18n.tr("dock.menu.unpin") : I18n.tr("dock.menu.pin") } - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } } } // Close item Rectangle { - width: parent.width + Layout.fillWidth: true height: 32 * scaling color: root.hoveredItem === 2 ? Color.mTertiary : Color.transparent radius: Style.radiusXS * scaling - Row { + RowLayout { anchors.left: parent.left anchors.leftMargin: Style.marginS * scaling anchors.verticalCenter: parent.verticalCenter @@ -255,16 +255,16 @@ PopupWindow { NIcon { icon: "close" - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } NText { text: I18n.tr("dock.menu.close") - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } } } diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index 0714530f..195512fd 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -302,11 +302,34 @@ NPanel { positionViewAtIndex(currentIndex, ListView.Contain) } } + onModelChanged: { + + } delegate: Rectangle { id: entry property bool isSelected: mouseArea.containsMouse || (index === selectedIndex) + // Accessor for app id + property string appId: (modelData && modelData.appId) ? String(modelData.appId) : "" + + // Pin helpers + function togglePin(appId) { + if (!appId) + return + let arr = (Settings.data.dock.pinnedApps || []).slice() + const idx = arr.indexOf(appId) + if (idx >= 0) + arr.splice(idx, 1) + else + arr.push(appId) + Settings.data.dock.pinnedApps = arr + } + + function isPinned(appId) { + const arr = Settings.data.dock.pinnedApps || [] + return appId && arr.indexOf(appId) >= 0 + } // Property to reliably track the current item's ID. // This changes whenever the delegate is recycled for a new item. @@ -321,7 +344,7 @@ NPanel { } width: resultsList.width - Style.marginS * scaling - height: entryHeight + implicitHeight: entryHeight radius: Style.radiusM * scaling color: entry.isSelected ? Color.mTertiary : Color.mSurface @@ -332,136 +355,152 @@ NPanel { } } - RowLayout { + ColumnLayout { + id: contentLayout anchors.fill: parent anchors.margins: Style.marginM * scaling spacing: Style.marginM * scaling - // Icon badge or Image preview - Rectangle { - Layout.preferredWidth: badgeSize - Layout.preferredHeight: badgeSize - radius: Style.radiusM * scaling - color: Color.mSurfaceVariant - clip: true + // Top row - Main entry content with pin button + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM * scaling - // Image preview for clipboard images - NImageRounded { - id: imagePreview - anchors.fill: parent - visible: modelData.isImage - imageRadius: Style.radiusM * scaling - - // This property creates a dependency on the service's revision counter - readonly property int _rev: ClipboardService.revision - - // Fetches from the service's cache. - // The dependency on `_rev` ensures this binding is re-evaluated when the cache is updated. - imagePath: { - _rev - return ClipboardService.getImageData(modelData.clipboardId) || "" - } - - // Loading indicator - Rectangle { - anchors.fill: parent - visible: parent.status === Image.Loading - color: Color.mSurfaceVariant - - BusyIndicator { - anchors.centerIn: parent - running: true - width: Style.baseWidgetSize * 0.5 * scaling - height: width - } - } - - // Error fallback - onStatusChanged: status => { - if (status === Image.Error) { - iconLoader.visible = true - imagePreview.visible = false - } - } - } - - // Icon fallback - Loader { - id: iconLoader - anchors.fill: parent - anchors.margins: Style.marginXS * scaling - - visible: !modelData.isImage || imagePreview.status === Image.Error - active: visible - - sourceComponent: Component { - IconImage { - anchors.fill: parent - source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : "" - visible: modelData.icon && source !== "" - asynchronous: true - } - } - } - - // Fallback text if no icon and no image - NText { - anchors.centerIn: parent - visible: !imagePreview.visible && !iconLoader.visible - text: modelData.name ? modelData.name.charAt(0).toUpperCase() : "?" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnPrimary - } - - // Image type indicator overlay + // Icon badge or Image preview Rectangle { - visible: modelData.isImage && imagePreview.visible - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.margins: 2 * scaling - width: formatLabel.width + 6 * scaling - height: formatLabel.height + 2 * scaling + Layout.preferredWidth: badgeSize + Layout.preferredHeight: badgeSize radius: Style.radiusM * scaling color: Color.mSurfaceVariant + clip: true + // Image preview for clipboard images + NImageRounded { + id: imagePreview + anchors.fill: parent + visible: modelData.isImage + imageRadius: Style.radiusM * scaling + + // This property creates a dependency on the service's revision counter + readonly property int _rev: ClipboardService.revision + + // Fetches from the service's cache. + // The dependency on `_rev` ensures this binding is re-evaluated when the cache is updated. + imagePath: { + _rev + return ClipboardService.getImageData(modelData.clipboardId) || "" + } + + // Loading indicator + Rectangle { + anchors.fill: parent + visible: parent.status === Image.Loading + color: Color.mSurfaceVariant + + BusyIndicator { + anchors.centerIn: parent + running: true + width: Style.baseWidgetSize * 0.5 * scaling + height: width + } + } + + // Error fallback + onStatusChanged: status => { + if (status === Image.Error) { + iconLoader.visible = true + imagePreview.visible = false + } + } + } + + // Icon fallback + Loader { + id: iconLoader + anchors.fill: parent + anchors.margins: Style.marginXS * scaling + + visible: !modelData.isImage || imagePreview.status === Image.Error + active: visible + + sourceComponent: Component { + IconImage { + anchors.fill: parent + source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : "" + visible: modelData.icon && source !== "" + asynchronous: true + } + } + } + + // Fallback text if no icon and no image NText { - id: formatLabel anchors.centerIn: parent - text: { - if (!modelData.isImage) - return "" - const desc = modelData.description || "" - const parts = desc.split(" • ") - return parts[0] || "IMG" + visible: !imagePreview.visible && !iconLoader.visible + text: modelData.name ? modelData.name.charAt(0).toUpperCase() : "?" + pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + } + + // Image type indicator overlay + Rectangle { + visible: modelData.isImage && imagePreview.visible + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: 2 * scaling + width: formatLabel.width + 6 * scaling + height: formatLabel.height + 2 * scaling + radius: Style.radiusM * scaling + color: Color.mSurfaceVariant + + NText { + id: formatLabel + anchors.centerIn: parent + text: { + if (!modelData.isImage) + return "" + const desc = modelData.description || "" + const parts = desc.split(" • ") + return parts[0] || "IMG" + } + pointSize: Style.fontSizeXXS * scaling + color: Color.mPrimary } - font.pointSize: Style.fontSizeXXS * scaling - color: Color.mPrimary } } - } - // Text content - ColumnLayout { - Layout.fillWidth: true - spacing: 0 * scaling - - NText { - text: modelData.name || "Unknown" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: entry.isSelected ? Color.mOnTertiary : Color.mOnSurface - elide: Text.ElideRight + // Text content + ColumnLayout { Layout.fillWidth: true + spacing: 0 * scaling + + NText { + text: modelData.name || "Unknown" + pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: entry.isSelected ? Color.mOnTertiary : Color.mOnSurface + elide: Text.ElideRight + Layout.fillWidth: true + } + + NText { + text: modelData.description || "" + pointSize: Style.fontSizeS * scaling + color: entry.isSelected ? Color.mOnTertiary : Color.mOnSurfaceVariant + elide: Text.ElideRight + Layout.fillWidth: true + visible: text !== "" + } } - NText { - text: modelData.description || "" - font.pointSize: Style.fontSizeS * scaling - color: entry.isSelected ? Color.mOnTertiary : Color.mOnSurfaceVariant - elide: Text.ElideRight - Layout.fillWidth: true - visible: text !== "" + // Pin/Unpin action icon button + NIconButton { + visible: !!entry.appId && !modelData.isImage && entry.isSelected && (Settings.data.dock.monitors && Settings.data.dock.monitors.length > 0) + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + icon: entry.isPinned(entry.appId) ? "unpin" : "pin" + tooltipText: entry.isPinned(entry.appId) ? I18n.tr("launcher.unpin") : I18n.tr("launcher.pin") + onClicked: entry.togglePin(entry.appId) } } } @@ -469,12 +508,17 @@ NPanel { MouseArea { id: mouseArea anchors.fill: parent + z: -1 hoverEnabled: true cursorShape: Qt.PointingHandCursor - onClicked: { - selectedIndex = index - ui.activate() - } + onClicked: mouse => { + if (mouse.button === Qt.LeftButton) { + selectedIndex = index + ui.activate() + mouse.accepted = true + } + } + acceptedButtons: Qt.LeftButton } } } @@ -492,7 +536,7 @@ NPanel { const prefix = activePlugin?.name ? `${activePlugin.name}: ` : "" return prefix + `${results.length} result${results.length !== 1 ? 's' : ''}` } - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: Color.mOnSurfaceVariant horizontalAlignment: Text.AlignCenter } diff --git a/Modules/Launcher/Plugins/ApplicationsPlugin.qml b/Modules/Launcher/Plugins/ApplicationsPlugin.qml index c92d883b..f6d7df93 100644 --- a/Modules/Launcher/Plugins/ApplicationsPlugin.qml +++ b/Modules/Launcher/Plugins/ApplicationsPlugin.qml @@ -69,9 +69,15 @@ Item { if (!query || query.trim() === "") { // Return all apps, optionally sorted by usage + const favoriteApps = Settings.data.appLauncher.favoriteApps || [] let sorted if (Settings.data.appLauncher.sortByMostUsed) { sorted = entries.slice().sort((a, b) => { + // Favorites first + const aFav = favoriteApps.includes(getAppKey(a)) + const bFav = favoriteApps.includes(getAppKey(b)) + if (aFav !== bFav) + return aFav ? -1 : 1 const ua = getUsageCount(a) const ub = getUsageCount(b) if (ub !== ua) @@ -79,7 +85,13 @@ Item { return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase()) }) } else { - sorted = entries.slice().sort((a, b) => (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase())) + sorted = entries.slice().sort((a, b) => { + const aFav = favoriteApps.includes(getAppKey(a)) + const bFav = favoriteApps.includes(getAppKey(b)) + if (aFav !== bFav) + return aFav ? -1 : 1 + return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase()) + }) } return sorted.map(app => createResultEntry(app)) } @@ -92,7 +104,18 @@ Item { "limit": 20 }) - return fuzzyResults.map(result => createResultEntry(result.obj)) + // Sort favorites first within fuzzy results while preserving fuzzysort order otherwise + const favoriteApps = Settings.data.appLauncher.favoriteApps || [] + const fav = [] + const nonFav = [] + for (const r of fuzzyResults) { + const app = r.obj + if (favoriteApps.includes(getAppKey(app))) + fav.push(r) + else + nonFav.push(r) + } + return fav.concat(nonFav).map(result => createResultEntry(result.obj)) } else { // Fallback to simple search const searchTerm = query.toLowerCase() @@ -118,6 +141,7 @@ Item { function createResultEntry(app) { return { + "appId": getAppKey(app), "name": app.name || "Unknown", "description": app.genericName || app.comment || "", "icon": app.icon || "application-x-executable", diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index be75508c..70868d85 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -27,12 +27,12 @@ Loader { } function formatTime() { - return Settings.data.location.use12hourFormat ? Qt.formatDateTime(new Date(), "h:mm A") : Qt.formatDateTime(new Date(), "HH:mm") + return Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "h:mm A") : Qt.locale().toString(new Date(), "HH:mm") } function formatDate() { // For full text date, day is always before month, so we use this format for everybody: Wednesday, September 17. - return Qt.formatDateTime(new Date(), "dddd, MMMM d") + return Qt.locale().toString(new Date(), "dddd, MMMM d") } function scheduleUnloadAfterUnlock() { @@ -147,9 +147,8 @@ Loader { NText { id: timeText text: formatTime() - font.family: Settings.data.ui.fontBillboard // Smaller time display when using longer 12 hour format - font.pointSize: Settings.data.location.use12hourFormat ? Style.fontSizeXXXL * 4 * scaling : Style.fontSizeXXXL * 5 * scaling + pointSize: Settings.data.location.use12hourFormat ? Style.fontSizeXXXL * 4 * scaling : Style.fontSizeXXXL * 5 * scaling font.weight: Style.fontWeightBold font.letterSpacing: -2 * scaling color: Color.mOnSurface @@ -174,8 +173,7 @@ Loader { NText { id: dateText text: formatDate() - font.family: Settings.data.ui.fontBillboard - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling font.weight: Font.Light color: Color.mOnSurface horizontalAlignment: Text.AlignHCenter @@ -385,8 +383,8 @@ Loader { NText { text: I18n.tr("lock-screen.secure-terminal") color: Color.mOnSurface - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeL * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold Layout.fillWidth: true } @@ -396,13 +394,13 @@ Loader { NText { text: keyboardLayout.currentLayout color: Color.mOnSurface - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeM * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold } NIcon { icon: "keyboard" - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: Color.mOnSurface } } @@ -412,15 +410,15 @@ Loader { visible: batteryIndicator.batteryVisible NIcon { icon: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging, batteryIndicator.isReady) - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface rotation: -90 } NText { text: Math.round(batteryIndicator.percent) + "%" color: Color.mOnSurface - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeM * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold } } @@ -443,8 +441,8 @@ Loader { NText { text: Quickshell.env("USER") + "@noctalia:~$" color: Color.mPrimary - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeL * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold } @@ -452,8 +450,8 @@ Loader { id: welcomeText text: "" color: Color.mOnSurface - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeL * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeL * scaling property int currentIndex: 0 property string fullText: I18n.tr("system.welcome-back", { "user": Quickshell.env("USER") @@ -482,16 +480,16 @@ Loader { NText { text: Quickshell.env("USER") + "@noctalia:~$" color: Color.mPrimary - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeL * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold } NText { text: I18n.tr("lock-screen.unlock-command") color: Color.mOnSurface - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeL * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeL * scaling } } @@ -502,8 +500,8 @@ Loader { NText { text: I18n.tr("lock-screen.password") color: Color.mPrimary - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeL * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold } @@ -545,8 +543,8 @@ Loader { id: asterisksText text: "*".repeat(passwordInput.text.length) color: Color.mOnSurface - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeL * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeL * scaling visible: passwordInput.activeFocus && !lockContext.unlockInProgress anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter @@ -612,8 +610,7 @@ Loader { return Color.mError return Color.transparent } - font.family: "DejaVu Sans Mono" - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling Layout.fillWidth: true SequentialAnimation on opacity { @@ -647,8 +644,8 @@ Loader { anchors.centerIn: parent text: lockContext.unlockInProgress ? "EXECUTING" : "EXECUTE" color: executeButtonArea.containsMouse ? Color.mOnPrimary : Color.mPrimary - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeM * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold } @@ -767,8 +764,8 @@ Loader { text: messages[index % messages.length] color: Color.mError - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeXXL * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeXXL * scaling font.weight: Style.fontWeightBold x: baseX @@ -885,7 +882,7 @@ Loader { id: iconPower anchors.centerIn: parent icon: "shutdown" - font.pointSize: Style.fontSizeXXXL * scaling + pointSize: Style.fontSizeXXXL * scaling color: powerButtonArea.containsMouse ? Color.mOnError : Color.mError } @@ -905,7 +902,7 @@ Loader { anchors.margins: Style.marginM * scaling anchors.fill: parent text: I18n.tr("lock-screen.shut-down") - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: Color.mOnSurfaceVariant horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -937,7 +934,7 @@ Loader { id: iconReboot anchors.centerIn: parent icon: "reboot" - font.pointSize: Style.fontSizeXXXL * scaling + pointSize: Style.fontSizeXXXL * scaling color: restartButtonArea.containsMouse ? Color.mOnPrimary : Color.mPrimary } @@ -957,7 +954,7 @@ Loader { anchors.margins: Style.marginM * scaling anchors.fill: parent text: I18n.tr("lock-screen.restart") - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: Color.mOnSurfaceVariant horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -990,7 +987,7 @@ Loader { id: iconSuspend anchors.centerIn: parent icon: "suspend" - font.pointSize: Style.fontSizeXXXL * scaling + pointSize: Style.fontSizeXXXL * scaling color: suspendButtonArea.containsMouse ? Color.mOnSecondary : Color.mSecondary } @@ -1010,7 +1007,7 @@ Loader { anchors.margins: Style.marginM * scaling anchors.fill: parent text: I18n.tr("lock-screen.suspend") - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: Color.mOnSurfaceVariant horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index b7f48abe..3ffe05e2 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -22,7 +22,7 @@ Variants { property ListModel notificationModel: NotificationService.activeList // If no notification display activated in settings, then show them all - active: Settings.isLoaded && modelData && (Settings.data.notifications.monitors.includes(modelData.name) || (Settings.data.notifications.monitors.length === 0)) + active: modelData && (Settings.data.notifications.monitors.includes(modelData.name) || (Settings.data.notifications.monitors.length === 0)) Connections { target: ScalingService @@ -37,11 +37,11 @@ Variants { screen: modelData WlrLayershell.namespace: "noctalia-notifications" - WlrLayershell.layer: (Settings.isLoaded && Settings.data && Settings.data.notifications && Settings.data.notifications.alwaysOnTop) ? WlrLayer.Overlay : WlrLayer.Top + WlrLayershell.layer: (Settings.data.notifications && Settings.data.notifications.alwaysOnTop) ? WlrLayer.Overlay : WlrLayer.Top color: Color.transparent - readonly property string location: (Settings.isLoaded && Settings.data && Settings.data.notifications && Settings.data.notifications.location) ? Settings.data.notifications.location : "top_right" + readonly property string location: (Settings.data.notifications && Settings.data.notifications.location) ? Settings.data.notifications.location : "top_right" readonly property bool isTop: (location === "top") || (location.length >= 3 && location.substring(0, 3) === "top") readonly property bool isBottom: (location === "bottom") || (location.length >= 6 && location.substring(0, 6) === "bottom") readonly property bool isLeft: location.indexOf("_left") >= 0 @@ -162,7 +162,7 @@ Variants { anchors.left: parent.left anchors.right: parent.right height: 2 * scaling - color: "transparent" + color: Color.transparent property real availableWidth: parent.width - (2 * parent.radius) @@ -307,7 +307,7 @@ Variants { NText { text: `${model.appName || I18n.tr("system.unknown-app")} · ${Time.formatRelativeTime(model.timestamp)}` color: Color.mSecondary - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling } Item { @@ -317,7 +317,7 @@ Variants { NText { text: model.summary || I18n.tr("general.no-summary") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightMedium color: Color.mOnSurface textFormat: Text.PlainText @@ -330,7 +330,7 @@ Variants { NText { text: model.body || "" - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: Color.mOnSurface textFormat: Text.PlainText wrapMode: Text.WrapAtWordBoundaryOrAnywhere diff --git a/Modules/Notification/NotificationHistoryPanel.qml b/Modules/Notification/NotificationHistoryPanel.qml index f5bd43d7..3bdcdbd4 100644 --- a/Modules/Notification/NotificationHistoryPanel.qml +++ b/Modules/Notification/NotificationHistoryPanel.qml @@ -32,13 +32,13 @@ NPanel { NIcon { icon: "bell" - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling color: Color.mPrimary } NText { text: I18n.tr("notifications.panel.title") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface Layout.fillWidth: true @@ -88,21 +88,21 @@ NPanel { NIcon { icon: "bell-off" - font.pointSize: 64 * scaling + pointSize: 64 * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: I18n.tr("notifications.panel.no-notifications") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: I18n.tr("notifications.panel.description") - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true @@ -199,13 +199,13 @@ NPanel { NText { text: model.appName || "Unknown App" - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: Color.mSecondary } NText { text: Time.formatRelativeTime(model.timestamp) - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: Color.mSecondary } @@ -217,7 +217,7 @@ NPanel { // Summary NText { text: model.summary || I18n.tr("general.no-summary") - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling font.weight: Font.Medium color: Color.mOnSurface textFormat: Text.PlainText @@ -230,7 +230,7 @@ NPanel { // Body NText { text: model.body || "" - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: Color.mOnSurfaceVariant textFormat: Text.PlainText wrapMode: Text.Wrap diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 059dcb5d..605aea37 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -22,7 +22,7 @@ Variants { property ListModel notificationModel: NotificationService.activeList // If no notification display activated in settings, then show them all - property bool canShowOnThisScreen: Settings.isLoaded && modelData && (Settings.data.osd.monitors.includes(modelData.name) || (Settings.data.osd.monitors.length === 0)) + property bool canShowOnThisScreen: modelData && (Settings.data.osd.monitors.includes(modelData.name) || (Settings.data.osd.monitors.length === 0)) // Loader is only active when actually showing something active: false @@ -104,7 +104,7 @@ Variants { id: panel screen: modelData - readonly property string location: (Settings.isLoaded && Settings.data && Settings.data.osd && Settings.data.osd.location) ? Settings.data.osd.location : "top_right" + readonly property string location: (Settings.data.osd && Settings.data.osd.location) ? Settings.data.osd.location : "top_right" readonly property bool isTop: (location === "top") || (location.length >= 3 && location.substring(0, 3) === "top") readonly property bool isBottom: (location === "bottom") || (location.length >= 6 && location.substring(0, 6) === "bottom") readonly property bool isLeft: (location.indexOf("_left") >= 0) || (location === "left") @@ -243,7 +243,7 @@ Variants { Item { anchors.fill: parent - Row { + RowLayout { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter @@ -253,8 +253,8 @@ Variants { NIcon { icon: root.getIcon() color: root.getIconColor() - font.pointSize: Style.fontSizeXL * root.scaling - anchors.verticalCenter: parent.verticalCenter + pointSize: Style.fontSizeXL * root.scaling + Layout.alignment: Qt.AlignVCenter Behavior on color { ColorAnimation { @@ -266,11 +266,11 @@ Variants { // Progress bar with calculated width Rectangle { - width: Math.round(220 * root.scaling) + Layout.preferredWidth: Math.round(220 * root.scaling) height: panel.barThickness radius: Math.round(panel.barThickness / 2) color: Color.mSurfaceVariant - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter Rectangle { anchors.left: parent.left @@ -299,12 +299,12 @@ Variants { NText { text: root.getDisplayPercentage() color: Color.mOnSurface - font.pointSize: Style.fontSizeS * root.scaling - font.family: Settings.data.ui.fontFixed - anchors.verticalCenter: parent.verticalCenter + pointSize: Style.fontSizeS * root.scaling + family: Settings.data.ui.fontFixed + Layout.alignment: Qt.AlignVCenter horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - width: Math.round(50 * root.scaling) + Layout.preferredWidth: Math.round(50 * root.scaling) } } } @@ -340,8 +340,8 @@ Variants { id: percentText text: root.getDisplayPercentage() color: Color.mOnSurface - font.pointSize: Style.fontSizeS * root.scaling - font.family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeS * root.scaling + family: Settings.data.ui.fontFixed anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter horizontalAlignment: Text.AlignHCenter @@ -389,7 +389,7 @@ Variants { NIcon { icon: root.getIcon() color: root.getIconColor() - font.pointSize: Style.fontSizeXL * root.scaling + pointSize: Style.fontSizeXL * root.scaling Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.bottomMargin: vMargin + Math.round(Style.marginM * root.scaling) + balanceDelta Behavior on color { diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml index 05b92b68..039c2c98 100644 --- a/Modules/SessionMenu/SessionMenu.qml +++ b/Modules/SessionMenu/SessionMenu.qml @@ -41,22 +41,22 @@ NPanel { "action": "suspend", "icon": "suspend", "title": I18n.tr("session-menu.suspend"), - "subtitle": "Put the system to sleep" + "subtitle": I18n.tr("session-menu.suspend-subtitle") }, { "action": "reboot", "icon": "reboot", "title": I18n.tr("session-menu.reboot"), - "subtitle": "Restart the system" + "subtitle": I18n.tr("session-menu.reboot-subtitle") }, { "action": "logout", "icon": "logout", "title": I18n.tr("session-menu.logout"), - "subtitle": I18n.tr("session-menu.end-subtitle") + "subtitle": I18n.tr("session-menu.logout-subtitle") }, { "action": "shutdown", "icon": "shutdown", "title": I18n.tr("session-menu.shutdown"), - "subtitle": "Turn off the system", + "subtitle": I18n.tr("session-menu.shutdown-subtitle"), "isShutdown": true }] @@ -103,10 +103,7 @@ NPanel { } break case "lockAndSuspend": - if (!lockScreen.active) { - lockScreen.active = true - } - CompositorService.suspend() + CompositorService.lockAndSuspend() break case "suspend": CompositorService.suspend() @@ -279,7 +276,7 @@ NPanel { "seconds": Math.ceil(timeRemaining / 1000) }) : I18n.tr("session-menu.title") font.weight: Style.fontWeightBold - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: timerActive ? Color.mPrimary : Color.mOnSurface Layout.alignment: Qt.AlignVCenter verticalAlignment: Text.AlignVCenter @@ -388,7 +385,7 @@ NPanel { return Color.mOnTertiary return Color.mOnSurface } - font.pointSize: Style.fontSizeXXXL * scaling + pointSize: Style.fontSizeXXXL * scaling width: Style.baseWidgetSize * 0.6 * scaling horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -412,7 +409,7 @@ NPanel { NText { text: buttonRoot.title font.weight: Style.fontWeightMedium - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: { if (buttonRoot.pending) return Color.mPrimary @@ -437,7 +434,7 @@ NPanel { } return buttonRoot.subtitle } - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: { if (buttonRoot.pending) return Color.mPrimary @@ -467,7 +464,7 @@ NPanel { NText { anchors.centerIn: parent text: Math.ceil(timeRemaining / 1000) - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightBold color: Color.mOnPrimary } diff --git a/Modules/Settings/Bar/BarSectionEditor.qml b/Modules/Settings/Bar/BarSectionEditor.qml index b1b9e10d..ba587246 100644 --- a/Modules/Settings/Bar/BarSectionEditor.qml +++ b/Modules/Settings/Bar/BarSectionEditor.qml @@ -70,7 +70,7 @@ NBox { NText { text: sectionName + " Section" - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface Layout.alignment: Qt.AlignVCenter @@ -225,7 +225,7 @@ NBox { NText { text: modelData.id - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: root.getWidgetColor(modelData)[1] horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight @@ -312,10 +312,10 @@ NBox { z: 2000 clip: false // Ensure ghost isn't clipped - Text { + NText { id: ghostText anchors.centerIn: parent - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: Color.mOnPrimary } } diff --git a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml index a198b1e3..1c2a1aaf 100644 --- a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml +++ b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml @@ -61,7 +61,7 @@ Popup { text: I18n.tr("system.widget-settings-title", { "widget": widgetSettings.widgetId }) - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mPrimary Layout.fillWidth: true @@ -133,7 +133,8 @@ Popup { "Spacer": "WidgetSettings/SpacerSettings.qml", "SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml", "Volume": "WidgetSettings/VolumeSettings.qml", - "Workspace": "WidgetSettings/WorkspaceSettings.qml" + "Workspace": "WidgetSettings/WorkspaceSettings.qml", + "Taskbar": "WidgetSettings/TaskbarSettings.qml" } const source = widgetSettingsMap[widgetId] diff --git a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml index f28c4bac..cb4f2c5e 100644 --- a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -23,7 +23,6 @@ ColumnLayout { settings.autoHide = valueAutoHide settings.showIcon = valueShowIcon settings.scrollingMode = valueScrollingMode - console.log(JSON.stringify(settings)) return settings } diff --git a/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml b/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml index 6214590a..fe3f1e73 100644 --- a/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml @@ -182,12 +182,12 @@ ColumnLayout { // Horizontal Repeater { Layout.topMargin: Style.marginM * scaling - model: Qt.formatDateTime(now, valueFormatHorizontal.trim()).split("\\n") + model: Qt.locale().toString(now, valueFormatHorizontal.trim()).split("\\n") delegate: NText { visible: text !== "" text: modelData - font.family: valueUseMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault - font.pointSize: Style.fontSizeM * scaling + family: valueUseMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold color: valueUsePrimaryColor ? Color.mPrimary : Color.mOnSurface wrapMode: Text.WordWrap @@ -213,12 +213,12 @@ ColumnLayout { Repeater { Layout.topMargin: Style.marginM * scaling - model: Qt.formatDateTime(now, valueFormatVertical.trim()).split(" ") + model: Qt.locale().toString(now, valueFormatVertical.trim()).split(" ") delegate: NText { visible: text !== "" text: modelData - font.family: valueUseMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault - font.pointSize: Style.fontSizeM * scaling + family: valueUseMonospacedFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold color: valueUsePrimaryColor ? Color.mPrimary : Color.mOnSurface wrapMode: Text.WordWrap diff --git a/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml b/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml index cd44b941..ac4ae04c 100644 --- a/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml @@ -59,7 +59,7 @@ ColumnLayout { NIcon { Layout.alignment: Qt.AlignVCenter icon: valueIcon - font.pointSize: Style.fontSizeXXL * 1.5 * scaling + pointSize: Style.fontSizeXXL * 1.5 * scaling visible: valueIcon !== "" && valueCustomIconPath === "" } } diff --git a/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml b/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml index 9c7aaed0..0520740f 100644 --- a/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml @@ -37,7 +37,7 @@ ColumnLayout { NIcon { Layout.alignment: Qt.AlignVCenter icon: valueIcon - font.pointSize: Style.fontSizeXL * scaling + pointSize: Style.fontSizeXL * scaling visible: valueIcon !== "" } diff --git a/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml b/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml new file mode 100644 index 00000000..98df4c3f --- /dev/null +++ b/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml @@ -0,0 +1,43 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local state + property bool valueOnlyActiveWorkspaces: widgetData.onlyActiveWorkspaces !== undefined ? widgetData.onlyActiveWorkspaces : widgetMetadata.onlyActiveWorkspaces + property bool valueOnlySameOutput: widgetData.onlySameOutput !== undefined ? widgetData.onlySameOutput : widgetMetadata.onlySameOutput + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.onlySameOutput = valueOnlySameOutput + settings.onlyActiveWorkspaces = valueOnlyActiveWorkspaces + console.log(JSON.stringify(settings)) + return settings + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.taskbar.only-same-output.label") + description: I18n.tr("bar.widget-settings.taskbar.only-same-output.description") + checked: root.valueOnlySameOutput + onToggled: checked => root.valueOnlySameOutput = checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.taskbar.only-active-workspaces.label") + description: I18n.tr("bar.widget-settings.taskbar.only-active-workspaces.description") + checked: root.valueOnlyActiveWorkspaces + onToggled: checked => root.valueOnlyActiveWorkspaces = checked + } +} diff --git a/Modules/Settings/SettingsPanel.qml b/Modules/Settings/SettingsPanel.qml index 266f8667..6b7a6619 100644 --- a/Modules/Settings/SettingsPanel.qml +++ b/Modules/Settings/SettingsPanel.qml @@ -396,14 +396,14 @@ NPanel { NIcon { icon: modelData.icon color: tabTextColor - font.pointSize: Style.fontSizeXL * scaling + pointSize: Style.fontSizeXL * scaling } // Tab label NText { text: I18n.tr(modelData.label) color: tabTextColor - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter @@ -456,13 +456,13 @@ NPanel { NIcon { icon: root.tabsModel[currentTabIndex]?.icon color: Color.mPrimary - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling } // Main title NText { text: I18n.tr(root.tabsModel[currentTabIndex]?.label) || "" - font.pointSize: Style.fontSizeXL * scaling + pointSize: Style.fontSizeXL * scaling font.weight: Style.fontWeightBold color: Color.mPrimary Layout.fillWidth: true diff --git a/Modules/Settings/Tabs/AboutTab.qml b/Modules/Settings/Tabs/AboutTab.qml index 1a4610d7..fb2eaaff 100644 --- a/Modules/Settings/Tabs/AboutTab.qml +++ b/Modules/Settings/Tabs/AboutTab.qml @@ -91,14 +91,14 @@ ColumnLayout { NIcon { icon: "download" - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary } NText { id: updateText text: I18n.tr("settings.about.noctalia.download-latest") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary } } @@ -200,7 +200,7 @@ ColumnLayout { NText { text: (modelData.contributions || 0) + " " + ((modelData.contributions || 0) === 1 ? "commit" : "commits") - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: contributorArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface } } diff --git a/Modules/Settings/Tabs/AudioTab.qml b/Modules/Settings/Tabs/AudioTab.qml index d15060a9..e4f70407 100644 --- a/Modules/Settings/Tabs/AudioTab.qml +++ b/Modules/Settings/Tabs/AudioTab.qml @@ -309,7 +309,7 @@ ColumnLayout { NText { text: modelData color: Color.mOnSurface - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling Layout.alignment: Qt.AlignVCenter Layout.leftMargin: Style.marginS * scaling } diff --git a/Modules/Settings/Tabs/BarTab.qml b/Modules/Settings/Tabs/BarTab.qml index 454db34f..31405f5c 100644 --- a/Modules/Settings/Tabs/BarTab.qml +++ b/Modules/Settings/Tabs/BarTab.qml @@ -140,7 +140,7 @@ ColumnLayout { NText { text: I18n.tr("settings.bar.appearance.margins.vertical") - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: Color.mOnSurfaceVariant } @@ -160,7 +160,7 @@ ColumnLayout { NText { text: I18n.tr("settings.bar.appearance.margins.horizontal") - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: Color.mOnSurfaceVariant } diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 57f3d399..5855d755 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -261,7 +261,7 @@ ColumnLayout { var chunks = schemePath.replace(".json", "").split("/") return chunks[chunks.length - 1] } - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold color: getSchemeColor(modelData, "mOnSurface") Layout.fillWidth: true @@ -326,7 +326,7 @@ ColumnLayout { NIcon { icon: "check" - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold color: Color.mOnSecondary anchors.centerIn: parent diff --git a/Modules/Settings/Tabs/DisplayTab.qml b/Modules/Settings/Tabs/DisplayTab.qml index e4431285..6eb9dca2 100644 --- a/Modules/Settings/Tabs/DisplayTab.qml +++ b/Modules/Settings/Tabs/DisplayTab.qml @@ -126,7 +126,7 @@ ColumnLayout { // Reset button container Item { - Layout.preferredWidth: 40 * scaling + Layout.preferredWidth: 30 * scaling Layout.preferredHeight: 30 * scaling NIconButton { @@ -168,13 +168,13 @@ ColumnLayout { // Empty container to match scale row layout Item { - Layout.preferredWidth: 40 * scaling + Layout.preferredWidth: 30 * scaling Layout.preferredHeight: 30 * scaling // Method text positioned in the button area NText { text: brightnessMonitor ? brightnessMonitor.method : "" - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: Color.mOnSurfaceVariant anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter @@ -186,22 +186,18 @@ ColumnLayout { } } } - // Brightness Step Section - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - NSpinBox { - Layout.fillWidth: true - label: I18n.tr("settings.display.monitors.brightness-step.label") - description: I18n.tr("settings.display.monitors.brightness-step.description") - minimum: 1 - maximum: 50 - value: Settings.data.brightness.brightnessStep - stepSize: 1 - suffix: "%" - onValueChanged: Settings.data.brightness.brightnessStep = value - } + // Brightness Step + NSpinBox { + Layout.fillWidth: true + label: I18n.tr("settings.display.monitors.brightness-step.label") + description: I18n.tr("settings.display.monitors.brightness-step.description") + minimum: 1 + maximum: 50 + value: Settings.data.brightness.brightnessStep + stepSize: 1 + suffix: "%" + onValueChanged: Settings.data.brightness.brightnessStep = value } } @@ -258,7 +254,7 @@ ColumnLayout { NText { text: I18n.tr("settings.display.night-light.temperature.night") - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignVCenter } @@ -280,7 +276,7 @@ ColumnLayout { NText { text: I18n.tr("settings.display.night-light.temperature.day") - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignVCenter } @@ -327,7 +323,7 @@ ColumnLayout { NText { text: I18n.tr("settings.display.night-light.manual-schedule.sunrise") - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: Color.mOnSurfaceVariant } @@ -345,7 +341,7 @@ ColumnLayout { NText { text: I18n.tr("settings.display.night-light.manual-schedule.sunset") - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: Color.mOnSurfaceVariant } diff --git a/Modules/Settings/Tabs/DockTab.qml b/Modules/Settings/Tabs/DockTab.qml index c3b1d559..55af3a6a 100644 --- a/Modules/Settings/Tabs/DockTab.qml +++ b/Modules/Settings/Tabs/DockTab.qml @@ -118,6 +118,13 @@ ColumnLayout { } } + NToggle { + label: I18n.tr("settings.dock.monitors.only-same-output.label") + description: I18n.tr("settings.dock.monitors.only-same-output.description") + checked: Settings.data.dock.onlySameOutput + onToggled: checked => Settings.data.dock.onlySameOutput = checked + } + NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginXL * scaling diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index c5f20cc2..405a5cd9 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -187,6 +187,7 @@ ColumnLayout { } } } + NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginXL * scaling @@ -236,17 +237,77 @@ ColumnLayout { } } - NSearchableComboBox { - label: I18n.tr("settings.general.fonts.accent.label") - description: I18n.tr("settings.general.fonts.accent.description") - model: FontService.displayFonts - currentKey: Settings.data.ui.fontBillboard - placeholder: I18n.tr("settings.general.fonts.accent.placeholder") - searchPlaceholder: I18n.tr("settings.general.fonts.accent.search-placeholder") - popupHeight: 320 * scaling - minimumWidth: 300 * scaling - onSelected: function (key) { - Settings.data.ui.fontBillboard = key + ColumnLayout { + NLabel { + label: I18n.tr("settings.general.fonts.default.scale.label") + description: I18n.tr("settings.general.fonts.default.scale.description") + } + + RowLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NValueSlider { + Layout.fillWidth: true + from: 0.75 + to: 1.25 + stepSize: 0.01 + value: Settings.data.ui.fontDefaultScale + onMoved: value => Settings.data.ui.fontDefaultScale = value + text: Math.floor(Settings.data.ui.fontDefaultScale * 100) + "%" + } + + // Reset button container + Item { + Layout.preferredWidth: 30 * scaling + Layout.preferredHeight: 30 * scaling + + NIconButton { + icon: "refresh" + baseSize: Style.baseWidgetSize * 0.9 + tooltipText: I18n.tr("settings.general.fonts.reset-scaling") + onClicked: Settings.data.ui.fontDefaultScale = 1.0 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + } + } + } + } + + ColumnLayout { + NLabel { + label: I18n.tr("settings.general.fonts.monospace.scale.label") + description: I18n.tr("settings.general.fonts.monospace.scale.description") + } + + RowLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NValueSlider { + Layout.fillWidth: true + from: 0.75 + to: 1.25 + stepSize: 0.01 + value: Settings.data.ui.fontFixedScale + onMoved: value => Settings.data.ui.fontFixedScale = value + text: Math.floor(Settings.data.ui.fontFixedScale * 100) + "%" + } + + // Reset button container + Item { + Layout.preferredWidth: 30 * scaling + Layout.preferredHeight: 30 * scaling + + NIconButton { + icon: "refresh" + baseSize: Style.baseWidgetSize * 0.9 + tooltipText: I18n.tr("settings.general.fonts.reset-scaling") + onClicked: Settings.data.ui.fontFixedScale = 1.0 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + } + } } } } diff --git a/Modules/Settings/Tabs/LauncherTab.qml b/Modules/Settings/Tabs/LauncherTab.qml index 884ce3d3..a01c13fb 100644 --- a/Modules/Settings/Tabs/LauncherTab.qml +++ b/Modules/Settings/Tabs/LauncherTab.qml @@ -53,14 +53,14 @@ ColumnLayout { NText { text: I18n.tr("settings.launcher.settings.background-opacity.label") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface } NText { text: I18n.tr("settings.launcher.settings.background-opacity.description") - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling color: Color.mOnSurfaceVariant wrapMode: Text.WordWrap Layout.fillWidth: true diff --git a/Modules/Settings/Tabs/LocationTab.qml b/Modules/Settings/Tabs/LocationTab.qml index eafa3902..769735d2 100644 --- a/Modules/Settings/Tabs/LocationTab.qml +++ b/Modules/Settings/Tabs/LocationTab.qml @@ -46,7 +46,7 @@ ColumnLayout { "name": LocationService.stableName, "coordinates": LocationService.displayCoordinates }) - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: Color.mOnSurfaceVariant verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignRight diff --git a/Modules/Settings/Tabs/WallpaperTab.qml b/Modules/Settings/Tabs/WallpaperTab.qml index 742849b9..ad1ee6b1 100644 --- a/Modules/Settings/Tabs/WallpaperTab.qml +++ b/Modules/Settings/Tabs/WallpaperTab.qml @@ -77,7 +77,7 @@ ColumnLayout { text: (modelData.name || "Unknown") color: Color.mPrimary font.weight: Style.fontWeightBold - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling } NTextInputButton { @@ -327,7 +327,7 @@ ColumnLayout { id: chipLabel anchors.centerIn: parent text: parent.label - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: parent.selected ? Color.mOnPrimary : Color.mOnSurface } } diff --git a/Modules/Toast/SimpleToast.qml b/Modules/Toast/SimpleToast.qml index 2d8bdf70..339e09c6 100644 --- a/Modules/Toast/SimpleToast.qml +++ b/Modules/Toast/SimpleToast.qml @@ -100,7 +100,7 @@ Rectangle { return Color.mOnSurface } } - font.pointSize: Style.fontSizeXXL * 1.5 * scaling + pointSize: Style.fontSizeXXL * 1.5 * scaling Layout.alignment: Qt.AlignVCenter } @@ -114,7 +114,7 @@ Rectangle { Layout.fillWidth: true text: root.message color: Color.mOnSurface - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold wrapMode: Text.WordWrap visible: text.length > 0 @@ -124,7 +124,7 @@ Rectangle { Layout.fillWidth: true text: root.description color: Color.mOnSurface - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling wrapMode: Text.WordWrap visible: text.length > 0 } diff --git a/Modules/Toast/ToastOverlay.qml b/Modules/Toast/ToastOverlay.qml index 78cb670a..11cb5037 100644 --- a/Modules/Toast/ToastOverlay.qml +++ b/Modules/Toast/ToastOverlay.qml @@ -16,6 +16,6 @@ Variants { scaling: ScalingService.getScreenScale(modelData) // Only activate on enabled screens - active: Settings.isLoaded && modelData && (Settings.data.notifications.monitors.includes(modelData.name) || Settings.data.notifications.monitors.length === 0) + active: modelData && (Settings.data.notifications.monitors.includes(modelData.name) || Settings.data.notifications.monitors.length === 0) } } diff --git a/Modules/Toast/ToastScreen.qml b/Modules/Toast/ToastScreen.qml index 6a635aea..ca35c68c 100644 --- a/Modules/Toast/ToastScreen.qml +++ b/Modules/Toast/ToastScreen.qml @@ -128,7 +128,7 @@ Item { screen: root.screen - readonly property string location: (Settings.isLoaded && Settings.data && Settings.data.notifications && Settings.data.notifications.location) ? Settings.data.notifications.location : "top_right" + readonly property string location: (Settings.data.notifications && Settings.data.notifications.location) ? Settings.data.notifications.location : "top_right" readonly property bool isTop: (location === "top") || (location.length >= 3 && location.substring(0, 3) === "top") readonly property bool isBottom: (location === "bottom") || (location.length >= 6 && location.substring(0, 6) === "bottom") readonly property bool isLeft: location.indexOf("_left") >= 0 diff --git a/Modules/Tooltip/Tooltip.qml b/Modules/Tooltip/Tooltip.qml index 6acb7e61..ded2b479 100644 --- a/Modules/Tooltip/Tooltip.qml +++ b/Modules/Tooltip/Tooltip.qml @@ -375,8 +375,8 @@ PopupWindow { anchors.fill: parent color: Color.mSurface border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - radius: Style.radiusS * scaling + border.width: Math.max(1, Style.borderS * root.scaling) + radius: Style.radiusS * root.scaling // Only show content when we have text visible: root.text !== "" @@ -386,12 +386,12 @@ PopupWindow { anchors.centerIn: parent anchors.margins: root.padding * root.scaling text: root.text - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * root.scaling color: Color.mOnSurfaceVariant horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap - width: root.maxWidth + width: root.maxWidth * root.scaling - (root.padding * 2 * root.scaling) } } } diff --git a/Modules/Wallpaper/WallpaperPanel.qml b/Modules/Wallpaper/WallpaperPanel.qml index 6266aa2b..46c32417 100644 --- a/Modules/Wallpaper/WallpaperPanel.qml +++ b/Modules/Wallpaper/WallpaperPanel.qml @@ -4,6 +4,7 @@ import QtQuick.Controls import Quickshell import Quickshell.Wayland import qs.Commons +import qs.Modules.Settings import qs.Services import qs.Widgets import "../../Helpers/FuzzySort.js" as FuzzySort @@ -51,18 +52,29 @@ NPanel { NIcon { icon: "settings-wallpaper-selector" - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling color: Color.mPrimary } NText { text: I18n.tr("wallpaper.panel.title") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface Layout.fillWidth: true } + NIconButton { + icon: "settings" + tooltipText: I18n.tr("settings.wallpaper.settings.section.label") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: { + var settingsPanel = PanelService.getPanel("settingsPanel") + settingsPanel.requestedTab = SettingsPanel.Tab.Wallpaper + settingsPanel.open() + } + } + NIconButton { icon: "refresh" tooltipText: I18n.tr("tooltips.refresh-wallpaper-list") @@ -122,11 +134,11 @@ NPanel { } } - contentItem: Text { + contentItem: NText { text: parent.text - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: screenTabBar.currentIndex === index ? Style.fontWeightBold : Style.fontWeightRegular - font.family: Settings.data.ui.fontDefault + family: Settings.data.ui.fontDefault color: screenTabBar.currentIndex === index ? Color.mOnSecondary : Color.mOnSurfaceVariant horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -177,7 +189,7 @@ NPanel { NText { text: I18n.tr("wallpaper.panel.search") color: Color.mOnSurface - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling Layout.preferredWidth: implicitWidth } @@ -355,7 +367,7 @@ NPanel { NIcon { icon: "check" - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold color: Color.mOnSecondary anchors.centerIn: parent @@ -393,7 +405,7 @@ NPanel { text: filename color: Color.mOnSurfaceVariant opacity: 0.5 - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling Layout.fillWidth: true Layout.leftMargin: Style.marginS * scaling Layout.rightMargin: Style.marginS * scaling @@ -431,7 +443,7 @@ NPanel { } NIcon { icon: "folder-open" - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling color: Color.mOnSurface Layout.alignment: Qt.AlignHCenter } diff --git a/Services/BarService.qml b/Services/BarService.qml index 63e607cc..9a28b550 100644 --- a/Services/BarService.qml +++ b/Services/BarService.qml @@ -9,6 +9,13 @@ Singleton { property bool hasAudioVisualizer: false property bool isVisible: true + property var readyBars: ({}) + + // Registry to store actual widget instances + // Key format: "screenName|section|widgetId|index" + property var widgetInstances: ({}) + + signal barReadyChanged(string screenName) // Simple timer that run once when the widget structure has changed // and determine if any MediaMini widget has the visualizer on @@ -28,14 +35,24 @@ Singleton { } } - // Registry to store actual widget instances - // Key format: "screenName|section|widgetId|index" - property var widgetInstances: ({}) - Component.onCompleted: { Logger.log("BarService", "Service started") } + // Function for the Bar to call when it's ready + function registerBar(screenName) { + if (!readyBars[screenName]) { + readyBars[screenName] = true + Logger.log("BarService", "Bar is ready on screen:", screenName) + barReadyChanged(screenName) + } + } + + // Function for the Dock to check if the bar is ready + function isBarReady(screenName) { + return readyBars[screenName] || false + } + // Register a widget instance function registerWidget(screenName, section, widgetId, index, instance) { const key = [screenName, section, widgetId, index].join("|") diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index bab1a4b4..b1d528ea 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -41,7 +41,7 @@ Singleton { "ActiveWindow": { "allowUserSettings": true, "showIcon": true, - "autoHide": true, + "autoHide": false, "scrollingMode": "hover" }, "Battery": { @@ -81,7 +81,7 @@ Singleton { }, "MediaMini": { "allowUserSettings": true, - "autoHide": true, + "autoHide": false, "scrollingMode": "hover", "showAlbumArt": false, "showVisualizer": false, @@ -109,6 +109,11 @@ Singleton { "showNetworkStats": false, "showDiskUsage": false }, + "Taskbar": { + "allowUserSettings": true, + "onlySameOutput": true, + "onlyActiveWorkspaces": true + }, "Workspace": { "allowUserSettings": true, "labelMode": "index", diff --git a/Services/ClipboardService.qml b/Services/ClipboardService.qml index 65287ea4..bb224a28 100644 --- a/Services/ClipboardService.qml +++ b/Services/ClipboardService.qml @@ -10,7 +10,7 @@ Singleton { id: root // Public API - property bool active: Settings.isLoaded && Settings.data.appLauncher.enableClipboardHistory && cliphistAvailable + property bool active: Settings.data.appLauncher.enableClipboardHistory && cliphistAvailable property bool loading: false property var items: [] // [{id, preview, mime, isImage}] diff --git a/Services/CompositorService.qml b/Services/CompositorService.qml index d2064350..4d30421c 100644 --- a/Services/CompositorService.qml +++ b/Services/CompositorService.qml @@ -14,7 +14,7 @@ Singleton { // Generic workspace and window data property ListModel workspaces: ListModel {} - property var windows: [] + property ListModel windows: ListModel {} property int focusedWindowIndex: -1 // Generic events @@ -82,10 +82,17 @@ Singleton { workspaceChanged() }) - backend.activeWindowChanged.connect(activeWindowChanged) + backend.activeWindowChanged.connect(() => { + // Sync active window when it changes + // TODO: Avoid re-syncing all windows + syncWindows() + // Forward the signal + activeWindowChanged() + }) + backend.windowListChanged.connect(() => { // Sync windows when they change - windows = backend.windows + syncWindows() // Forward the signal windowListChanged() }) @@ -97,7 +104,7 @@ Singleton { // Initial sync syncWorkspaces() - windows = backend.windows + syncWindows() focusedWindowIndex = backend.focusedWindowIndex } @@ -111,10 +118,28 @@ Singleton { workspacesChanged() } - // Get window title for focused window + function syncWindows() { + windows.clear() + const ws = backend.windows + for (var i = 0; i < ws.length; i++) { + windows.append(ws[i]) + } + // Emit signal to notify listeners that workspace list has been updated + windowListChanged() + } + + // Get focused window + function getFocusedWindow() { + if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) { + return windows.get(focusedWindowIndex) + } + return null + } + + // Get focused window title function getFocusedWindowTitle() { - if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.length) { - return windows[focusedWindowIndex].title || "" + if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) { + return windows.get(focusedWindowIndex).title || "" } return "" } @@ -139,12 +164,34 @@ Singleton { return null } - // Get focused window - function getFocusedWindow() { - if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.length) { - return windows[focusedWindowIndex] + // Get active workspaces + function getActiveWorkspaces() { + const activeWorkspaces = [] + for (var i = 0; i < workspaces.count; i++) { + const ws = workspaces.get(i) + if (ws.isActive) { + activeWorkspaces.push(ws) + } + } + return activeWorkspaces + } + + // Set focused window + function focusWindow(windowId) { + if (backend && backend.focusWindow) { + backend.focusWindow(windowId) + } else { + Logger.warn("Compositor", "No backend available for window focus") + } + } + + // Close window + function closeWindow(windowId) { + if (backend && backend.closeWindow) { + backend.closeWindow(windowId) + } else { + Logger.warn("Compositor", "No backend available for window closing") } - return null } // Session management @@ -167,4 +214,16 @@ Singleton { function suspend() { Quickshell.execDetached(["systemctl", "suspend"]) } + + function lockAndSuspend() { + try { + if (PanelService && PanelService.lockScreen && !PanelService.lockScreen.active) { + PanelService.lockScreen.active = true + } + } catch (e) { + Logger.warn("Compositor", "Failed to activate lock screen before suspend: " + e) + } + // Queue suspend to the next event loop cycle to allow lock UI to render + Qt.callLater(suspend) + } } diff --git a/Services/HyprlandService.qml b/Services/HyprlandService.qml index 1b36cf4b..1cd43f1e 100644 --- a/Services/HyprlandService.qml +++ b/Services/HyprlandService.qml @@ -278,6 +278,22 @@ Item { } } + function focusWindow(windowId) { + try { + Hyprland.dispatch(`focuswindow ${windowId}`) + } catch (e) { + Logger.error("HyprlandService", "Failed to switch window:", e) + } + } + + function closeWindow(windowId) { + try { + Hyprland.dispatch(`killwindow ${windowId}`) + } catch (e) { + Logger.error("HyprlandService", "Failed to close window:", e) + } + } + function logout() { try { Quickshell.execDetached(["hyprctl", "dispatch", "exit"]) diff --git a/Services/MatugenService.qml b/Services/MatugenService.qml index e3410d57..694bb1e7 100644 --- a/Services/MatugenService.qml +++ b/Services/MatugenService.qml @@ -10,7 +10,7 @@ import qs.Services Singleton { id: root - property string dynamicConfigPath: Settings.isLoaded ? Settings.cacheDir + "matugen.dynamic.toml" : "" + property string dynamicConfigPath: Settings.cacheDir + "matugen.dynamic.toml" // External state management Connections { @@ -47,11 +47,6 @@ Singleton { // Generate colors using current wallpaper and settings function generateFromWallpaper() { - if (!Settings.isLoaded) { - Logger.log("Matugen", "Settings not loaded yet, skipping wallpaper color generation") - return - } - Logger.log("Matugen", "Generating from wallpaper on screen:", Screen.name) var wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''") if (wp === "") { diff --git a/Services/NiriService.qml b/Services/NiriService.qml index b65d9d19..ef525bb8 100644 --- a/Services/NiriService.qml +++ b/Services/NiriService.qml @@ -6,6 +6,9 @@ import qs.Commons Item { id: root + // Sorts floating windows after scrolling ones + property int floatingWindowPosition: Number.MAX_SAFE_INTEGER + // Properties that match the facade interface property ListModel workspaces: ListModel {} property var windows: [] @@ -91,32 +94,7 @@ Item { onRead: function (line) { try { const windowsData = JSON.parse(line) - const windowsList = [] - - for (const win of windowsData) { - windowsList.push({ - "id": win.id, - "title": win.title || "", - "appId": win.app_id || "", - "workspaceId": win.workspace_id || null, - "isFocused": win.is_focused === true - }) - } - - windowsList.sort((a, b) => a.id - b.id) - windows = windowsList - windowListChanged() - - // Update focused window index - focusedWindowIndex = -1 - for (var i = 0; i < windowsList.length; i++) { - if (windowsList[i].isFocused) { - focusedWindowIndex = i - break - } - } - - activeWindowChanged() + recollectWindows(windowsData) } catch (e) { Logger.error("NiriService", "Failed to parse windows:", e, line) } @@ -147,8 +125,9 @@ Item { updateWorkspaces() } else if (event.WindowFocusChanged) { handleWindowFocusChanged(event.WindowFocusChanged) + } else if (event.WindowLayoutsChanged) { + handleWindowLayoutsChanged(event.WindowLayoutsChanged) } - // Removed OverviewOpenedOrClosed handling } catch (e) { Logger.error("NiriService", "Error parsing event stream:", e, data) } @@ -156,19 +135,81 @@ Item { } } + // Utility functions + function getWindowPosition(layout) { + if (layout.pos_in_scrolling_layout) { + return { + "x": layout.pos_in_scrolling_layout[0], + "y": layout.pos_in_scrolling_layout[1] + } + } else { + return { + "x": floatingWindowPosition, + "y": floatingWindowPosition + } + } + } + + function getWindowOutput(win) { + for (var i = 0; i < workspaces.count; i++) { + if (workspaces.get(i).id === win.workspace_id) { + return workspaces.get(i).output + } + } + return null + } + + function getWindowData(win) { + return { + "id": win.id, + "title": win.title || "", + "appId": win.app_id || "", + "workspaceId": win.workspace_id || -1, + "isFocused": win.is_focused === true, + "output": getWindowOutput(win) || "", + "position": getWindowPosition(win.layout) + } + } + + // Sort windows + // 1. by workspace ID + // 2. by position X + // 3. by position Y + function compareWindows(a, b) { + if (a.workspaceId !== b.workspaceId) { + return a.workspaceId - b.workspaceId + } + if (a.position.x !== b.position.x) { + return a.position.x - b.position.x + } + return a.position.y - b.position.y + } + + function recollectWindows(windowsData) { + const windowsList = [] + for (const win of windowsData) { + windowsList.push(getWindowData(win)) + } + windowsList.sort(compareWindows) + windows = windowsList + windowListChanged() + + focusedWindowIndex = -1 + for (var i = 0; i < windowsList.length; i++) { + if (windowsList[i].isFocused) { + focusedWindowIndex = i + break + } + } + activeWindowChanged() + } + // Event handlers function handleWindowOpenedOrChanged(eventData) { try { const windowData = eventData.window const existingIndex = windows.findIndex(w => w.id === windowData.id) - - const newWindow = { - "id": windowData.id, - "title": windowData.title || "", - "appId": windowData.app_id || "", - "workspaceId": windowData.workspace_id || null, - "isFocused": windowData.is_focused === true - } + const newWindow = getWindowData(windowData) if (existingIndex >= 0) { // Update existing window @@ -176,8 +217,8 @@ Item { } else { // Add new window windows.push(newWindow) - windows.sort((a, b) => a.id - b.id) } + windows.sort(compareWindows) // Update focused window index if this window is focused if (newWindow.isFocused) { @@ -186,6 +227,9 @@ Item { // Only emit activeWindowChanged if the focused window actually changed if (oldFocusedIndex !== focusedWindowIndex) { + if (oldFocusedIndex >= 0 && oldFocusedIndex < windows.length) { + windows[oldFocusedIndex].isFocused = false + } activeWindowChanged() } } @@ -223,32 +267,7 @@ Item { function handleWindowsChanged(eventData) { try { const windowsData = eventData.windows - const windowsList = [] - - for (const win of windowsData) { - windowsList.push({ - "id": win.id, - "title": win.title || "", - "appId": win.app_id || "", - "workspaceId": win.workspace_id || null, - "isFocused": win.is_focused === true - }) - } - - windowsList.sort((a, b) => a.id - b.id) - windows = windowsList - windowListChanged() - - // Update focused window index - focusedWindowIndex = -1 - for (var i = 0; i < windowsList.length; i++) { - if (windowsList[i].isFocused) { - focusedWindowIndex = i - break - } - } - - activeWindowChanged() + recollectWindows(windowsData) } catch (e) { Logger.error("NiriService", "Error handling WindowsChanged:", e) } @@ -258,8 +277,17 @@ Item { try { const focusedId = eventData.id + if (windows[focusedWindowIndex]) { + windows[focusedWindowIndex].isFocused = false + } + if (focusedId) { const newIndex = windows.findIndex(w => w.id === focusedId) + + if (newIndex >= 0 && newIndex < windows.length) { + windows[newIndex].isFocused = true + } + focusedWindowIndex = newIndex >= 0 ? newIndex : -1 } else { focusedWindowIndex = -1 @@ -271,6 +299,25 @@ Item { } } + function handleWindowLayoutsChanged(eventData) { + try { + for (const change of eventData.changes) { + const windowId = change[0] + const layout = change[1] + const window = windows.find(w => w.id === windowId) + if (window) { + window.position = getWindowPosition(layout) + } + } + + windows.sort(compareWindows) + + windowListChanged() + } catch (e) { + Logger.error("NiriService", "Error handling WindowLayoutChanged:", e) + } + } + // Public functions function switchToWorkspace(workspaceId) { try { @@ -280,6 +327,22 @@ Item { } } + function focusWindow(windowId) { + try { + Quickshell.execDetached(["niri", "msg", "action", "focus-window", "--id", windowId.toString()]) + } catch (e) { + Logger.error("NiriService", "Failed to switch window:", e) + } + } + + function closeWindow(windowId) { + try { + Quickshell.execDetached(["niri", "msg", "action", "close-window", "--id", windowId.toString()]) + } catch (e) { + Logger.error("NiriService", "Failed to close window:", e) + } + } + function logout() { try { Quickshell.execDetached(["niri", "msg", "action", "quit", "--skip-confirmation"]) diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml index 7eaf6f6b..aab8b7de 100644 --- a/Services/NotificationService.qml +++ b/Services/NotificationService.qml @@ -28,7 +28,7 @@ Singleton { PanelWindow { implicitHeight: 1 implicitWidth: 1 - color: "transparent" + color: Color.transparent mask: Region {} Image { diff --git a/Services/ScalingService.qml b/Services/ScalingService.qml index 6b204f77..0b8ef479 100644 --- a/Services/ScalingService.qml +++ b/Services/ScalingService.qml @@ -15,19 +15,13 @@ Singleton { Component.onCompleted: { Logger.log("Scaling", "Service started") - } - - Connections { - target: Settings - function onSettingsLoaded() { - // Initialize cache from Settings once they are loaded on startup - var monitors = Settings.data.ui.monitorsScaling || [] - for (var i = 0; i < monitors.length; i++) { - if (monitors[i].name && monitors[i].scale !== undefined) { - currentScales[monitors[i].name] = monitors[i].scale - root.scaleChanged(monitors[i].name, monitors[i].scale) - Logger.log("Scaling", "Caching scaling for", monitors[i].name, ":", monitors[i].scale) - } + // Initialize cache from Settings once they are loaded on startup + var monitors = Settings.data.ui.monitorsScaling || [] + for (var i = 0; i < monitors.length; i++) { + if (monitors[i].name && monitors[i].scale !== undefined) { + currentScales[monitors[i].name] = monitors[i].scale + root.scaleChanged(monitors[i].name, monitors[i].scale) + Logger.log("Scaling", "Caching scaling for", monitors[i].name, ":", monitors[i].scale) } } } diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 37181a64..46869ff2 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -26,6 +26,8 @@ Singleton { // Cache for current wallpapers - can be updated directly since we use signals for notifications property var currentWallpapers: ({}) + property bool isInitialized: false + // Signals for reactive UI updates signal wallpaperChanged(string screenName, string path) // Emitted when a wallpaper changes @@ -75,18 +77,18 @@ Singleton { function init() { Logger.log("Wallpaper", "Service started") - // Rebuild cache from persisted settings - var monitors = Settings.data.wallpaper.monitors || [] + translateModels() + + // Rebuild cache from settings currentWallpapers = ({}) + var monitors = Settings.data.wallpaper.monitors || [] for (var i = 0; i < monitors.length; i++) { if (monitors[i].name && monitors[i].wallpaper) { currentWallpapers[monitors[i].name] = monitors[i].wallpaper - // Notify listeners so Background updates immediately after settings load - root.wallpaperChanged(monitors[i].name, monitors[i].wallpaper) } } - translateModels() + isInitialized = true } // ------------------------------------------------- diff --git a/Shaders/frag/wp_disc.frag b/Shaders/frag/wp_disc.frag index 0fc21229..ba0192c3 100644 --- a/Shaders/frag/wp_disc.frag +++ b/Shaders/frag/wp_disc.frag @@ -110,8 +110,11 @@ void main() { // Add extra range for smoothness to ensure complete coverage // Adjust smoothness for aspect ratio to maintain consistent visual appearance float adjustedSmoothness = mappedSmoothness * max(1.0, ubuf.aspectRatio); - float radius = ubuf.progress * (maxDist + adjustedSmoothness); + // Start the radius from -adjustedSmoothness, this ensures the disc is completely hidden when progress=0 + float totalDistance = maxDist + 2.0 * adjustedSmoothness; + float radius = -adjustedSmoothness + ubuf.progress * totalDistance; + // Use smoothstep for a smooth edge transition float factor = smoothstep(radius - adjustedSmoothness, radius + adjustedSmoothness, dist); diff --git a/Shaders/qsb/wp_disc.frag.qsb b/Shaders/qsb/wp_disc.frag.qsb index 8e1705e7..5dddf017 100644 Binary files a/Shaders/qsb/wp_disc.frag.qsb and b/Shaders/qsb/wp_disc.frag.qsb differ diff --git a/Widgets/NButton.qml b/Widgets/NButton.qml index df48655e..93390ce9 100644 --- a/Widgets/NButton.qml +++ b/Widgets/NButton.qml @@ -78,9 +78,8 @@ Rectangle { NIcon { Layout.alignment: Qt.AlignVCenter visible: root.icon !== "" - icon: root.icon - font.pointSize: root.iconSize + pointSize: root.iconSize color: { if (!root.enabled) return Color.mOnSurfaceVariant @@ -105,7 +104,7 @@ Rectangle { Layout.alignment: Qt.AlignVCenter visible: root.text !== "" text: root.text - font.pointSize: root.fontSize + pointSize: root.fontSize font.weight: root.fontWeight color: { if (!root.enabled) diff --git a/Widgets/NCheckbox.qml b/Widgets/NCheckbox.qml index b9c2ae64..4ea76318 100644 --- a/Widgets/NCheckbox.qml +++ b/Widgets/NCheckbox.qml @@ -60,7 +60,7 @@ RowLayout { anchors.horizontalCenterOffset: -1 * scaling icon: "check" color: root.activeOnColor - font.pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5) * scaling + pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5) * scaling font.weight: Style.fontWeightBold } diff --git a/Widgets/NCircleStat.qml b/Widgets/NCircleStat.qml index 16e6e0a9..67668184 100644 --- a/Widgets/NCircleStat.qml +++ b/Widgets/NCircleStat.qml @@ -79,7 +79,7 @@ Rectangle { id: valueLabel anchors.centerIn: parent text: `${root.value}${root.suffix}` - font.pointSize: Style.fontSizeM * scaling * contentScale + pointSize: Style.fontSizeM * scaling * contentScale font.weight: Style.fontWeightBold color: Color.mOnSurface horizontalAlignment: Text.AlignHCenter @@ -102,7 +102,7 @@ Rectangle { anchors.centerIn: parent icon: root.icon color: Color.mOnPrimary - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } diff --git a/Widgets/NCollapsible.qml b/Widgets/NCollapsible.qml index 1403cf39..2efd3f01 100644 --- a/Widgets/NCollapsible.qml +++ b/Widgets/NCollapsible.qml @@ -82,7 +82,7 @@ ColumnLayout { NIcon { id: chevronIcon icon: "chevron-right" - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: root.expanded ? Color.mOnSecondary : Color.mOnSurfaceVariant Layout.alignment: Qt.AlignVCenter @@ -109,7 +109,7 @@ ColumnLayout { NText { text: root.label - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightSemiBold color: root.expanded ? Color.mOnSecondary : Color.mOnSurface Layout.fillWidth: true @@ -124,7 +124,7 @@ ColumnLayout { NText { text: root.description - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightRegular color: root.expanded ? Color.mOnSecondary : Color.mOnSurfaceVariant Layout.fillWidth: true diff --git a/Widgets/NColorPicker.qml b/Widgets/NColorPicker.qml index 8eee16ba..642d74a9 100644 --- a/Widgets/NColorPicker.qml +++ b/Widgets/NColorPicker.qml @@ -58,7 +58,7 @@ Rectangle { NText { text: root.selectedColor.toString().toUpperCase() - font.family: Settings.data.ui.fontFixed + family: Settings.data.ui.fontFixed Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter } diff --git a/Widgets/NColorPickerDialog.qml b/Widgets/NColorPickerDialog.qml index 7c823444..3165922e 100644 --- a/Widgets/NColorPickerDialog.qml +++ b/Widgets/NColorPickerDialog.qml @@ -138,13 +138,13 @@ Popup { NIcon { icon: "color-picker" - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling color: Color.mPrimary } NText { text: I18n.tr("widgets.color-picker.title") - font.pointSize: Style.fontSizeXL * scaling + pointSize: Style.fontSizeXL * scaling font.weight: Style.fontWeightBold color: Color.mPrimary } @@ -179,8 +179,8 @@ Popup { NText { text: root.selectedColor.toString().toUpperCase() - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeL * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeL * scaling font.weight: Font.Bold color: root.selectedColor.r + root.selectedColor.g + root.selectedColor.b > 1.5 ? "#000000" : "#FFFFFF" Layout.alignment: Qt.AlignHCenter @@ -188,8 +188,8 @@ Popup { NText { text: "RGB(" + Math.round(root.selectedColor.r * 255) + ", " + Math.round(root.selectedColor.g * 255) + ", " + Math.round(root.selectedColor.b * 255) + ")" - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeM * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeM * scaling color: root.selectedColor.r + root.selectedColor.g + root.selectedColor.b > 1.5 ? "#000000" : "#FFFFFF" Layout.alignment: Qt.AlignHCenter } diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index 6035b620..a7a054b5 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -89,7 +89,7 @@ RowLayout { contentItem: NText { leftPadding: Style.marginL * scaling rightPadding: combo.indicator.width + Style.marginL * scaling - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling verticalAlignment: Text.AlignVCenter elide: Text.ElideRight color: (combo.currentIndex >= 0 && combo.currentIndex < itemCount()) ? Color.mOnSurface : Color.mOnSurfaceVariant @@ -100,7 +100,7 @@ RowLayout { x: combo.width - width - Style.marginM * scaling y: combo.topPadding + (combo.availableHeight - height) / 2 icon: "caret-down" - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling } popup: Popup { @@ -159,7 +159,7 @@ RowLayout { var item = root.getItem(index) return item && item.name ? item.name : "" } - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: highlighted ? Color.mOnTertiary : Color.mOnSurface verticalAlignment: Text.AlignVCenter elide: Text.ElideRight diff --git a/Widgets/NContextMenu.qml b/Widgets/NContextMenu.qml index a1b7562c..55d0f6ae 100644 --- a/Widgets/NContextMenu.qml +++ b/Widgets/NContextMenu.qml @@ -62,7 +62,7 @@ Popup { NIcon { visible: modelData.icon !== undefined icon: modelData.icon || "" - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: menuItem.hovered && menuItem.enabled ? Color.mOnTertiary : Color.mOnSurface Layout.leftMargin: root.itemPadding @@ -75,7 +75,7 @@ Popup { NText { text: modelData.label || modelData.text || "" - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: menuItem.hovered && menuItem.enabled ? Color.mOnTertiary : Color.mOnSurface verticalAlignment: Text.AlignVCenter Layout.fillWidth: true diff --git a/Widgets/NDateTimeTokens.qml b/Widgets/NDateTimeTokens.qml index 523e74f0..1ad1ec94 100644 --- a/Widgets/NDateTimeTokens.qml +++ b/Widgets/NDateTimeTokens.qml @@ -238,7 +238,7 @@ Rectangle { anchors.centerIn: parent text: modelData.category color: getCategoryColor(modelData.category)[1] - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling } } @@ -261,7 +261,7 @@ Rectangle { anchors.centerIn: parent text: modelData.token color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurface - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightBold Behavior on color { @@ -278,7 +278,7 @@ Rectangle { Layout.alignment: Qt.AlignVCenter // Added this line text: modelData.description color: tokenMouseArea.containsMouse ? Color.mOnSurface : Color.mOnSurfaceVariant - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling wrapMode: Text.WordWrap Behavior on color { @@ -312,9 +312,9 @@ Rectangle { NText { anchors.centerIn: parent - text: Qt.formatDateTime(root.sampleDate, modelData.token) + text: Qt.locale().toString(root.sampleDate, modelData.token) color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurfaceVariant - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling Behavior on color { ColorAnimation { diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index d2e2f766..24922ec7 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -191,11 +191,11 @@ Popup { NIcon { icon: "filepicker-folder" color: Color.mPrimary - font.pointSize: Style.fontSizeXXL * scaling + pointSize: Style.fontSizeXXL * scaling } NText { text: root.title - font.pointSize: Style.fontSizeXL * scaling + pointSize: Style.fontSizeXL * scaling font.weight: Style.fontWeightBold color: Color.mPrimary Layout.fillWidth: true @@ -357,7 +357,7 @@ Popup { NIcon { icon: "filepicker-search" color: Color.mOnSurfaceVariant - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling } NTextInput { id: searchInput @@ -588,7 +588,7 @@ Popup { visible: thumbnail.status === Image.Loading NIcon { icon: "filepicker-photo" - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: Color.mOnSurfaceVariant anchors.centerIn: parent } @@ -597,7 +597,7 @@ Popup { NIcon { icon: model.fileIsDir ? "filepicker-folder" : root.getFileIcon(model.fileName) - font.pointSize: Style.fontSizeXXL * 2 * scaling + pointSize: Style.fontSizeXXL * 2 * scaling color: { if (isSelected) return Color.mSecondary @@ -623,7 +623,7 @@ Popup { visible: isSelected NIcon { icon: "filepicker-check" - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightBold color: Color.mOnSecondary anchors.centerIn: parent @@ -641,7 +641,7 @@ Popup { else return Color.mOnSurface } - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling font.weight: isSelected ? Style.fontWeightBold : Style.fontWeightRegular Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter @@ -728,14 +728,14 @@ Popup { NIcon { icon: model.fileIsDir ? "filepicker-folder" : root.getFileIcon(model.fileName) - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling color: model.fileIsDir ? (filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mPrimary) : Color.mOnSurfaceVariant } NText { text: model.fileName color: filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mOnSurface - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling font.weight: filePickerPanel.currentSelection.includes(model.filePath) ? Style.fontWeightBold : Style.fontWeightRegular Layout.fillWidth: true elide: Text.ElideRight @@ -744,7 +744,7 @@ Popup { NText { text: model.fileIsDir ? "" : root.formatFileSize(model.fileSize) color: filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mOnSurfaceVariant - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling visible: !model.fileIsDir Layout.preferredWidth: implicitWidth } @@ -810,7 +810,7 @@ Popup { } } color: filePickerPanel.searchText.length > 0 ? Color.mPrimary : Color.mOnSurfaceVariant - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling Layout.fillWidth: true } diff --git a/Widgets/NHeader.qml b/Widgets/NHeader.qml index f8bd2beb..dfbcfd54 100644 --- a/Widgets/NHeader.qml +++ b/Widgets/NHeader.qml @@ -14,7 +14,7 @@ ColumnLayout { NText { text: root.label - font.pointSize: Style.fontSizeXL * scaling + pointSize: Style.fontSizeXL * scaling font.weight: Style.fontWeightBold color: Color.mSecondary visible: root.label !== "" @@ -22,7 +22,7 @@ ColumnLayout { NText { text: root.description - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: Color.mOnSurfaceVariant wrapMode: Text.WordWrap Layout.fillWidth: true diff --git a/Widgets/NIcon.qml b/Widgets/NIcon.qml index 9f66c7c1..adca75f6 100644 --- a/Widgets/NIcon.qml +++ b/Widgets/NIcon.qml @@ -7,6 +7,7 @@ Text { id: root property string icon: Icons.defaultIcon + property real pointSize: Style.fontSizeL * scaling visible: (icon !== undefined) && (icon !== "") text: { @@ -21,7 +22,7 @@ Text { return Icons.get(icon) } font.family: Icons.fontFamily - font.pointSize: Style.fontSizeL * scaling + font.pointSize: root.pointSize color: Color.mOnSurface verticalAlignment: Text.AlignVCenter } diff --git a/Widgets/NIconButton.qml b/Widgets/NIconButton.qml index 290fb00b..d533153c 100644 --- a/Widgets/NIconButton.qml +++ b/Widgets/NIconButton.qml @@ -48,7 +48,7 @@ Rectangle { NIcon { icon: root.icon - font.pointSize: Math.max(1, root.compact ? root.width * 0.65 : root.width * 0.48) + pointSize: Math.max(1, root.compact ? root.width * 0.65 : root.width * 0.48) color: root.enabled && root.hovering ? colorFgHover : colorFg // Center horizontally x: (root.width - width) / 2 diff --git a/Widgets/NIconPicker.qml b/Widgets/NIconPicker.qml index 86ea50a4..813aa468 100644 --- a/Widgets/NIconPicker.qml +++ b/Widgets/NIconPicker.qml @@ -64,7 +64,7 @@ Popup { Layout.fillWidth: true NText { text: I18n.tr("widgets.icon-picker.title") - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mPrimary Layout.fillWidth: true @@ -112,7 +112,7 @@ Popup { height: grid.cellHeight radius: Style.radiusS * scaling clip: true - color: (root.selectedIcon === modelData) ? Qt.alpha(Color.mPrimary, 0.15) : "transparent" + color: (root.selectedIcon === modelData) ? Qt.alpha(Color.mPrimary, 0.15) : Color.transparent border.color: (root.selectedIcon === modelData) ? Color.mPrimary : Qt.rgba(0, 0, 0, 0) border.width: (root.selectedIcon === modelData) ? Style.borderS * scaling : 0 @@ -137,7 +137,7 @@ Popup { NIcon { Layout.alignment: Qt.AlignHCenter icon: modelData - font.pointSize: 42 * scaling + pointSize: 42 * scaling } NText { Layout.alignment: Qt.AlignHCenter @@ -148,7 +148,7 @@ Popup { maximumLineCount: 1 horizontalAlignment: Text.AlignHCenter color: Color.mOnSurfaceVariant - font.pointSize: Style.fontSizeXS * scaling + pointSize: Style.fontSizeXS * scaling text: modelData } Item { diff --git a/Widgets/NImageCircled.qml b/Widgets/NImageCircled.qml index 57ff3db4..eb0ee9bd 100644 --- a/Widgets/NImageCircled.qml +++ b/Widgets/NImageCircled.qml @@ -58,7 +58,7 @@ Rectangle { sourceComponent: NIcon { anchors.centerIn: parent icon: fallbackIcon - font.pointSize: fallbackIconSize + pointSize: fallbackIconSize z: 0 } } diff --git a/Widgets/NImageRounded.qml b/Widgets/NImageRounded.qml index d03dcf1d..ff55a571 100644 --- a/Widgets/NImageRounded.qml +++ b/Widgets/NImageRounded.qml @@ -66,7 +66,7 @@ Rectangle { Rectangle { id: background anchors.fill: parent - color: "transparent" + color: Color.transparent z: -1 } } @@ -78,7 +78,7 @@ Rectangle { sourceComponent: NIcon { anchors.centerIn: parent icon: fallbackIcon - font.pointSize: fallbackIconSize + pointSize: fallbackIconSize z: 0 } } diff --git a/Widgets/NLabel.qml b/Widgets/NLabel.qml index c322e164..8348fd0c 100644 --- a/Widgets/NLabel.qml +++ b/Widgets/NLabel.qml @@ -15,7 +15,7 @@ ColumnLayout { NText { text: label - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: labelColor visible: label !== "" @@ -24,7 +24,7 @@ ColumnLayout { NText { text: description - font.pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeS * scaling color: descriptionColor wrapMode: Text.WordWrap visible: description !== "" diff --git a/Widgets/NRadioButton.qml b/Widgets/NRadioButton.qml index 6b71dc37..d04ea777 100644 --- a/Widgets/NRadioButton.qml +++ b/Widgets/NRadioButton.qml @@ -41,7 +41,7 @@ RadioButton { contentItem: NText { text: root.text - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling anchors.verticalCenter: parent.verticalCenter anchors.left: outerCircle.right anchors.right: parent.right diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index e949405d..3175f126 100644 --- a/Widgets/NSearchableComboBox.qml +++ b/Widgets/NSearchableComboBox.qml @@ -138,7 +138,7 @@ RowLayout { contentItem: NText { leftPadding: Style.marginL * scaling rightPadding: combo.indicator.width + Style.marginL * scaling - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling verticalAlignment: Text.AlignVCenter elide: Text.ElideRight color: (combo.currentIndex >= 0 && combo.currentIndex < filteredModel.count) ? Color.mOnSurface : Color.mOnSurfaceVariant @@ -149,7 +149,7 @@ RowLayout { x: combo.width - width - Style.marginM * scaling y: combo.topPadding + (combo.availableHeight - height) / 2 icon: "caret-down" - font.pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeL * scaling } popup: Popup { @@ -206,7 +206,7 @@ RowLayout { contentItem: NText { text: name - font.pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeM * scaling color: highlighted ? Color.mSurface : Color.mOnSurface verticalAlignment: Text.AlignVCenter elide: Text.ElideRight diff --git a/Widgets/NSpinBox.qml b/Widgets/NSpinBox.qml index 5202ecfc..697cb056 100644 --- a/Widgets/NSpinBox.qml +++ b/Widgets/NSpinBox.qml @@ -42,7 +42,7 @@ RowLayout { implicitHeight: (root.baseSize - 4) * scaling radius: height * 0.5 color: Color.mSurfaceVariant - border.color: root.hovering ? Color.mPrimary : Color.mOutline + border.color: (root.hovering || decreaseArea.containsMouse || increaseArea.containsMouse) ? Color.mTertiary : Color.mOutline border.width: 1 Behavior on border.color { @@ -76,29 +76,86 @@ RowLayout { } // Decrease button (left) - Rectangle { + Item { id: decreaseButton - width: parent.height * 0.8 - height: parent.height * 0.8 - anchors.verticalCenter: parent.verticalCenter + height: parent.height + width: leftSemicircle.width + (leftDiamondContainer.width / 2) + anchors.top: parent.top + anchors.bottom: parent.bottom anchors.left: parent.left - anchors.leftMargin: parent.height * 0.1 - radius: width * 0.5 - color: decreaseArea.containsMouse ? Color.mPrimary : "transparent" opacity: root.enabled && root.value > root.from ? 1.0 : 0.3 + clip: true - Behavior on color { - ColorAnimation { - duration: Style.animationFast - easing.type: Easing.InOutCubic + Item { + id: leftSemicircle + width: Math.round(parent.height / 2) + height: parent.height + clip: true + anchors.left: parent.left + Rectangle { + width: Math.round(parent.height) + height: parent.height + radius: width / 2 + anchors.left: parent.left + color: decreaseArea.containsMouse ? Color.mTertiary : Color.transparent + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + + Item { + id: leftDiamondContainer + + height: Math.round(parent.height / 2) * 2 + width: height * Math.sqrt(2) + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: leftSemicircle.right + + Rectangle { + id: leftDiamondVisual + width: 100 + height: 100 + radius: width / 4 + + color: decreaseArea.containsMouse ? Color.mTertiary : Color.transparent + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + anchors.centerIn: parent + + transform: [ + Rotation { + angle: 45 + origin.x: 50 + origin.y: 50 + }, + Scale { + id: leftScaler + origin.x: 50 + origin.y: 50 + + // This is the full formula for the height of the rotated, rounded square + readonly property real trueHeight: (leftDiamondVisual.width - 2 * leftDiamondVisual.radius) * Math.sqrt(2) + (2 * leftDiamondVisual.radius) + xScale: leftDiamondContainer.height / leftScaler.trueHeight + yScale: leftDiamondContainer.height / leftScaler.trueHeight + } + ] } } NIcon { - anchors.centerIn: parent + anchors.left: parent.left + anchors.leftMargin: parent.width * 0.25 + anchors.verticalCenter: parent.verticalCenter icon: "chevron-left" - font.pointSize: Style.fontSizeS * scaling - color: decreaseArea.containsMouse ? Color.mOnPrimary : Color.mPrimary + pointSize: Style.fontSizeS * scaling + color: decreaseArea.containsMouse ? Color.mOnTertiary : Color.mPrimary } MouseArea { @@ -115,28 +172,86 @@ RowLayout { } // Increase button (right) - Rectangle { + Item { id: increaseButton - width: parent.height * 0.8 - height: parent.height * 0.8 - anchors.verticalCenter: parent.verticalCenter + height: parent.height + width: rightSemicircle.width + (rightDiamondContainer.width / 2) + anchors.top: parent.top + anchors.bottom: parent.bottom anchors.right: parent.right - anchors.rightMargin: parent.height * 0.1 - radius: width * 0.5 - color: increaseArea.containsMouse ? Color.mPrimary : "transparent" opacity: root.enabled && root.value < root.to ? 1.0 : 0.3 + clip: true - Behavior on color { - ColorAnimation { - duration: Style.animationFast + Item { + id: rightSemicircle + width: Math.round(parent.height / 2) + height: parent.height + clip: true + anchors.right: parent.right + Rectangle { + width: Math.round(parent.height) + height: parent.height + radius: width / 2 + anchors.right: parent.right + color: increaseArea.containsMouse ? Color.mTertiary : Color.transparent + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + + Item { + id: rightDiamondContainer + + height: Math.round(parent.height / 2) * 2 + width: height * Math.sqrt(2) + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: rightSemicircle.left + + Rectangle { + id: rightDiamondVisual + width: 100 + height: 100 + radius: width / 4 + + color: increaseArea.containsMouse ? Color.mTertiary : Color.transparent + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + anchors.centerIn: parent + + transform: [ + Rotation { + angle: 45 + origin.x: 50 + origin.y: 50 + }, + Scale { + id: rightScaler + origin.x: 50 + origin.y: 50 + + // This is the full formula for the height of the rotated, rounded square + readonly property real trueHeight: (rightDiamondVisual.width - 2 * rightDiamondVisual.radius) * Math.sqrt(2) + (2 * rightDiamondVisual.radius) + xScale: rightDiamondContainer.height / rightScaler.trueHeight + yScale: rightDiamondContainer.height / rightScaler.trueHeight + } + ] } } NIcon { - anchors.centerIn: parent + anchors.right: parent.right + anchors.rightMargin: parent.width * 0.25 + anchors.verticalCenter: parent.verticalCenter icon: "chevron-right" - font.pointSize: Style.fontSizeS * scaling - color: increaseArea.containsMouse ? Color.mOnPrimary : Color.mPrimary + pointSize: Style.fontSizeS * scaling + color: increaseArea.containsMouse ? Color.mOnTertiary : Color.mPrimary } MouseArea { @@ -160,32 +275,34 @@ RowLayout { anchors.verticalCenter: parent.verticalCenter anchors.margins: 4 * scaling height: parent.height - color: "transparent" + color: Color.transparent - Row { + RowLayout { anchors.centerIn: parent spacing: 0 // Prefix text (non-editable) - Text { + NText { text: root.prefix - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeM * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightMedium color: Color.mOnSurface verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignVCenter visible: root.prefix !== "" } // Editable number input TextInput { id: valueInput - text: root.value.toString() + text: valueInput.focus ? valueInput.text : root.value.toString() font.family: Settings.data.ui.fontFixed font.pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightMedium color: Color.mOnSurface verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignVCenter selectByMouse: true enabled: root.enabled @@ -216,23 +333,22 @@ RowLayout { function applyValue() { let newValue = parseInt(text) if (!isNaN(newValue)) { + // Don't manually set text here - let the binding handle it newValue = Math.max(root.from, Math.min(root.to, newValue)) root.value = newValue - text = root.value.toString() - } else { - text = root.value.toString() } } } // Suffix text (non-editable) - Text { + NText { text: root.suffix - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeM * scaling + family: Settings.data.ui.fontFixed + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightMedium color: Color.mOnSurface verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignVCenter visible: root.suffix !== "" } } diff --git a/Widgets/NText.qml b/Widgets/NText.qml index a0434821..9efd199e 100644 --- a/Widgets/NText.qml +++ b/Widgets/NText.qml @@ -6,9 +6,16 @@ import qs.Widgets Text { id: root - font.family: Settings.data.ui.fontDefault - font.pointSize: Style.fontSizeM * scaling + + property string family: Settings.data.ui.fontDefault + property real pointSize: Style.fontSizeM * scaling + property real fontScale: { + return (root.family === Settings.data.ui.fontDefault ? Settings.data.ui.fontDefaultScale : Settings.data.ui.fontFixedScale) + } + + font.family: root.family font.weight: Style.fontWeightMedium + font.pointSize: root.pointSize * fontScale color: Color.mOnSurface elide: Text.ElideRight wrapMode: Text.NoWrap diff --git a/Widgets/NValueSlider.qml b/Widgets/NValueSlider.qml index 981b7905..32d8ed76 100644 --- a/Widgets/NValueSlider.qml +++ b/Widgets/NValueSlider.qml @@ -40,9 +40,9 @@ RowLayout { NText { visible: root.text !== "" text: root.text - font.family: Settings.data.ui.fontFixed + family: Settings.data.ui.fontFixed Layout.alignment: Qt.AlignVCenter - Layout.preferredWidth: 40 * scaling + Layout.preferredWidth: 45 * scaling horizontalAlignment: Text.AlignRight } } diff --git a/flake.nix b/flake.nix index 9b869208..179164ac 100644 --- a/flake.nix +++ b/flake.nix @@ -264,6 +264,7 @@ after = [ cfg.target ]; partOf = [ cfg.target ]; wantedBy = [ cfg.target ]; + restartTriggers = [ cfg.package ]; unitConfig = { StartLimitIntervalSec = 60; @@ -276,7 +277,10 @@ RestartSec = 3; TimeoutStartSec = 10; TimeoutStopSec = 5; - Environment = [ "PATH=${config.system.path}/bin" ]; + Environment = [ + "PATH=${config.system.path}/bin" + "NOCTALIA_SETTINGS_FALLBACK=%h/.config/noctalia/gui-settings.json" + ]; }; }; diff --git a/shell.qml b/shell.qml index 65cbf272..c15468e1 100644 --- a/shell.qml +++ b/shell.qml @@ -29,7 +29,6 @@ import qs.Modules.Bar import qs.Modules.Bar.Extras import qs.Modules.Bar.Bluetooth import qs.Modules.Bar.Calendar - import qs.Modules.Bar.WiFi // Panels & UI Components @@ -45,79 +44,12 @@ import qs.Modules.Wallpaper ShellRoot { id: shellRoot - Background {} - Overview {} - ScreenCorners {} - Bar {} - Dock {} - - Notification { - id: notification - } - - LockScreen { - id: lockScreen - } - - ToastOverlay {} - OSD {} - - // IPCService is treated as a service - // but it's actually an Item that needs to exists in the shell. - IPCService {} - - // ------------------------------ - // All the NPanels - Launcher { - id: launcherPanel - objectName: "launcherPanel" - } - - ControlCenterPanel { - id: controlCenterPanel - objectName: "controlCenterPanel" - } - - CalendarPanel { - id: calendarPanel - objectName: "calendarPanel" - } - - SettingsPanel { - id: settingsPanel - objectName: "settingsPanel" - } - - NotificationHistoryPanel { - id: notificationHistoryPanel - objectName: "notificationHistoryPanel" - } - - SessionMenu { - id: sessionMenuPanel - objectName: "sessionMenuPanel" - } - - WiFiPanel { - id: wifiPanel - objectName: "wifiPanel" - } - - BluetoothPanel { - id: bluetoothPanel - objectName: "bluetoothPanel" - } - - WallpaperPanel { - id: wallpaperPanel - objectName: "wallpaperPanel" - } + property bool i18nLoaded: false + property bool settingsLoaded: false Component.onCompleted: { - // Save a ref. to our lockScreen so we can access it easily - PanelService.lockScreen = lockScreen - - BarWidgetRegistry.init() + Logger.log("Shell", "---------------------------") + Logger.log("Shell", "Noctalia Hello!") } Connections { @@ -126,4 +58,108 @@ ShellRoot { Quickshell.inhibitReloadPopup() } } + + Connections { + target: I18n + function onTranslationsLoaded() { + i18nLoaded = true + } + } + + Connections { + target: Settings + function onSettingsLoaded() { + settingsLoaded = true + } + } + + LazyLoader { + active: i18nLoaded && settingsLoaded + + Item { + Component.onCompleted: { + // Save a ref. to our lockScreen so we can access it easily + PanelService.lockScreen = lockScreen + + Logger.log("Shell", "---------------------------") + WallpaperService.init() + MatugenService.init() + ColorSchemeService.init() + BarWidgetRegistry.init() + LocationService.init() + NightLightService.apply() + FontService.init() + HooksService.init() + BluetoothService.init() + } + + Background {} + Overview {} + ScreenCorners {} + Bar {} + Dock {} + + Notification { + id: notification + } + + LockScreen { + id: lockScreen + } + + ToastOverlay {} + OSD {} + + // IPCService is treated as a service + // but it's actually an Item that needs to exists in the shell. + IPCService {} + + // ------------------------------ + // All the NPanels + Launcher { + id: launcherPanel + objectName: "launcherPanel" + } + + ControlCenterPanel { + id: controlCenterPanel + objectName: "controlCenterPanel" + } + + CalendarPanel { + id: calendarPanel + objectName: "calendarPanel" + } + + SettingsPanel { + id: settingsPanel + objectName: "settingsPanel" + } + + NotificationHistoryPanel { + id: notificationHistoryPanel + objectName: "notificationHistoryPanel" + } + + SessionMenu { + id: sessionMenuPanel + objectName: "sessionMenuPanel" + } + + WiFiPanel { + id: wifiPanel + objectName: "wifiPanel" + } + + BluetoothPanel { + id: bluetoothPanel + objectName: "bluetoothPanel" + } + + WallpaperPanel { + id: wallpaperPanel + objectName: "wallpaperPanel" + } + } + } }