From a6c0a9fc990f9911fbf6fbe739363b698e649474 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Tue, 14 Oct 2025 22:21:19 +0200 Subject: [PATCH 01/76] DarkModeService: Added for automatic dark mode switching. --- Services/DarkModeService.qml | 85 ++++++++++++++++++++++++++++++++++++ Services/LocationService.qml | 2 +- shell.qml | 1 + 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 Services/DarkModeService.qml diff --git a/Services/DarkModeService.qml b/Services/DarkModeService.qml new file mode 100644 index 00000000..afba5481 --- /dev/null +++ b/Services/DarkModeService.qml @@ -0,0 +1,85 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Services + +Singleton { + id: root + + property bool initComplete: false + property bool nextDarkModeState: false + + Connections { + target: LocationService.data + function onWeatherChanged() { + if (LocationService.data.weather !== null) { + const changes = root.collectChanges(LocationService.data.weather) + if (!root.initComplete) { + root.initComplete = true + root.resetDarkMode(changes) + } + root.scheduleChange(changes) + } + } + } + + Timer { + id: timer + onTriggered: { + Settings.data.colorSchemes.darkMode = root.nextDarkModeState + if (LocationService.data.weather !== null) { + const changes = root.collectChanges(LocationService.data.weather) + root.scheduleChange(changes) + } + } + } + + function collectChanges(weather) { + const changes = [] + for (var i = 0; i < weather.daily.sunrise.length; i++) { + changes.push({ + "time": Date.parse(weather.daily.sunrise[i]), + "darkMode": false + }) + changes.push({ + "time": Date.parse(weather.daily.sunset[i]), + "darkMode": true + }) + } + return changes + } + + function resetDarkMode(changes) { + const now = Date.now() + + // changes.findLast(change => change.time < now) // not available in QML... + let lastChange = null + for (var i = 0; i < changes.length; i++) { + if (changes[i].time < now) { + lastChange = changes[i] + } + } + + if (lastChange) { + Settings.data.colorSchemes.darkMode = lastChange.darkMode + Logger.log("DarkModeService", `Reset: darkmode=${lastChange.darkMode}`) + } + } + + function scheduleChange(changes) { + const now = Date.now() + const nextChange = changes.find(change => change.time > now) + if (nextChange) { + root.nextDarkModeState = nextChange.darkMode + timer.interval = nextChange.time - now + timer.restart() + Logger.log("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`) + } + } + + function init() { + Logger.log("DarkModeService", "Service started") + } +} diff --git a/Services/LocationService.qml b/Services/LocationService.qml index 0e3c0c9b..729f05bf 100644 --- a/Services/LocationService.qml +++ b/Services/LocationService.qml @@ -190,7 +190,7 @@ Singleton { // -------------------------------- function _fetchWeather(latitude, longitude, errorCallback) { Logger.log("Location", "Fetching weather from api.open-meteo.com") - var url = "https://api.open-meteo.com/v1/forecast?latitude=" + latitude + "&longitude=" + longitude + "¤t_weather=true¤t=relativehumidity_2m,surface_pressure&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=auto" + var url = "https://api.open-meteo.com/v1/forecast?latitude=" + latitude + "&longitude=" + longitude + "¤t_weather=true¤t=relativehumidity_2m,surface_pressure&daily=temperature_2m_max,temperature_2m_min,weathercode,sunset,sunrise&timezone=auto" var xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { diff --git a/shell.qml b/shell.qml index 4c4291c4..cc2837ad 100644 --- a/shell.qml +++ b/shell.qml @@ -86,6 +86,7 @@ ShellRoot { BarWidgetRegistry.init() LocationService.init() NightLightService.apply() + DarkModeService.init() FontService.init() HooksService.init() BluetoothService.init() From 49f4ab114f4f47d5e7c248bcab61616938131a8a Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Thu, 16 Oct 2025 17:23:32 +0200 Subject: [PATCH 02/76] DarkModeService: Add settings and manual scheduling mode. --- Commons/Settings.qml | 3 + Modules/Settings/Tabs/ColorSchemeTab.qml | 89 +++++++++++++++++++ Services/DarkModeService.qml | 108 ++++++++++++++++++++--- Services/LocationService.qml | 2 +- 4 files changed, 189 insertions(+), 13 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 271cecb7..36b10f2a 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -354,6 +354,9 @@ Singleton { property bool useWallpaperColors: false property string predefinedScheme: "Noctalia (default)" property bool darkMode: true + property string schedulingMode: "off" + property string manualSunrise: "06:30" + property string manualSunset: "18:30" property string matugenSchemeType: "scheme-fruit-salad" property bool generateTemplatesForPredefined: true } diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 969c8769..74205d9d 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -13,6 +13,24 @@ ColumnLayout { property var schemeColorsCache: ({}) property int cacheVersion: 0 // Increment to trigger UI updates + // Time dropdown options (00:00 .. 23:30) + ListModel { + id: timeOptions + } + Component.onCompleted: { + for (var h = 0; h < 24; h++) { + for (var m = 0; m < 60; m += 30) { + var hh = ("0" + h).slice(-2) + var mm = ("0" + m).slice(-2) + var key = hh + ":" + mm + timeOptions.append({ + "key": key, + "name": key + }) + } + } + } + spacing: Style.marginL // Helper function to extract scheme name from path @@ -148,6 +166,77 @@ ColumnLayout { } } + NComboBox { + label: "Dark Mode Schedule" + description: "Enables automatic switching between light and dark mode" + + model: [{ + "name": "Off", + "key": "off" + }, { + "name": "Manual", + "key": "manual" + }, { + "name": "Sunrise/Sunset", + "key": "location" + }] + + currentKey: Settings.data.colorSchemes.schedulingMode + + onSelected: key => { + Settings.data.colorSchemes.schedulingMode = key + AppThemeService.generate() + } + } + + // Manual scheduling + ColumnLayout { + spacing: Style.marginS + visible: Settings.data.colorSchemes.schedulingMode === "manual" + + NLabel { + label: I18n.tr("settings.display.night-light.manual-schedule.label") + description: I18n.tr("settings.display.night-light.manual-schedule.description") + } + + RowLayout { + Layout.fillWidth: false + spacing: Style.marginS + + NText { + text: I18n.tr("settings.display.night-light.manual-schedule.sunrise") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + + NComboBox { + model: timeOptions + currentKey: Settings.data.colorSchemes.manualSunrise + placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-start") + onSelected: key => Settings.data.colorSchemes.manualSunrise = key + minimumWidth: 120 + } + + Item { + Layout.preferredWidth: 20 + } + + NText { + text: I18n.tr("settings.display.night-light.manual-schedule.sunset") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + + NComboBox { + model: timeOptions + currentKey: Settings.data.colorSchemes.manualSunset + placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-stop") + onSelected: key => Settings.data.colorSchemes.manualSunset = key + minimumWidth: 120 + } + } + } + // Use Wallpaper Colors NToggle { label: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label") diff --git a/Services/DarkModeService.qml b/Services/DarkModeService.qml index afba5481..308d393b 100644 --- a/Services/DarkModeService.qml +++ b/Services/DarkModeService.qml @@ -13,31 +13,118 @@ Singleton { Connections { target: LocationService.data + enabled: Settings.data.colorSchemes.schedulingMode == "location" function onWeatherChanged() { if (LocationService.data.weather !== null) { - const changes = root.collectChanges(LocationService.data.weather) + const changes = root.collectWeatherChanges(LocationService.data.weather) if (!root.initComplete) { root.initComplete = true - root.resetDarkMode(changes) + root.applyCurrentMode(changes) } - root.scheduleChange(changes) + root.scheduleNextMode(changes) } } } + Connections { + target: Settings.data.colorSchemes + enabled: Settings.data.colorSchemes.schedulingMode == "manual" + function onManualSunriseChanged() { + const changes = root.collectManualChanges() + root.applyCurrentMode(changes) + root.scheduleNextMode(changes) + } + function onManualSunsetChanged() { + const changes = root.collectManualChanges() + root.applyCurrentMode(changes) + root.scheduleNextMode(changes) + } + } + + Connections { + target: Settings.data.colorSchemes + function onSchedulingModeChanged() { + root.init() + } + } + Timer { id: timer onTriggered: { Settings.data.colorSchemes.darkMode = root.nextDarkModeState if (LocationService.data.weather !== null) { - const changes = root.collectChanges(LocationService.data.weather) - root.scheduleChange(changes) + const changes = root.collectWeatherChanges(LocationService.data.weather) + root.scheduleNextMode(changes) } } } - function collectChanges(weather) { + function init() { + Logger.log("DarkModeService", "Service started") + + if (Settings.data.colorSchemes.schedulingMode == "manual") { + const changes = collectManualChanges() + initComplete = true + applyCurrentMode(changes) + scheduleNextMode(changes) + } + + if (Settings.data.colorSchemes.schedulingMode == "location" && LocationService.data.weather) { + const changes = collectWeatherChanges(LocationService.data.weather) + initComplete = true + applyCurrentMode(changes) + scheduleNextMode(changes) + } + } + + function parseTime(timeString) { + const parts = timeString.split(":").map(Number) + return { + "hour": parts[0], + "minute": parts[1] + } + } + + function collectManualChanges() { + const sunriseTime = parseTime(Settings.data.colorSchemes.manualSunrise) + const sunsetTime = parseTime(Settings.data.colorSchemes.manualSunset) + + const now = new Date() + const year = now.getFullYear() + const month = now.getMonth() + const day = now.getDate() + + const yesterdaysSunset = new Date(year, month, day - 1, sunsetTime.hour, sunsetTime.minute) + const todaysSunrise = new Date(year, month, day, sunriseTime.hour, sunriseTime.minute) + const todaysSunset = new Date(year, month, day, sunsetTime.hour, sunsetTime.minute) + const tomorrowsSunrise = new Date(year, month, day + 1, sunriseTime.hour, sunriseTime.minute) + + return [{ + "time": yesterdaysSunset.getTime(), + "darkMode": true + }, { + "time": todaysSunrise.getTime(), + "darkMode": false + }, { + "time": todaysSunset.getTime(), + "darkMode": true + }, { + "time": tomorrowsSunrise.getTime(), + "darkMode": false + }] + } + + function collectWeatherChanges(weather) { const changes = [] + + if (Date.now() < Date.parse(weather.daily.sunrise[0])) { + // The sun has not risen yet + changes.push({ + "time": Date.now() - 1, + "darkMode": true + }) + } + for (var i = 0; i < weather.daily.sunrise.length; i++) { changes.push({ "time": Date.parse(weather.daily.sunrise[i]), @@ -48,10 +135,11 @@ Singleton { "darkMode": true }) } + return changes } - function resetDarkMode(changes) { + function applyCurrentMode(changes) { const now = Date.now() // changes.findLast(change => change.time < now) // not available in QML... @@ -68,7 +156,7 @@ Singleton { } } - function scheduleChange(changes) { + function scheduleNextMode(changes) { const now = Date.now() const nextChange = changes.find(change => change.time > now) if (nextChange) { @@ -78,8 +166,4 @@ Singleton { Logger.log("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`) } } - - function init() { - Logger.log("DarkModeService", "Service started") - } } diff --git a/Services/LocationService.qml b/Services/LocationService.qml index 729f05bf..4c2709bd 100644 --- a/Services/LocationService.qml +++ b/Services/LocationService.qml @@ -69,7 +69,7 @@ Singleton { Timer { id: updateTimer interval: 20 * 1000 - running: Settings.data.location.weatherEnabled + running: Settings.data.location.weatherEnabled || Settings.data.colorSchemes.schedulingMode == "location" repeat: true onTriggered: { updateWeather() From b82cdefd97a532c92ddb7d4be4e5497e91594883 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Thu, 16 Oct 2025 17:40:00 +0200 Subject: [PATCH 03/76] ColorSchemeTab: Add translations for DarkModeService settings. --- Assets/Translations/de.json | 17 +++++++++++++---- Assets/Translations/en.json | 19 ++++++++++++++----- Assets/Translations/es.json | 10 ++++++---- Assets/Translations/fr.json | 10 ++++++---- Assets/Translations/pt.json | 10 ++++++---- Assets/Translations/zh-CN.json | 10 ++++++---- Modules/Settings/Tabs/ColorSchemeTab.qml | 14 +++++++------- 7 files changed, 58 insertions(+), 32 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 2be6197b..2499d5a2 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -475,10 +475,6 @@ "label": "Farbquelle", "description": "Haupteinstellungen für Noctalias Farben." }, - "dark-mode": { - "label": "Dunkler Modus", - "description": "Wechselt zu einem dunkleren Theme für einfachere Betrachtung bei Nacht." - }, "use-wallpaper-colors": { "label": "Hintergrundbild-Farben verwenden", "description": "Farbschemata aus Ihrem Hintergrundbild mit Matugen generieren. Extrahiert automatisch Farben für ein kohärentes Design." @@ -488,6 +484,19 @@ "description": "Wähle einen Farbstil für Matugen aus." } }, + "dark-mode": { + "switch": { + "label": "Dunkler Modus", + "description": "Wechselt zu einem dunkleren Theme für einfachere Betrachtung bei Nacht." + }, + "mode": { + "label": "Automatischer dunkler Modus", + "description": "Ermöglicht automatisches Wechseln zwischen dem hellen und dunklen Modus.", + "off": "Aus", + "manual": "Manuell", + "location": "Standort" + } + }, "predefined": { "section": { "label": "Vordefinierte Farbschemata", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 915b75d1..31b9be46 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -475,10 +475,6 @@ "label": "Color source", "description": "Main settings for Noctalia's colors." }, - "dark-mode": { - "label": "Dark mode", - "description": "Switches to a darker theme for easier viewing at night." - }, "use-wallpaper-colors": { "label": "Use wallpaper colors", "description": "Generate color schemes from your wallpaper using Matugen. Automatically extracts colors to create a cohesive theme." @@ -488,6 +484,19 @@ "description": "Choose the color scheme generation algorithm for Matugen." } }, + "dark-mode": { + "switch": { + "label": "Dark mode", + "description": "Switches to a darker theme for easier viewing at night." + }, + "mode": { + "label": "Dark mode schedule", + "description": "Enables automatic switching between light and dark mode.", + "off": "Off", + "manual": "Manual", + "location": "Location" + } + }, "predefined": { "section": { "label": "Predefined color schemes", @@ -1187,7 +1196,7 @@ "scan-again": "Scan again" } }, - + "tooltips": { "refresh": "Refresh", "close": "Close", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 72251b3f..d02fa0ec 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -475,10 +475,6 @@ "label": "Fuente de color", "description": "Configuración principal de los colores de Noctalia." }, - "dark-mode": { - "label": "Modo oscuro", - "description": "Cambia a un tema más oscuro para una visualización más fácil por la noche." - }, "use-wallpaper-colors": { "label": "Usar colores del fondo de pantalla", "description": "Generar esquemas de color desde tu fondo de pantalla usando Matugen. Extrae automáticamente colores para crear un tema cohesivo." @@ -488,6 +484,12 @@ "description": "Elige el algoritmo de generación de esquema de colores para Matugen." } }, + "dark-mode": { + "switch": { + "label": "Modo oscuro", + "description": "Cambia a un tema más oscuro para una visualización más fácil por la noche." + } + }, "predefined": { "section": { "label": "Esquemas de colores predefinidos", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8399a63a..fdc0455c 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -475,10 +475,6 @@ "label": "Source des couleurs", "description": "Paramètres principaux pour les couleurs de Noctalia." }, - "dark-mode": { - "label": "Mode sombre", - "description": "Passe à un thème plus sombre pour une visualisation plus facile la nuit." - }, "use-wallpaper-colors": { "label": "Utiliser les couleurs du fond d'écran", "description": "Générer des schémas de couleurs à partir de votre fond d'écran avec Matugen. Extrait automatiquement les couleurs pour créer un thème cohérent." @@ -488,6 +484,12 @@ "description": "Choisissez l'algorithme de génération de schéma de couleurs pour Matugen." } }, + "dark-mode": { + "switch": { + "label": "Mode sombre", + "description": "Passe à un thème plus sombre pour une visualisation plus facile la nuit." + } + }, "predefined": { "section": { "label": "Jeux de couleurs prédéfinis", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index c4c3ea52..89113f07 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -437,10 +437,6 @@ "label": "Fonte de cor", "description": "Configurações principais para as cores do Noctalia." }, - "dark-mode": { - "label": "Modo escuro", - "description": "Muda para um tema mais escuro para facilitar a visualização à noite." - }, "use-wallpaper-colors": { "label": "Usar cores do papel de parede", "description": "Gerar esquemas de cores do seu papel de parede usando Matugen. Extrai automaticamente cores para criar um tema coeso." @@ -450,6 +446,12 @@ "description": "Escolha o algoritmo de geração de esquema de cores para Matugen." } }, + "dark-mode": { + "switch": { + "label": "Modo escuro", + "description": "Muda para um tema mais escuro para facilitar a visualização à noite." + } + }, "predefined": { "section": { "label": "Esquemas de cores predefinidos", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 0116a5da..8d27c6ad 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -475,10 +475,6 @@ "label": "颜色来源", "description": "Noctalia 颜色的主要设置。" }, - "dark-mode": { - "label": "深色模式", - "description": "切换到更暗的主题,便于夜间观看。" - }, "use-wallpaper-colors": { "label": "使用壁纸颜色", "description": "使用 Matugen 从壁纸生成颜色方案。自动提取颜色以创建一致的主题。" @@ -488,6 +484,12 @@ "description": "为 Matugen 选择配色方案生成算法。" } }, + "dark-mode": { + "switch": { + "label": "深色模式", + "description": "切换到更暗的主题,便于夜间观看。" + } + }, "predefined": { "section": { "label": "预定义配色方案", diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 74205d9d..fbe5a296 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -156,8 +156,8 @@ ColumnLayout { // Dark Mode Toggle NToggle { - label: I18n.tr("settings.color-scheme.color-source.dark-mode.label") - description: I18n.tr("settings.color-scheme.color-source.dark-mode.description") + label: I18n.tr("settings.color-scheme.dark-mode.switch.label") + description: I18n.tr("settings.color-scheme.dark-mode.switch.description") checked: Settings.data.colorSchemes.darkMode enabled: true onToggled: checked => { @@ -167,17 +167,17 @@ ColumnLayout { } NComboBox { - label: "Dark Mode Schedule" - description: "Enables automatic switching between light and dark mode" + label: I18n.tr("settings.color-scheme.dark-mode.mode.label") + description: I18n.tr("settings.color-scheme.dark-mode.mode.description") model: [{ - "name": "Off", + "name": I18n.tr("settings.color-scheme.dark-mode.mode.off"), "key": "off" }, { - "name": "Manual", + "name": I18n.tr("settings.color-scheme.dark-mode.mode.manual"), "key": "manual" }, { - "name": "Sunrise/Sunset", + "name": I18n.tr("settings.color-scheme.dark-mode.mode.location"), "key": "location" }] From e57b565f800567ed27d63973faa92f90a2943369 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Thu, 16 Oct 2025 17:48:11 +0200 Subject: [PATCH 04/76] DarkModeService: Update to new logging style. --- Services/DarkModeService.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Services/DarkModeService.qml b/Services/DarkModeService.qml index 308d393b..ad3eef00 100644 --- a/Services/DarkModeService.qml +++ b/Services/DarkModeService.qml @@ -60,7 +60,7 @@ Singleton { } function init() { - Logger.log("DarkModeService", "Service started") + Logger.i("DarkModeService", "Service started") if (Settings.data.colorSchemes.schedulingMode == "manual") { const changes = collectManualChanges() @@ -152,7 +152,7 @@ Singleton { if (lastChange) { Settings.data.colorSchemes.darkMode = lastChange.darkMode - Logger.log("DarkModeService", `Reset: darkmode=${lastChange.darkMode}`) + Logger.d("DarkModeService", `Reset: darkmode=${lastChange.darkMode}`) } } @@ -163,7 +163,7 @@ Singleton { root.nextDarkModeState = nextChange.darkMode timer.interval = nextChange.time - now timer.restart() - Logger.log("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`) + Logger.d("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`) } } } From 8e5e003f8ab806dd022245c038408b0f671ea4a9 Mon Sep 17 00:00:00 2001 From: Sakari <20642596+sakarie9@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:57:26 +0800 Subject: [PATCH 05/76] MediaMini: implement dynamic width with max width setting --- Assets/Translations/de.json | 4 ++ Assets/Translations/en.json | 4 ++ Assets/Translations/es.json | 4 ++ Assets/Translations/fr.json | 4 ++ Assets/Translations/pt.json | 4 ++ Assets/Translations/zh-CN.json | 4 ++ Modules/Bar/Widgets/MediaMini.qml | 54 +++++++++++++++++-- .../Bar/WidgetSettings/MediaMiniSettings.qml | 11 ++++ Services/BarWidgetRegistry.qml | 1 + 9 files changed, 86 insertions(+), 4 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 2be6197b..8991324b 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1071,6 +1071,10 @@ "label": "Visualizer-Typ", "description": "Stil des Audio-Visualizers auswählen." }, + "max-width": { + "label": "Maximale Breite", + "description": "Stellt die maximale Horizontalgröße des Widgets ein. Das Widget wird sich an kürzere Inhalte anpassen." + }, "scrolling-mode": { "label": "Scrollmodus", "description": "Steuern, wann Textscrolling für lange Track-Titel aktiviert ist." diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 915b75d1..15f35798 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1054,6 +1054,10 @@ "label": "Visualizer type", "description": "Choose the style of audio visualizer to display." }, + "max-width": { + "label": "Maximum Width", + "description": "Sets the maximum horizontal size of the widget. The widget will shrink to fit shorter content." + }, "scrolling-mode": { "label": "Scrolling mode", "description": "Control when text scrolling is enabled for long track titles." diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 72251b3f..644ed5ba 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1054,6 +1054,10 @@ "label": "Tipo de visualizador", "description": "Elegir el estilo de visualizador de audio a mostrar." }, + "max-width": { + "label": "Ancho Máximo", + "description": "Establece el tamaño horizontal máximo del widget. El widget se reducirá para adaptarse a contenido más corto." + }, "scrolling-mode": { "label": "Modo de desplazamiento", "description": "Controlar cuándo está habilitado el desplazamiento de texto para títulos de pista largos." diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8399a63a..e4a78db2 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1050,6 +1050,10 @@ "label": "Type de visualiseur", "description": "Choisir le style de visualiseur audio à afficher." }, + "max-width": { + "label": "Largeur Maximale", + "description": "Définit la taille horizontale maximale du widget. Le widget se rétrécira pour s'adapter à un contenu plus court." + }, "scrolling-mode": { "label": "Mode de défilement", "description": "Contrôler quand le défilement de texte est activé pour les titres de piste longs." diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index c4c3ea52..e2901797 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1054,6 +1054,10 @@ "label": "Tipo de visualizador", "description": "Escolher o estilo de visualizador de áudio a exibir." }, + "max-width": { + "label": "Largura Máxima", + "description": "Define o tamanho horizontal máximo do widget. O widget será reduzido para se adequar a conteúdo mais curto." + }, "scrolling-mode": { "label": "Modo de rolagem", "description": "Controlar quando a rolagem de texto está habilitada para títulos de faixa longos." diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 0116a5da..27504728 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1054,6 +1054,10 @@ "label": "可视化器类型", "description": "选择要显示的音频可视化器样式。" }, + "max-width": { + "label": "最大宽度", + "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。" + }, "scrolling-mode": { "label": "滚动模式", "description": "控制何时为长曲目标题启用文本滚动。" diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 470bc37e..7f538b54 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -38,8 +38,8 @@ Item { readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType !== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : widgetMetadata.scrollingMode - // Fixed width - no expansion - readonly property real widgetWidth: Math.max(145, screen.width * 0.06) + // Maximum widget width with user settings support + readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) readonly property bool hasActivePlayer: MediaService.currentPlayer !== null readonly property string placeholderText: I18n.tr("bar.widget-settings.media-mini.no-active-player") @@ -60,7 +60,7 @@ Item { } implicitHeight: visible ? (isVerticalBar ? calculatedVerticalDimension() : Style.barHeight) : 0 - implicitWidth: visible ? (isVerticalBar ? calculatedVerticalDimension() : widgetWidth) : 0 + implicitWidth: visible ? (isVerticalBar ? calculatedVerticalDimension() : dynamicWidth) : 0 // "visible": Always Visible, "hidden": Hide When Empty, "transparent": Transparent When Empty visible: hideMode !== "hidden" || hasActivePlayer @@ -80,6 +80,44 @@ Item { return Math.round((Style.baseWidgetSize - 5) * scaling) } + function calculateContentWidth() { + // Calculate the actual content width based on visible elements + var contentWidth = 0 + var margins = Style.marginS * scaling * 2 // Left and right margins + + // Icon or album art width + if (!hasActivePlayer || !showAlbumArt) { + // Icon width + contentWidth += Style.fontSizeL * scaling + } else if (showAlbumArt && hasActivePlayer) { + // Album art width + contentWidth += 21 * scaling + } + + // Spacing between icon/art and text + contentWidth += Style.marginS * scaling + + // Text width (use the measured width) + contentWidth += fullTitleMetrics.contentWidth + + // Additional small margin for text + contentWidth += Style.marginXXS * 2 + + // Add container margins + contentWidth += margins + + return Math.ceil(contentWidth) + } + + // Dynamic width: adapt to content but respect maximum width setting + readonly property real dynamicWidth: { + if (!hasActivePlayer) { + return maxWidth + } + // Use content width but don't exceed user-set maximum width + return Math.min(calculateContentWidth(), maxWidth) + } + // A hidden text element to safely measure the full title width NText { id: fullTitleMetrics @@ -95,11 +133,19 @@ Item { visible: root.visible anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - width: isVerticalBar ? root.width : (widgetWidth) + width: isVerticalBar ? root.width : dynamicWidth height: isVerticalBar ? width : Style.capsuleHeight radius: isVerticalBar ? width / 2 : Style.radiusM color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent + // Smooth width transition + Behavior on width { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + Item { id: mainContainer anchors.fill: parent diff --git a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml index 1d03c87f..6d778510 100644 --- a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml @@ -19,6 +19,7 @@ ColumnLayout { property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer property string valueVisualizerType: widgetData.visualizerType || widgetMetadata.visualizerType property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode + property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth Component.onCompleted: { if (widgetData && widgetData.hideMode !== undefined) { @@ -33,6 +34,7 @@ ColumnLayout { settings.showVisualizer = valueShowVisualizer settings.visualizerType = valueVisualizerType settings.scrollingMode = valueScrollingMode + settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth return settings } @@ -87,6 +89,15 @@ ColumnLayout { minimumWidth: 200 } + NTextInput { + id: widthInput + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.media-mini.max-width.label") + description: I18n.tr("bar.widget-settings.media-mini.max-width.description") + placeholderText: widgetMetadata.maxWidth + text: valueMaxWidth + } + NComboBox { label: I18n.tr("bar.widget-settings.media-mini.scrolling-mode.label") description: I18n.tr("bar.widget-settings.media-mini.scrolling-mode.description") diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index bd134591..f23a7b16 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -88,6 +88,7 @@ Singleton { "hideMode": "hidden", "scrollingMode"// "visible", "hidden", "transparent" : "hover", + "maxWidth": 145, "showAlbumArt": false, "showVisualizer": false, "visualizerType": "linear" From a12fbca80b08ab0e9fa90b3d0245e26cad271a17 Mon Sep 17 00:00:00 2001 From: Sakari <20642596+sakarie9@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:11:53 +0800 Subject: [PATCH 06/76] ActiveWindow: implement dynamic width with max width setting --- Assets/Translations/de.json | 6 +-- Assets/Translations/en.json | 6 +-- Assets/Translations/es.json | 6 +-- Assets/Translations/fr.json | 6 +-- Assets/Translations/pt.json | 6 +-- Assets/Translations/zh-CN.json | 6 +-- Modules/Bar/Widgets/ActiveWindow.qml | 48 +++++++++++++++++-- .../WidgetSettings/ActiveWindowSettings.qml | 12 ++--- Services/BarWidgetRegistry.qml | 2 +- 9 files changed, 70 insertions(+), 28 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 2be6197b..88d32a40 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -918,9 +918,9 @@ "label": "Scrollmodus", "description": "Steuern, wann Textscrolling für lange Fenstertitel aktiviert ist." }, - "width": { - "description": "Steuert die horizontale Größe des Widgets.", - "label": "Widget-Breite" + "max-width": { + "label": "Maximale Breite", + "description": "Stellt die maximale Horizontalgröße des Widgets ein. Das Widget wird sich an kürzere Inhalte anpassen." }, "colorize-icons": { "label": "Symbole einfärben", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 915b75d1..ee7261de 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -901,9 +901,9 @@ "label": "Scrolling mode", "description": "Control when text scrolling is enabled for long window titles." }, - "width": { - "label": "Widget Width", - "description": "Controls the horizontal size of the widget." + "max-width": { + "label": "Maximum Width", + "description": "Sets the maximum horizontal size of the widget. The widget will shrink to fit shorter content." }, "colorize-icons": { "label": "Colorize Icons", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 72251b3f..c0e891fb 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -901,9 +901,9 @@ "label": "Modo de desplazamiento", "description": "Controlar cuándo está habilitado el desplazamiento de texto para títulos de ventana largos." }, - "width": { - "description": "Controla el tamaño horizontal del widget.", - "label": "Ancho del widget" + "max-width": { + "label": "Ancho Máximo", + "description": "Establece el tamaño horizontal máximo del widget. El widget se reducirá para adaptarse a contenido más corto." }, "colorize-icons": { "label": "Colorear iconos", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8399a63a..2019a400 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -901,9 +901,9 @@ "label": "Mode de masquage", "description": "Contrôle le comportement du widget lorsqu'aucune fenêtre n'est active." }, - "width": { - "description": "Contrôle la taille horizontale du widget.", - "label": "Largeur du widget" + "max-width": { + "label": "Largeur Maximale", + "description": "Définit la taille horizontale maximale du widget. Le widget se rétrécira pour s'adapter à un contenu plus court." }, "colorize-icons": { "label": "Coloriser les icônes", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index c4c3ea52..537014f1 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -901,9 +901,9 @@ "label": "Modo de rolagem", "description": "Controlar quando a rolagem de texto está habilitada para títulos de janela longos." }, - "width": { - "description": "Controla o tamanho horizontal do widget.", - "label": "Largura do Widget" + "max-width": { + "label": "Largura Máxima", + "description": "Define o tamanho horizontal máximo do widget. O widget será reduzido para se adequar a conteúdo mais curto." }, "colorize-icons": { "label": "Colorir ícones", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 0116a5da..72e9c542 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -901,9 +901,9 @@ "label": "滚动模式", "description": "控制何时为长窗口标题启用文本滚动。" }, - "width": { - "description": "控制小部件的水平尺寸。", - "label": "小部件宽度" + "max-width": { + "label": "最大宽度", + "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。" }, "colorize-icons": { "label": "着色图标", diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 145395d8..118e41cb 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -35,7 +35,9 @@ Item { readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon readonly property string hideMode: (widgetSettings.hideMode !== undefined) ? widgetSettings.hideMode : widgetMetadata.hideMode readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : (widgetMetadata.scrollingMode !== undefined ? widgetMetadata.scrollingMode : "hover") - readonly property int widgetWidth: (widgetSettings.width !== undefined) ? widgetSettings.width : Math.max(widgetMetadata.width, screen.width * 0.06) + + // Maximum widget width with user settings support + readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") readonly property bool hasFocusedWindow: CompositorService.getFocusedWindow() !== null @@ -43,7 +45,7 @@ Item { readonly property string fallbackIcon: "user-desktop" implicitHeight: visible ? (isVerticalBar ? calculatedVerticalDimension() : Style.barHeight) : 0 - implicitWidth: visible ? (isVerticalBar ? calculatedVerticalDimension() : widgetWidth) : 0 + implicitWidth: visible ? (isVerticalBar ? calculatedVerticalDimension() : dynamicWidth) : 0 // "visible": Always Visible, "hidden": Hide When Empty, "transparent": Transparent When Empty visible: hideMode !== "hidden" || hasFocusedWindow @@ -59,6 +61,38 @@ Item { return Math.round((Style.baseWidgetSize - 5) * scaling) } + function calculateContentWidth() { + // Calculate the actual content width based on visible elements + var contentWidth = 0 + var margins = Style.marginS * scaling * 2 // Left and right margins + + // Icon width (if visible) + if (showIcon) { + contentWidth += 18 * scaling + contentWidth += Style.marginS * scaling // Spacing after icon + } + + // Text width (use the measured width) + contentWidth += fullTitleMetrics.contentWidth + + // Additional small margin for text + contentWidth += Style.marginXXS * 2 + + // Add container margins + contentWidth += margins + + return Math.ceil(contentWidth) + } + + // Dynamic width: adapt to content but respect maximum width setting + readonly property real dynamicWidth: { + if (!hasFocusedWindow) { + return maxWidth + } + // Use content width but don't exceed user-set maximum width + return Math.min(calculateContentWidth(), maxWidth) + } + function getAppIcon() { try { // Try CompositorService first @@ -117,11 +151,19 @@ Item { visible: root.visible anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - width: isVerticalBar ? root.width : widgetWidth + width: isVerticalBar ? root.width : dynamicWidth height: isVerticalBar ? width : Style.capsuleHeight radius: isVerticalBar ? width / 2 : Style.radiusM color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent + // Smooth width transition + Behavior on width { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + Item { id: mainContainer anchors.fill: parent diff --git a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml index 643684dd..0a4b5044 100644 --- a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -17,7 +17,7 @@ ColumnLayout { property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon property string valueHideMode: "hidden" // Default to 'Hide When Empty' property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode - property int valueWidth: widgetData.width !== undefined ? widgetData.width : widgetMetadata.width + property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons Component.onCompleted: { @@ -31,7 +31,7 @@ ColumnLayout { settings.hideMode = valueHideMode settings.showIcon = valueShowIcon settings.scrollingMode = valueScrollingMode - settings.width = parseInt(widthInput.text) || widgetMetadata.width + settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth settings.colorizeIcons = valueColorizeIcons return settings } @@ -73,10 +73,10 @@ ColumnLayout { NTextInput { id: widthInput Layout.fillWidth: true - label: I18n.tr("bar.widget-settings.active-window.width.label") - description: I18n.tr("bar.widget-settings.active-window.width.description") - placeholderText: widgetMetadata.width - text: valueWidth + label: I18n.tr("bar.widget-settings.active-window.max-width.label") + description: I18n.tr("bar.widget-settings.active-window.max-width.description") + placeholderText: widgetMetadata.maxWidth + text: valueMaxWidth } NComboBox { diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index bd134591..f16bee0a 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -44,7 +44,7 @@ Singleton { "hideMode": "hidden", "scrollingMode"// "visible", "hidden", "transparent" : "hover", - "width": 145, + "maxWidth": 145, "colorizeIcons": false }, "Battery": { From 19bfeb2a40f6b5ac4f7f8e1f64ac0c719bdddc9b Mon Sep 17 00:00:00 2001 From: Sakari <20642596+sakarie9@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:28:32 +0800 Subject: [PATCH 07/76] MediaMini: add fixed width option --- Assets/Translations/de.json | 4 ++++ Assets/Translations/en.json | 4 ++++ Assets/Translations/es.json | 4 ++++ Assets/Translations/fr.json | 4 ++++ Assets/Translations/pt.json | 4 ++++ Assets/Translations/zh-CN.json | 4 ++++ Modules/Bar/Widgets/MediaMini.qml | 6 ++++++ .../Settings/Bar/WidgetSettings/MediaMiniSettings.qml | 9 +++++++++ Services/BarWidgetRegistry.qml | 1 + 9 files changed, 40 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 8991324b..21110d4f 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1075,6 +1075,10 @@ "label": "Maximale Breite", "description": "Stellt die maximale Horizontalgröße des Widgets ein. Das Widget wird sich an kürzere Inhalte anpassen." }, + "use-fixed-width": { + "label": "Feste Breite verwenden", + "description": "Wenn aktiviert, verwendet das Widget immer die maximale Breite, anstatt sich dynamisch an den Inhalt anzupassen." + }, "scrolling-mode": { "label": "Scrollmodus", "description": "Steuern, wann Textscrolling für lange Track-Titel aktiviert ist." diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 15f35798..7ad8c91b 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1058,6 +1058,10 @@ "label": "Maximum Width", "description": "Sets the maximum horizontal size of the widget. The widget will shrink to fit shorter content." }, + "use-fixed-width": { + "label": "Use Fixed Width", + "description": "When enabled, the widget will always use the maximum width instead of dynamically adjusting to content." + }, "scrolling-mode": { "label": "Scrolling mode", "description": "Control when text scrolling is enabled for long track titles." diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 644ed5ba..cbb96e68 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1058,6 +1058,10 @@ "label": "Ancho Máximo", "description": "Establece el tamaño horizontal máximo del widget. El widget se reducirá para adaptarse a contenido más corto." }, + "use-fixed-width": { + "label": "Usar Ancho Fijo", + "description": "Cuando está activado, el widget siempre usará el ancho máximo en lugar de ajustarse dinámicamente al contenido." + }, "scrolling-mode": { "label": "Modo de desplazamiento", "description": "Controlar cuándo está habilitado el desplazamiento de texto para títulos de pista largos." diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index e4a78db2..e783353d 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1054,6 +1054,10 @@ "label": "Largeur Maximale", "description": "Définit la taille horizontale maximale du widget. Le widget se rétrécira pour s'adapter à un contenu plus court." }, + "use-fixed-width": { + "label": "Utiliser une Largeur Fixe", + "description": "Lorsque activé, le widget utilisera toujours la largeur maximale au lieu de s'ajuster dynamiquement au contenu." + }, "scrolling-mode": { "label": "Mode de défilement", "description": "Contrôler quand le défilement de texte est activé pour les titres de piste longs." diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index e2901797..48bea69a 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1058,6 +1058,10 @@ "label": "Largura Máxima", "description": "Define o tamanho horizontal máximo do widget. O widget será reduzido para se adequar a conteúdo mais curto." }, + "use-fixed-width": { + "label": "Usar Largura Fixa", + "description": "Quando ativado, o widget sempre usará a largura máxima em vez de ajustar dinamicamente ao conteúdo." + }, "scrolling-mode": { "label": "Modo de rolagem", "description": "Controlar quando a rolagem de texto está habilitada para títulos de faixa longos." diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 27504728..1fc13bda 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1058,6 +1058,10 @@ "label": "最大宽度", "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。" }, + "use-fixed-width": { + "label": "使用固定宽度", + "description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。" + }, "scrolling-mode": { "label": "滚动模式", "description": "控制何时为长曲目标题启用文本滚动。" diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 7f538b54..38a56a34 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -40,6 +40,7 @@ Item { // Maximum widget width with user settings support readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) + readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth readonly property bool hasActivePlayer: MediaService.currentPlayer !== null readonly property string placeholderText: I18n.tr("bar.widget-settings.media-mini.no-active-player") @@ -111,6 +112,11 @@ Item { // Dynamic width: adapt to content but respect maximum width setting readonly property real dynamicWidth: { + // If using fixed width mode, always use maxWidth + if (useFixedWidth) { + return maxWidth + } + // Otherwise, adapt to content if (!hasActivePlayer) { return maxWidth } diff --git a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml index 6d778510..5e109feb 100644 --- a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml @@ -20,6 +20,7 @@ ColumnLayout { property string valueVisualizerType: widgetData.visualizerType || widgetMetadata.visualizerType property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth + property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth Component.onCompleted: { if (widgetData && widgetData.hideMode !== undefined) { @@ -35,6 +36,7 @@ ColumnLayout { settings.visualizerType = valueVisualizerType settings.scrollingMode = valueScrollingMode settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth + settings.useFixedWidth = valueUseFixedWidth return settings } @@ -98,6 +100,13 @@ ColumnLayout { text: valueMaxWidth } + NToggle { + label: I18n.tr("bar.widget-settings.media-mini.use-fixed-width.label") + description: I18n.tr("bar.widget-settings.media-mini.use-fixed-width.description") + checked: valueUseFixedWidth + onToggled: checked => valueUseFixedWidth = checked + } + NComboBox { label: I18n.tr("bar.widget-settings.media-mini.scrolling-mode.label") description: I18n.tr("bar.widget-settings.media-mini.scrolling-mode.description") diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index f23a7b16..ad78c31f 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -89,6 +89,7 @@ Singleton { "scrollingMode"// "visible", "hidden", "transparent" : "hover", "maxWidth": 145, + "useFixedWidth": false, "showAlbumArt": false, "showVisualizer": false, "visualizerType": "linear" From 0ab65f7f7e62a732e68199618475a6ce7c1d1b61 Mon Sep 17 00:00:00 2001 From: Sakari <20642596+sakarie9@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:38:39 +0800 Subject: [PATCH 08/76] ActiveWindow: add fixed width option --- Assets/Translations/de.json | 4 ++++ Assets/Translations/en.json | 4 ++++ Assets/Translations/es.json | 4 ++++ Assets/Translations/fr.json | 4 ++++ Assets/Translations/pt.json | 4 ++++ Assets/Translations/zh-CN.json | 4 ++++ Modules/Bar/Widgets/ActiveWindow.qml | 6 ++++++ .../Bar/WidgetSettings/ActiveWindowSettings.qml | 10 ++++++++++ Services/BarWidgetRegistry.qml | 1 + 9 files changed, 41 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 88d32a40..fd7f64d7 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -922,6 +922,10 @@ "label": "Maximale Breite", "description": "Stellt die maximale Horizontalgröße des Widgets ein. Das Widget wird sich an kürzere Inhalte anpassen." }, + "use-fixed-width": { + "label": "Feste Breite verwenden", + "description": "Wenn aktiviert, verwendet das Widget immer die maximale Breite, anstatt sich dynamisch an den Inhalt anzupassen." + }, "colorize-icons": { "label": "Symbole einfärben", "description": "Theme-Farben auf das aktive Fenster-Symbol anwenden." diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index ee7261de..bd6a676e 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -905,6 +905,10 @@ "label": "Maximum Width", "description": "Sets the maximum horizontal size of the widget. The widget will shrink to fit shorter content." }, + "use-fixed-width": { + "label": "Use Fixed Width", + "description": "When enabled, the widget will always use the maximum width instead of dynamically adjusting to content." + }, "colorize-icons": { "label": "Colorize Icons", "description": "Apply theme colors to active window icon." diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index c0e891fb..e8de1458 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -905,6 +905,10 @@ "label": "Ancho Máximo", "description": "Establece el tamaño horizontal máximo del widget. El widget se reducirá para adaptarse a contenido más corto." }, + "use-fixed-width": { + "label": "Usar Ancho Fijo", + "description": "Cuando está activado, el widget siempre usará el ancho máximo en lugar de ajustarse dinámicamente al contenido." + }, "colorize-icons": { "label": "Colorear iconos", "description": "Aplicar colores del tema al icono de la ventana activa." diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 2019a400..7ed8fb63 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -905,6 +905,10 @@ "label": "Largeur Maximale", "description": "Définit la taille horizontale maximale du widget. Le widget se rétrécira pour s'adapter à un contenu plus court." }, + "use-fixed-width": { + "label": "Utiliser une Largeur Fixe", + "description": "Lorsque activé, le widget utilisera toujours la largeur maximale au lieu de s'ajuster dynamiquement au contenu." + }, "colorize-icons": { "label": "Coloriser les icônes", "description": "Appliquer les couleurs du thème à l'icône de la fenêtre active." diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 537014f1..3d652ff7 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -905,6 +905,10 @@ "label": "Largura Máxima", "description": "Define o tamanho horizontal máximo do widget. O widget será reduzido para se adequar a conteúdo mais curto." }, + "use-fixed-width": { + "label": "Usar Largura Fixa", + "description": "Quando ativado, o widget sempre usará a largura máxima em vez de ajustar dinamicamente ao conteúdo." + }, "colorize-icons": { "label": "Colorir ícones", "description": "Aplicar cores do tema ao ícone da janela ativa." diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 72e9c542..96b71ed1 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -905,6 +905,10 @@ "label": "最大宽度", "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。" }, + "use-fixed-width": { + "label": "使用固定宽度", + "description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。" + }, "colorize-icons": { "label": "着色图标", "description": "将主题颜色应用到活动窗口图标。" diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 118e41cb..4fdf3d2b 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -38,6 +38,7 @@ Item { // Maximum widget width with user settings support readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) + readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") readonly property bool hasFocusedWindow: CompositorService.getFocusedWindow() !== null @@ -86,6 +87,11 @@ Item { // Dynamic width: adapt to content but respect maximum width setting readonly property real dynamicWidth: { + // If using fixed width mode, always use maxWidth + if (useFixedWidth) { + return maxWidth + } + // Otherwise, adapt to content if (!hasFocusedWindow) { return maxWidth } diff --git a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml index 0a4b5044..514733dd 100644 --- a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -18,6 +18,7 @@ ColumnLayout { property string valueHideMode: "hidden" // Default to 'Hide When Empty' property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth + property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons Component.onCompleted: { @@ -32,6 +33,7 @@ ColumnLayout { settings.showIcon = valueShowIcon settings.scrollingMode = valueScrollingMode settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth + settings.useFixedWidth = valueUseFixedWidth settings.colorizeIcons = valueColorizeIcons return settings } @@ -79,6 +81,14 @@ ColumnLayout { text: valueMaxWidth } + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.active-window.use-fixed-width.label") + description: I18n.tr("bar.widget-settings.active-window.use-fixed-width.description") + checked: valueUseFixedWidth + onToggled: checked => valueUseFixedWidth = checked + } + NComboBox { label: I18n.tr("bar.widget-settings.active-window.scrolling-mode.label") description: I18n.tr("bar.widget-settings.active-window.scrolling-mode.description") diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index f16bee0a..e7f2865c 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -45,6 +45,7 @@ Singleton { "scrollingMode"// "visible", "hidden", "transparent" : "hover", "maxWidth": 145, + "useFixedWidth": false, "colorizeIcons": false }, "Battery": { From 5146479225b7206bae7e1c60065e2c04cec8b56a Mon Sep 17 00:00:00 2001 From: Lysec <52084453+Ly-sec@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:31:15 +0200 Subject: [PATCH 09/76] Matugen: fix vesktop template --- Services/MatugenTemplates.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index 75c83db4..68ceec44 100644 --- a/Services/MatugenTemplates.qml +++ b/Services/MatugenTemplates.qml @@ -115,9 +115,9 @@ Singleton { "input": "pywalfox.json", "postHook": AppThemeService.colorsApplyScript + " pywalfox" }, { - "name": "discord_vesktops", + "name": "discord_vesktop", "templates": [{ - "version": "discord_vesktops", + "version": "discord_vesktop", "output": "~/.config/vesktop/themes/noctalia.theme.css" }], "input": "vesktop.css" From 9621dbb4d68a777c3ca66adfa56d2c1e12b04eab Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 10:32:18 -0400 Subject: [PATCH 10/76] Tooltip: proper size update when text changes --- Modules/Tooltip/Tooltip.qml | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/Modules/Tooltip/Tooltip.qml b/Modules/Tooltip/Tooltip.qml index 43a6850c..3f5e6ca4 100644 --- a/Modules/Tooltip/Tooltip.qml +++ b/Modules/Tooltip/Tooltip.qml @@ -321,13 +321,38 @@ PopupWindow { completeHide() } - // Update text function for binding support - function updateText(newText) { - if (visible && targetItem) { - text = newText - positionAndShow() +// Update text function for binding support +function updateText(newText) { + if (visible && targetItem) { + text = newText + + // Recalculate dimensions + const tipWidth = Math.min(tooltipText.implicitWidth + (padding * 2), maxWidth) + root.implicitWidth = tipWidth + + const tipHeight = tooltipText.implicitHeight + (padding * 2) + root.implicitHeight = tipHeight + + // Reposition if necessary + var targetGlobal = targetItem.mapToItem(null, 0, 0) + const targetWidth = targetItem.width + + // Adjust horizontal position to keep tooltip on screen if needed + const globalX = targetGlobal.x + anchorX + if (globalX < 0) { + anchorX = -targetGlobal.x + margin + } else if (globalX + tipWidth > screenWidth) { + anchorX = screenWidth - targetGlobal.x - tipWidth - margin } + + // Force anchor update + Qt.callLater(() => { + if (root.anchor && root.visible) { + root.anchor.updateAnchor() + } + }) } +} // Reset function to clean up state function reset() { From 702cd1d283e5ffec8db63b84ac70535ec0327360 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 10:33:46 -0400 Subject: [PATCH 11/76] Test commit for lefthook. --- Modules/Tooltip/Tooltip.qml | 60 ++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Modules/Tooltip/Tooltip.qml b/Modules/Tooltip/Tooltip.qml index 3f5e6ca4..00c139b6 100644 --- a/Modules/Tooltip/Tooltip.qml +++ b/Modules/Tooltip/Tooltip.qml @@ -321,38 +321,38 @@ PopupWindow { completeHide() } -// Update text function for binding support -function updateText(newText) { - if (visible && targetItem) { - text = newText - - // Recalculate dimensions - const tipWidth = Math.min(tooltipText.implicitWidth + (padding * 2), maxWidth) - root.implicitWidth = tipWidth - - const tipHeight = tooltipText.implicitHeight + (padding * 2) - root.implicitHeight = tipHeight - - // Reposition if necessary - var targetGlobal = targetItem.mapToItem(null, 0, 0) - const targetWidth = targetItem.width - - // Adjust horizontal position to keep tooltip on screen if needed - const globalX = targetGlobal.x + anchorX - if (globalX < 0) { - anchorX = -targetGlobal.x + margin - } else if (globalX + tipWidth > screenWidth) { - anchorX = screenWidth - targetGlobal.x - tipWidth - margin - } - - // Force anchor update - Qt.callLater(() => { - if (root.anchor && root.visible) { - root.anchor.updateAnchor() + // Update text function + function updateText(newText) { + if (visible && targetItem) { + text = newText + + // Recalculate dimensions + const tipWidth = Math.min(tooltipText.implicitWidth + (padding * 2), maxWidth) + root.implicitWidth = tipWidth + + const tipHeight = tooltipText.implicitHeight + (padding * 2) + root.implicitHeight = tipHeight + + // Reposition if necessary + var targetGlobal = targetItem.mapToItem(null, 0, 0) + const targetWidth = targetItem.width + + // Adjust horizontal position to keep tooltip on screen if needed + const globalX = targetGlobal.x + anchorX + if (globalX < 0) { + anchorX = -targetGlobal.x + margin + } else if (globalX + tipWidth > screenWidth) { + anchorX = screenWidth - targetGlobal.x - tipWidth - margin } - }) + + // Force anchor update + Qt.callLater(() => { + if (root.anchor && root.visible) { + root.anchor.updateAnchor() + } + }) + } } -} // Reset function to clean up state function reset() { From fae2535d00740e20ff14800b7ed980ec50d96af3 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 11:03:48 -0400 Subject: [PATCH 12/76] qmlfmt: detect array destructuring and skip file to avoid breakage. --- Bin/dev/qmlfmt.sh | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Bin/dev/qmlfmt.sh b/Bin/dev/qmlfmt.sh index 6f0fe315..8d09aa3d 100755 --- a/Bin/dev/qmlfmt.sh +++ b/Bin/dev/qmlfmt.sh @@ -10,10 +10,25 @@ command -v qmlfmt &>/dev/null || { echo "qmlfmt not found" >&2; exit 1; } format_file() { qmlfmt -e -b 360 -t 2 -i 2 -w "$1" || { echo "Failed: $1" >&2; return 1; }; } export -f format_file -mapfile -t files < <(find "${1:-.}" -name "*.qml" -type f) -[ ${#files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; } +# Find all .qml files +mapfile -t all_files < <(find "${1:-.}" -name "*.qml" -type f) +[ ${#all_files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; } -echo "Formatting ${#files[@]} files..." -printf '%s\0' "${files[@]}" | \ +echo "Scanning ${#all_files[@]} files for array destructuring..." +safe_files=() +for file in "${all_files[@]}"; do + # Checks for a comma inside brackets followed by an equals sign aka "array destructuring" + # as this ES6 syntax is not supported by qmlfmt and will result in breakage. + if grep -qE '\[.*,.*\]\s*=' "$file"; then + echo "-> Skipping (Array destructuring detected): $file" >&2 + else + safe_files+=("$file") + fi +done + +[ ${#safe_files[@]} -eq 0 ] && { echo "No safe files to format after filtering."; exit 0; } + +echo "Formatting ${#safe_files[@]} files..." +printf '%s\0' "${safe_files[@]}" | \ xargs -0 -P "${QMLFMT_JOBS:-$(nproc)}" -I {} bash -c 'format_file "$@"' _ {} \ && echo "Done" || { echo "Errors occurred" >&2; exit 1; } \ No newline at end of file From bda54677e1724054c3676da8f049c61378790b3a Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 11:19:19 -0400 Subject: [PATCH 13/76] SystemStat: fixing wrong memGb calculation. Fix #507 --- Services/SystemStatService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/SystemStatService.qml b/Services/SystemStatService.qml index e69cd112..1e89d5c2 100644 --- a/Services/SystemStatService.qml +++ b/Services/SystemStatService.qml @@ -205,7 +205,7 @@ Singleton { if (memTotal > 0) { const usageKb = memTotal - memAvailable - root.memGb = (usageKb / 1000000).toFixed(1) + root.memGb = (usageKb / 1048576).toFixed(1) // 1024*1024 = 1048576 root.memPercent = Math.round((usageKb / memTotal) * 100) } } From 6af7753f50d947e02aa688b8ee605c53ef6047ec Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 11:43:38 -0400 Subject: [PATCH 14/76] v2.18.2 --- Services/UpdateService.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index 34b27996..632a7c32 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -8,8 +8,8 @@ Singleton { id: root // Public properties - property string baseVersion: "2.18.1" - property bool isDevelopment: true + property string baseVersion: "2.18.2" + property bool isDevelopment: false property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 03ec0e9b90b1e137387b944bbe1fa57beb0abeee Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 11:44:06 -0400 Subject: [PATCH 15/76] dev version --- Services/UpdateService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index 632a7c32..41c5daf4 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -9,7 +9,7 @@ Singleton { // Public properties property string baseVersion: "2.18.2" - property bool isDevelopment: false + property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 26803f9588a7f5210bab65d29c3fe4102119a645 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Fri, 17 Oct 2025 19:00:52 +0200 Subject: [PATCH 16/76] Add qmlls config to gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e69de29b..dfbea23a 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +.qmlls.ini From fffae8d4a3b6d3b98e7637ed5ec6c195c69739cf Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Fri, 17 Oct 2025 19:01:08 +0200 Subject: [PATCH 17/76] Add Zed config to gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index dfbea23a..2d6d8923 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .qmlls.ini +.zed From f89bc3da905925485b7696f967df0c3db3174918 Mon Sep 17 00:00:00 2001 From: Tobias Pahl Date: Sat, 18 Oct 2025 10:35:31 +0200 Subject: [PATCH 18/76] fix translation --- Assets/Translations/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index aed4c10c..2de414dd 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -746,7 +746,7 @@ "label": "Widgets für Kurzbefehle" }, "sectionLeft": "Links", - "sectionRight": "Richtig" + "sectionRight": "Rechts" } }, "user-interface": { From 5fa1481780c77af3d57bdbc6778b745aacf3561a Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 14:01:27 +0200 Subject: [PATCH 19/76] README: add LionHeartP to donation list <3 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8c03cb67..a577af03 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ While all donations are greatly appreciated, they are completely voluntary. * Gohma * DiscoCevapi * PikaOS +* LionHeartP --- From 6ba3b465de493e7e255df5d986607f0eb4c2defe Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 14:08:51 +0200 Subject: [PATCH 20/76] SessionMenu: remove lock & suspend option, add toggle in general tab to decide if lockscreen is used or not --- Assets/Translations/en.json | 10 ++++++++++ Assets/settings-default.json | 3 ++- Commons/Settings.qml | 1 + Modules/SessionMenu/SessionMenu.qml | 16 +++++++--------- Modules/Settings/Tabs/GeneralTab.qml | 24 ++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index f18e6ac9..e8ee372b 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -32,6 +32,16 @@ "reset": "Reset screen corners radius" } }, + "lockscreen": { + "section": { + "label": "Lock screen", + "description": "Configure lock screen behavior." + }, + "lock-on-suspend": { + "label": "Lock on suspend", + "description": "Automatically lock the screen when suspending the system." + } + }, "fonts": { "reset-scaling": "Reset scaling", "section": { diff --git a/Assets/settings-default.json b/Assets/settings-default.json index ce680d75..27f2b735 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -65,7 +65,8 @@ "screenRadiusRatio": 1, "animationSpeed": 1, "animationDisabled": false, - "compactLockScreen": false + "compactLockScreen": false, + "lockOnSuspend": true }, "location": { "name": "Tokyo", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 1f925537..a0306c13 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -191,6 +191,7 @@ Singleton { property real animationSpeed: 1.0 property bool animationDisabled: false property bool compactLockScreen: false + property bool lockOnSuspend: true } // location diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml index 83afaf5f..558452b7 100644 --- a/Modules/SessionMenu/SessionMenu.qml +++ b/Modules/SessionMenu/SessionMenu.qml @@ -14,7 +14,7 @@ NPanel { id: root preferredWidth: 320 * Style.uiScaleRatio - preferredHeight: 360 * Style.uiScaleRatio + preferredHeight: 280 * Style.uiScaleRatio panelAnchorHorizontalCenter: true panelAnchorVerticalCenter: true panelKeyboardFocus: true @@ -31,10 +31,6 @@ NPanel { "action": "lock", "icon": "lock", "title": I18n.tr("session-menu.lock") - }, { - "action": "lockAndSuspend", - "icon": "lock-pause", - "title": I18n.tr("session-menu.lock-and-suspend") }, { "action": "suspend", "icon": "suspend", @@ -96,11 +92,13 @@ NPanel { lockScreen.active = true } break - case "lockAndSuspend": - CompositorService.lockAndSuspend() - break case "suspend": - CompositorService.suspend() + // Check if we should lock before suspending + if (Settings.data.general.lockOnSuspend) { + CompositorService.lockAndSuspend() + } else { + CompositorService.suspend() + } break case "reboot": CompositorService.reboot() diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index c4554145..0a63a7fd 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -189,4 +189,28 @@ ColumnLayout { Layout.topMargin: Style.marginXL Layout.bottomMargin: Style.marginXL } + + // Lock Screen + ColumnLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NHeader { + label: I18n.tr("settings.general.lockscreen.section.label") + description: I18n.tr("settings.general.lockscreen.section.description") + } + + NToggle { + label: I18n.tr("settings.general.lockscreen.lock-on-suspend.label") + description: I18n.tr("settings.general.lockscreen.lock-on-suspend.description") + checked: Settings.data.general.lockOnSuspend + onToggled: Settings.data.general.lockOnSuspend = checked + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL + Layout.bottomMargin: Style.marginXL + } } From 7d15736e4e0e0933e8188e08f48edaeaabb9d6f3 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 14:10:46 +0200 Subject: [PATCH 21/76] i18n: add general tab lockscreen settings to all languages --- Assets/Translations/de.json | 10 ++++++++++ Assets/Translations/es.json | 10 ++++++++++ Assets/Translations/fr.json | 10 ++++++++++ Assets/Translations/pt.json | 10 ++++++++++ Assets/Translations/zh-CN.json | 10 ++++++++++ 5 files changed, 50 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 2de414dd..f53299c2 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -32,6 +32,16 @@ "reset": "Eckenradius des Bildschirms zurücksetzen" } }, + "lockscreen": { + "section": { + "label": "Sperrbildschirm", + "description": "Sperrbildschirm-Verhalten konfigurieren." + }, + "lock-on-suspend": { + "label": "Beim Standby sperren", + "description": "Bildschirm automatisch sperren, wenn das System in den Standby-Modus wechselt." + } + }, "fonts": { "section": { "label": "Schriftarten", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index a00881fe..77bd8612 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -32,6 +32,16 @@ "reset": "Restablecer el radio de las esquinas de la pantalla" } }, + "lockscreen": { + "section": { + "label": "Pantalla de bloqueo", + "description": "Configura el comportamiento de la pantalla de bloqueo." + }, + "lock-on-suspend": { + "label": "Bloquear al suspender", + "description": "Bloquear automáticamente la pantalla al suspender el sistema." + } + }, "fonts": { "section": { "label": "Fuentes", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index ec9af8ac..f180575a 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -32,6 +32,16 @@ "reset": "Réinitialiser le rayon des coins de l'écran" } }, + "lockscreen": { + "section": { + "label": "Écran de verrouillage", + "description": "Configurer le comportement de l'écran de verrouillage." + }, + "lock-on-suspend": { + "label": "Verrouiller lors de la suspension", + "description": "Verrouiller automatiquement l'écran lors de la mise en veille du système." + } + }, "fonts": { "section": { "label": "Polices", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 02979bf1..5e2a5658 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -32,6 +32,16 @@ "reset": "Redefinir raio dos cantos da tela" } }, + "lockscreen": { + "section": { + "label": "Tela de bloqueio", + "description": "Configure o comportamento da tela de bloqueio." + }, + "lock-on-suspend": { + "label": "Bloquear ao suspender", + "description": "Bloquear automaticamente a tela ao suspender o sistema." + } + }, "fonts": { "section": { "label": "Fontes", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index a251a5d4..889266c4 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -32,6 +32,16 @@ "reset": "重置屏幕圆角半径" } }, + "lockscreen": { + "section": { + "label": "锁屏", + "description": "配置锁屏行为。" + }, + "lock-on-suspend": { + "label": "挂起时锁定", + "description": "在系统挂起时自动锁定屏幕。" + } + }, "fonts": { "reset-scaling": "恢复默认缩放", "section": { From e426180f045e8237b1325ef1332eeb49e7cbe43d Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 14:13:17 +0200 Subject: [PATCH 22/76] Dock: add exclusiveZone: 0 to fix maximize issue (niri) --- Modules/Dock/Dock.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 56a932e5..391dd928 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -244,6 +244,7 @@ Variants { WlrLayershell.namespace: "noctalia-dock-main" WlrLayershell.exclusionMode: exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore + exclusiveZone: 0 // Size to fit the dock container exactly implicitWidth: dockContainerWrapper.width From 91bb3e866fb6977936121be6165c9b68c66a77f1 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 18 Oct 2025 08:29:26 -0400 Subject: [PATCH 23/76] Dock: attempt to fix visible 1px peek zone. --- Modules/Dock/Dock.qml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 391dd928..b90e9275 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -199,15 +199,9 @@ Variants { color: Color.transparent WlrLayershell.namespace: "noctalia-dock-peek" - WlrLayershell.exclusionMode: ExclusionMode.Auto // Always exclusive - + WlrLayershell.exclusionMode: ExclusionMode.ignore implicitHeight: peekHeight - Rectangle { - anchors.fill: parent - color: barAtBottom ? Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) : Color.transparent - } - MouseArea { id: peekArea anchors.fill: parent @@ -244,7 +238,6 @@ Variants { WlrLayershell.namespace: "noctalia-dock-main" WlrLayershell.exclusionMode: exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore - exclusiveZone: 0 // Size to fit the dock container exactly implicitWidth: dockContainerWrapper.width From 22e8358c6960bcf35edf4cef48120643b0c823fb Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 18 Oct 2025 08:41:36 -0400 Subject: [PATCH 24/76] Dock: fixed 1px height peek zone --- Modules/Dock/Dock.qml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index b90e9275..9bdf82cc 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -199,7 +199,7 @@ Variants { color: Color.transparent WlrLayershell.namespace: "noctalia-dock-peek" - WlrLayershell.exclusionMode: ExclusionMode.ignore + WlrLayershell.exclusionMode: ExclusionMode.Ignore implicitHeight: peekHeight MouseArea { @@ -255,12 +255,6 @@ Variants { } } - // Rectangle { - // anchors.fill: parent - // color: "#000FF0" - // z: -1 - // } - // Wrapper item for scale/opacity animations Item { id: dockContainerWrapper From fcb5510e94c56557bc585200d7c28cee06d05116 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 14:42:35 +0200 Subject: [PATCH 25/76] SessionMenu: better layout --- Modules/SessionMenu/SessionMenu.qml | 121 ++++++++++++++-------------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml index 558452b7..b4a2bc15 100644 --- a/Modules/SessionMenu/SessionMenu.qml +++ b/Modules/SessionMenu/SessionMenu.qml @@ -13,8 +13,8 @@ import qs.Widgets NPanel { id: root - preferredWidth: 320 * Style.uiScaleRatio - preferredHeight: 280 * Style.uiScaleRatio + preferredWidth: 400 * Style.uiScaleRatio + preferredHeight: 340 * Style.uiScaleRatio panelAnchorHorizontalCenter: true panelAnchorVerticalCenter: true panelKeyboardFocus: true @@ -261,74 +261,77 @@ NPanel { root.activate() } - ColumnLayout { + NBox { anchors.fill: parent - anchors.topMargin: Style.marginM - anchors.leftMargin: Style.marginM - anchors.rightMargin: Style.marginM - anchors.bottomMargin: Style.marginS - spacing: Style.marginXS + anchors.margins: Style.marginL - // Header with title and close button - RowLayout { - Layout.fillWidth: true - Layout.preferredHeight: Style.baseWidgetSize * 0.6 + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginL - NText { - text: timerActive ? I18n.tr("session-menu.action-in-seconds", { - "action": pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1), - "seconds": Math.ceil(timeRemaining / 1000) - }) : I18n.tr("session-menu.title") - font.weight: Style.fontWeightBold - pointSize: Style.fontSizeM - color: timerActive ? Color.mPrimary : Color.mOnSurface - Layout.alignment: Qt.AlignVCenter - verticalAlignment: Text.AlignVCenter - } - - Item { + // Header with title and close button + RowLayout { Layout.fillWidth: true - } + Layout.preferredHeight: Style.baseWidgetSize * 0.6 - NIconButton { - icon: timerActive ? "stop" : "close" - tooltipText: timerActive ? I18n.tr("tooltips.cancel-timer") : I18n.tr("tooltips.close") - Layout.alignment: Qt.AlignVCenter - colorBg: timerActive ? Qt.alpha(Color.mError, 0.08) : Color.transparent - colorFg: timerActive ? Color.mError : Color.mOnSurface - onClicked: { - if (timerActive) { - cancelTimer() - } else { - cancelTimer() - root.close() + NText { + text: timerActive ? I18n.tr("session-menu.action-in-seconds", { + "action": pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1), + "seconds": Math.ceil(timeRemaining / 1000) + }) : I18n.tr("session-menu.title") + font.weight: Style.fontWeightBold + pointSize: Style.fontSizeM + color: timerActive ? Color.mPrimary : Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + verticalAlignment: Text.AlignVCenter + } + + Item { + Layout.fillWidth: true + } + + NIconButton { + icon: timerActive ? "stop" : "close" + tooltipText: timerActive ? I18n.tr("tooltips.cancel-timer") : I18n.tr("tooltips.close") + Layout.alignment: Qt.AlignVCenter + baseSize: Style.baseWidgetSize * 0.7 + colorBg: timerActive ? Qt.alpha(Color.mError, 0.08) : Color.transparent + colorFg: timerActive ? Color.mError : Color.mOnSurface + onClicked: { + if (timerActive) { + cancelTimer() + } else { + cancelTimer() + root.close() + } } } } - } - NDivider { - Layout.fillWidth: true - } + NDivider { + Layout.fillWidth: true + } - // Power options - ColumnLayout { - Layout.fillWidth: true - spacing: Style.marginS + // Power options + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS - Repeater { - model: powerOptions - delegate: PowerButton { - Layout.fillWidth: true - icon: modelData.icon - title: modelData.title - isShutdown: modelData.isShutdown || false - isSelected: index === selectedIndex - onClicked: { - selectedIndex = index - startTimer(modelData.action) + Repeater { + model: powerOptions + delegate: PowerButton { + Layout.fillWidth: true + icon: modelData.icon + title: modelData.title + isShutdown: modelData.isShutdown || false + isSelected: index === selectedIndex + onClicked: { + selectedIndex = index + startTimer(modelData.action) + } + pending: timerActive && pendingAction === modelData.action } - pending: timerActive && pendingAction === modelData.action } } } @@ -347,7 +350,7 @@ NPanel { signal clicked - height: Style.baseWidgetSize * 1.2 * Style.uiScaleRatio + height: Style.baseWidgetSize * 1.3 * Style.uiScaleRatio radius: Style.radiusS color: { if (pending) { From 94f247eefc17e9ddd130951dd45f483d9081b6dc Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 18 Oct 2025 09:26:15 -0400 Subject: [PATCH 26/76] SetupWizard: wait for proper detection of the OS before opening the wizard. --- Services/DistroService.qml | 7 +++++-- shell.qml | 30 +++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Services/DistroService.qml b/Services/DistroService.qml index 6f5a8944..568bc2f0 100644 --- a/Services/DistroService.qml +++ b/Services/DistroService.qml @@ -12,6 +12,7 @@ Singleton { property string osPretty: "" property string osLogo: "" property bool isNixOS: false + property bool isReady: false // Internal helpers function buildCandidates(name) { @@ -79,8 +80,10 @@ Singleton { const osId = (val("ID") || "").toLowerCase() root.isNixOS = osId === "nixos" || (root.osPretty || "").toLowerCase().includes("nixos") const logoName = val("LOGO") - if (logoName) - resolveLogo(logoName) + if (logoName) { + resolveLogo(logoName) + } + root.isReady = true } catch (e) { Logger.w("DistroService", "failed to read os-release", e) } diff --git a/shell.qml b/shell.qml index 26022135..e3a41352 100644 --- a/shell.qml +++ b/shell.qml @@ -190,16 +190,28 @@ ShellRoot { function onSettingsLoaded() { // Only open the setup wizard for new users if (!Settings.data.setupCompleted) { - if (DistroService && DistroService.isNixOS) { - Settings.data.setupCompleted = true - return - } - if (Settings.data.settingsVersion >= Settings.settingsVersion) { - setupWizardLoader.active = true - } else { - Settings.data.setupCompleted = true - } + checkSetupWizard() } } } + + function checkSetupWizard() { + // Wait for distro service + if (!DistroService.isReady) { + Qt.callLater(checkSetupWizard) + return + } + + // No setup wizard on NixOS + if (DistroService.isNixOS) { + Settings.data.setupCompleted = true + return + } + + if (Settings.data.settingsVersion >= Settings.settingsVersion) { + setupWizardLoader.active = true + } else { + Settings.data.setupCompleted = true + } + } } From b7f96e3abd95a716f32433ab27bf4529de651db4 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 15:37:00 +0200 Subject: [PATCH 27/76] Matugen: user defined templates can now use predefined color schemes, changed path to config (~/.config/noctalia/user-templates.toml) --- Assets/Translations/de.json | 2 +- Assets/Translations/en.json | 2 +- Assets/Translations/es.json | 2 +- Assets/Translations/fr.json | 2 +- Assets/Translations/pt.json | 2 +- Assets/Translations/zh-CN.json | 2 +- Modules/Settings/Tabs/ColorSchemeTab.qml | 3 ++ Services/AppThemeService.qml | 41 +++++++++++++++- Services/MatugenTemplates.qml | 62 ++++++++++++++++++++++++ 9 files changed, 110 insertions(+), 8 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f53299c2..8f04dee0 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -559,7 +559,7 @@ "description": "Zusätzliche Konfigurationsoptionen.", "user-templates": { "label": "Benutzer-Vorlagen", - "description": "Benutzerdefinierte Matugen-Konfiguration aus ~/.config/matugen/config.toml aktivieren" + "description": "Benutzerdefinierte Matugen-Konfiguration aktivieren. Eine Vorlagendatei wird beim ersten Aktivieren unter ~/.config/noctalia/user-templates.toml erstellt" } }, "section": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index e8ee372b..3f3b9683 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -563,7 +563,7 @@ "description": "Additional configuration options.", "user-templates": { "label": "User templates", - "description": "Enable user-defined Matugen config from ~/.config/matugen/config.toml" + "description": "Enable user-defined Matugen config. A template file will be created at ~/.config/noctalia/user-templates.toml on first enable" } } } diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 77bd8612..3193fff1 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -559,7 +559,7 @@ "description": "Opciones de configuración adicionales.", "user-templates": { "label": "Plantillas de usuario", - "description": "Habilitar configuración de Matugen definida por el usuario desde ~/.config/matugen/config.toml" + "description": "Habilitar configuración de Matugen definida por el usuario. Se creará un archivo de plantilla en ~/.config/noctalia/user-templates.toml al activar por primera vez" } }, "section": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index f180575a..aee01c11 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -559,7 +559,7 @@ "description": "Options de configuration supplémentaires.", "user-templates": { "label": "Modèles utilisateur", - "description": "Activer la configuration Matugen définie par l'utilisateur depuis ~/.config/matugen/config.toml" + "description": "Activer la configuration Matugen définie par l'utilisateur. Un fichier modèle sera créé dans ~/.config/noctalia/user-templates.toml lors de la première activation" } }, "section": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 5e2a5658..0db4489b 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -521,7 +521,7 @@ "description": "Opções de configuração adicionais.", "user-templates": { "label": "Modelos do usuário", - "description": "Ativa a configuração do Matugen definida pelo usuário em ~/.config/matugen/config.toml" + "description": "Ativa a configuração do Matugen definida pelo usuário. Um arquivo de modelo será criado em ~/.config/noctalia/user-templates.toml na primeira ativação" } }, "section": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 889266c4..ddfd0059 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -559,7 +559,7 @@ "description": "其他配置选项。", "user-templates": { "label": "用户模板", - "description": "启用来自 ~/.config/matugen/config.toml 的用户定义 Matugen 配置" + "description": "启用用户定义的 Matugen 配置。首次启用时将在 ~/.config/noctalia/user-templates.toml 创建模板文件" } }, "section": { diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index ddf48db1..5909040f 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -590,6 +590,9 @@ ColumnLayout { checked: Settings.data.templates.enableUserTemplates onToggled: checked => { Settings.data.templates.enableUserTemplates = checked + if (checked) { + MatugenTemplates.writeUserTemplatesToml() + } AppThemeService.generate() } } diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index dd24327a..fc1f01d0 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -150,7 +150,10 @@ Singleton { const colors = schemeData[mode] const matugenColors = generatePalette(colors.mPrimary, colors.mSecondary, colors.mTertiary, colors.mError, colors.mSurface, isDarkMode) - const script = processAllTemplates(matugenColors, mode) + let script = processAllTemplates(matugenColors, mode) + + // Add user templates if enabled + script += buildUserTemplateCommandForPredefined(schemeData, mode) generateProcess.command = ["bash", "-lc", script] generateProcess.running = true @@ -335,8 +338,42 @@ Singleton { return script } + function buildUserTemplateCommandForPredefined(schemeData, mode) { + if (!Settings.data.templates.enableUserTemplates) { + return "" + } + + const userConfigPath = getUserConfigPath() + const isDarkMode = Settings.data.colorSchemes.darkMode + const colors = schemeData[mode] + + // Generate the matugen palette JSON + const matugenColors = generatePalette(colors.mPrimary, colors.mSecondary, colors.mTertiary, colors.mError, colors.mSurface, isDarkMode) + + // Create a temporary JSON file with the color palette + const tempJsonPath = Settings.cacheDir + "predefined-colors.json" + const homeDir = Quickshell.env("HOME") + const tempJsonPathEsc = tempJsonPath.replace(/'/g, "'\\''") + + let script = "\n# Execute user templates with predefined scheme colors\n" + script += `if [ -f '${userConfigPath}' ]; then\n` + + // Write the color palette to a temp JSON file + script += ` cat > '${tempJsonPathEsc}' << 'EOF'\n` + script += JSON.stringify({ + "colors": matugenColors + }, null, 2) + "\n" + script += "EOF\n" + + // Use matugen json subcommand with the color palette + script += ` matugen json '${tempJsonPathEsc}' --config '${userConfigPath}' --mode ${mode}\n` + script += "fi" + + return script + } + function getUserConfigPath() { - return (Quickshell.env("HOME") + "/.config/matugen/config.toml").replace(/'/g, "'\\''") + return (Settings.configDir + "user-templates.toml").replace(/'/g, "'\\''") } // -------------------------------------------------------------------------------- diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index 68ceec44..633f7fea 100644 --- a/Services/MatugenTemplates.qml +++ b/Services/MatugenTemplates.qml @@ -2,6 +2,7 @@ pragma Singleton import QtQuick import Quickshell +import Quickshell.Io import qs.Commons // Central place to define which templates we generate and where they write. @@ -32,6 +33,51 @@ Singleton { return "" } + // Build user templates TOML for ~/.config/noctalia/user-templates.toml + function buildUserTemplatesToml() { + var lines = [] + lines.push("[config]") + lines.push("") + lines.push("# User-defined templates") + lines.push("# Add your custom templates below") + lines.push("# Example:") + lines.push("# [templates.myapp]") + lines.push("# input_path = \"~/.config/noctalia/templates/myapp.css\"") + lines.push("# output_path = \"~/.config/myapp/theme.css\"") + lines.push("# post_hook = \"myapp --reload-theme\"") + lines.push("") + lines.push("# Remove this section and add your own templates") + lines.push("[templates.placeholder]") + lines.push("input_path = \"" + Quickshell.shellDir + "/Assets/MatugenTemplates/noctalia.json\"") + lines.push("output_path = \"" + Settings.cacheDir + "placeholder.json\"") + lines.push("post_hook = \"echo 'User templates enabled - replace this placeholder with your own templates'\"") + lines.push("") + + return lines.join("\n") + "\n" + } + + // Write user templates TOML to ~/.config/noctalia/user-templates.toml + function writeUserTemplatesToml() { + var userConfigPath = Settings.configDir + "user-templates.toml" + + // Check if file already exists + fileCheckProcess.command = ["test", "-f", userConfigPath] + fileCheckProcess.running = true + } + + function doWriteUserTemplatesToml() { + var userConfigPath = Settings.configDir + "user-templates.toml" + var configContent = buildUserTemplatesToml() + + // Ensure directory exists (should already exist but just in case) + Quickshell.execDetached(["mkdir", "-p", Settings.configDir]) + + // Write the config file + Quickshell.execDetached(["sh", "-c", `echo '${configContent.replace(/'/g, "'\\''")}' > '${userConfigPath}'`]) + + Logger.i("MatugenTemplates", "User templates config written to:", userConfigPath) + } + // -------------------------------- function addWallpaperBasedTemplates(lines, mode) { // Noctalia colors @@ -208,4 +254,20 @@ Singleton { } return clients } + + // Process for checking if user templates file exists + Process { + id: fileCheckProcess + running: false + + onExited: function (exitCode) { + if (exitCode === 0) { + // File exists, skip creation + Logger.d("MatugenTemplates", "User templates config already exists, skipping creation") + } else { + // File doesn't exist, create it + doWriteUserTemplatesToml() + } + } + } } From e564ec2a7cf234c491eaa94adf1109ab2a012cf3 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 21:54:41 +0800 Subject: [PATCH 28/76] feat: Add adjustable dock size setting --- Assets/Translations/en.json | 6 +++++- Assets/settings-default.json | 3 ++- Commons/Settings.qml | 1 + Modules/Dock/Dock.qml | 2 +- Modules/Settings/Tabs/DockTab.qml | 18 ++++++++++++++++++ 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index e8ee372b..8fb27cb5 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -269,7 +269,11 @@ }, "floating-distance": { "label": "Dock floating distance", - "description": "Adjust the floating distance from the screen edge." + "description": "Set the distance between the dock and the edge of the screen." + }, + "icon-size": { + "label": "Dock size", + "description": "Adjust the overall size of the dock." }, "colorize-icons": { "label": "Colorize Icons", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 27f2b735..a62c77eb 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -172,7 +172,8 @@ "onlySameOutput": true, "monitors": [], "pinnedApps": [], - "colorizeIcons": false + "colorizeIcons": false, + "size": 1 }, "network": { "wifiEnabled": true diff --git a/Commons/Settings.qml b/Commons/Settings.qml index a0306c13..44f30145 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -293,6 +293,7 @@ Singleton { property string displayMode: "always_visible" // "always_visible", "auto_hide", "exclusive" property real backgroundOpacity: 1.0 property real floatingRatio: 1.0 + property real size: 1 property bool onlySameOutput: true property list monitors: [] // Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop") diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 9bdf82cc..8e60bbf3 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -63,7 +63,7 @@ Variants { readonly property int hideAnimationDuration: Style.animationFast readonly property int showAnimationDuration: Style.animationFast readonly property int peekHeight: 1 - readonly property int iconSize: 36 + readonly property int iconSize: Math.round(12 + 24 * (Settings.data.dock.size ?? 1)) readonly property int floatingMargin: Settings.data.dock.floatingRatio * Style.marginL // Bar detection and positioning properties diff --git a/Modules/Settings/Tabs/DockTab.qml b/Modules/Settings/Tabs/DockTab.qml index 3203446e..c7f1d258 100644 --- a/Modules/Settings/Tabs/DockTab.qml +++ b/Modules/Settings/Tabs/DockTab.qml @@ -87,6 +87,24 @@ ColumnLayout { } } + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + NLabel { + label: I18n.tr("settings.dock.appearance.icon-size.label") + description: I18n.tr("settings.dock.appearance.icon-size.description") + } + NValueSlider { + Layout.fillWidth: true + from: 0 + to: 2 + stepSize: 0.01 + value: Settings.data.dock.size + onMoved: value => Settings.data.dock.size = value + text: Math.floor(Settings.data.dock.size * 100) + "%" + } + } + NToggle { label: I18n.tr("settings.dock.monitors.only-same-output.label") description: I18n.tr("settings.dock.monitors.only-same-output.description") From ca9b21f695772813f545270ddc5d107813908cb9 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 21:59:46 +0800 Subject: [PATCH 29/76] feat(i18n): Add dock size translation for German --- Assets/Translations/de.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f53299c2..e291fcfd 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -271,6 +271,10 @@ "label": "Dock-Schwebeabstand", "description": "Schwebeabstand vom Bildschirmrand anpassen." }, + "icon-size": { + "label": "Dock-Größe", + "description": "Gesamtgröße des Docks anpassen." + }, "colorize-icons": { "label": "Symbole einfärben", "description": "Theme-Farben auf Dock-App-Symbole anwenden (nur nicht fokussierte Apps)." From b6fe65e57e750dfed3613c236b8cd2975b24e7a3 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:00:07 +0800 Subject: [PATCH 30/76] feat(i18n): Add dock size translation for Spanish --- Assets/Translations/es.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 77bd8612..3f33f17d 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -271,6 +271,10 @@ "label": "Distancia de flotación del dock", "description": "Ajusta la distancia de flotación desde el borde de la pantalla." }, + "icon-size": { + "label": "Tamaño del Dock", + "description": "Ajusta el tamaño general del Dock." + }, "colorize-icons": { "label": "Colorear iconos", "description": "Aplicar colores del tema a los iconos de aplicaciones del dock (solo aplicaciones no enfocadas)." From 5e67d33a641574eaf52a6e18942cfe7558b75447 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:00:30 +0800 Subject: [PATCH 31/76] feat(i18n): Add dock size translation for French --- Assets/Translations/fr.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index f180575a..21d73cd4 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -271,6 +271,10 @@ "label": "Distance de flottaison du dock", "description": "Ajustez la distance de flottaison par rapport au bord de l'écran." }, + "icon-size": { + "label": "Taille du Dock", + "description": "Ajuster la taille globale du Dock." + }, "colorize-icons": { "label": "Coloriser les icônes", "description": "Appliquer les couleurs du thème aux icônes d'applications du dock (applications non focalisées uniquement)." From 814165eb38de72cf86d0adddadab9c13357b64b5 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:01:07 +0800 Subject: [PATCH 32/76] feat(i18n): Add dock size translation for Portuguese --- Assets/Translations/pt.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 5e2a5658..d696c5ce 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -271,6 +271,10 @@ "label": "Distância de flutuação da dock", "description": "Ajuste a distância de flutuação da borda da tela." }, + "icon-size": { + "label": "Tamanho do Dock", + "description": "Ajustar o tamanho geral do Dock." + }, "colorize-icons": { "label": "Colorir ícones", "description": "Aplicar cores do tema aos ícones de aplicativos da dock (apenas aplicativos não focados)." From 3556d76dc591a3ff06dcb66257a2eb1fea6c5df4 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:01:48 +0800 Subject: [PATCH 33/76] feat(i18n): Add dock size translation for Simplified Chinese --- Assets/Translations/zh-CN.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 889266c4..b8b4bf7e 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -271,6 +271,10 @@ "label": "Dock 浮动距离", "description": "调整距离屏幕边缘的浮动距离。" }, + "icon-size": { + "label": "Dock 大小", + "description": "调整 Dock 的整体大小。" + }, "colorize-icons": { "label": "着色图标", "description": "将主题颜色应用到 Dock 应用图标(仅限非聚焦应用)。" From e85f4894297ac328105d332a34e0b200ead7263c Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:49:36 +0800 Subject: [PATCH 34/76] feat: Implement language selection feature --- Assets/Translations/en.json | 12 ++++++++++- Assets/settings-default.json | 5 +++-- Commons/I18n.qml | 7 +++++++ Commons/Settings.qml | 1 + Modules/Settings/Tabs/GeneralTab.qml | 31 +++++++++++++++++++++++++++- 5 files changed, 52 insertions(+), 4 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index e8ee372b..5c0b9e5d 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -68,6 +68,16 @@ "description": "Increase or decrease the size of the monospaced text." } } + }, + "language": { + "section": { + "label": "Language", + "description": "Choose your preferred language for the application." + }, + "select": { + "label": "Application Language", + "description": "Select the language used in the application's interface." + } } }, "audio": { @@ -1197,7 +1207,7 @@ "scan-again": "Scan again" } }, - + "tooltips": { "refresh": "Refresh", "close": "Close", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 27f2b735..850fc6f8 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -66,7 +66,8 @@ "animationSpeed": 1, "animationDisabled": false, "compactLockScreen": false, - "lockOnSuspend": true + "lockOnSuspend": true, + "language": "en" }, "location": { "name": "Tokyo", @@ -255,4 +256,4 @@ "battery": { "chargingMode": 0 } -} \ No newline at end of file +} diff --git a/Commons/I18n.qml b/Commons/I18n.qml index 6f49f097..fe2ab0a6 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -168,6 +168,13 @@ Singleton { } } + // Check for user-defined language setting + if (Settings.data.general.language !== "" && availableLanguages.includes(Settings.data.general.language)) { + Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`) + setLanguage(Settings.data.general.language) + return + } + // Detect user's favorite locale - languages for (var i = 0; i < Qt.locale().uiLanguages.length; i++) { const fullUserLang = Qt.locale().uiLanguages[i] diff --git a/Commons/Settings.qml b/Commons/Settings.qml index a0306c13..6799dc8e 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -192,6 +192,7 @@ Singleton { property bool animationDisabled: false property bool compactLockScreen: false property bool lockOnSuspend: true + property string language: "en" } // location diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index 0a63a7fd..3bd5de54 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -190,7 +190,36 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL } - // Lock Screen + // Language selection + ColumnLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NHeader { + label: I18n.tr("settings.general.language.section.label") + description: I18n.tr("settings.general.language.section.description") + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("settings.general.language.select.label") + description: I18n.tr("settings.general.language.select.description") + model: I18n.availableLanguages.map(function(langCode) { + return { "key": langCode, "name": langCode } + }) + currentKey: Settings.data.general.language !== "" ? Settings.data.general.language : I18n.langCode + onSelected: key => { + Settings.data.general.language = key + I18n.setLanguage(key) + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL + Layout.bottomMargin: Style.marginXL + } ColumnLayout { spacing: Style.marginL Layout.fillWidth: true From 677cd373aca8979c5019ecd22835f231b49055dc Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:52:38 +0800 Subject: [PATCH 35/76] feat(i18n): Add language selection translation for German --- Assets/Translations/de.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f53299c2..5cffaab1 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -68,6 +68,16 @@ } }, "reset-scaling": "Skalierung zurücksetzen" + }, + "language": { + "section": { + "label": "Sprache", + "description": "Wählen Sie Ihre bevorzugte Sprache für die Anwendung." + }, + "select": { + "label": "Anwendungssprache", + "description": "Wählen Sie die in der Anwendungsoberfläche verwendete Sprache." + } } }, "audio": { From 3e7386b344ea66ff5bcb1b4b9c33bbc0de604f92 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:53:48 +0800 Subject: [PATCH 36/76] feat(i18n): Add language selection translation for Spanish --- Assets/Translations/es.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 77bd8612..7df19467 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -68,6 +68,16 @@ } }, "reset-scaling": "Restablecer la escala" + }, + "language": { + "section": { + "label": "Idioma", + "description": "Elige tu idioma preferido para la aplicación." + }, + "select": { + "label": "Idioma de la aplicación", + "description": "Selecciona el idioma utilizado en la interfaz de la aplicación." + } } }, "audio": { From a594a03e584da26a7b6c8a1b5213f12cf450a729 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:55:18 +0800 Subject: [PATCH 37/76] feat(i18n): Add language selection translation for French --- Assets/Translations/fr.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index f180575a..a4ab5fe1 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -68,6 +68,16 @@ } }, "reset-scaling": "Réinitialiser l'échelle" + }, + "language": { + "section": { + "label": "Langue", + "description": "Choisissez votre langue préférée pour l'application." + }, + "select": { + "label": "Langue de l'application", + "description": "Sélectionnez la langue utilisée dans l'interface de l'application." + } } }, "audio": { From 57fe2c1a21f8568f70535163528da13b0e379df5 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:56:17 +0800 Subject: [PATCH 38/76] feat(i18n): Add language selection translation for Portuguese --- Assets/Translations/pt.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 5e2a5658..16f24122 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -68,6 +68,16 @@ } }, "reset-scaling": "Redefinir escala" + }, + "language": { + "section": { + "label": "Idioma", + "description": "Escolha o seu idioma preferido para a aplicação." + }, + "select": { + "label": "Idioma da aplicação", + "description": "Selecione o idioma usado na interface da aplicação." + } } }, "audio": { From 5cffc848033dae2e7561ac93c3096b7c3036e829 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:57:19 +0800 Subject: [PATCH 39/76] feat(i18n): Add language selection translation for Simplified Chinese --- Assets/Translations/zh-CN.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 889266c4..c9532210 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -68,6 +68,16 @@ "description": "增大或减小等宽文本的尺寸" } } + }, + "language": { + "section": { + "label": "语言", + "description": "选择您偏好的应用程序语言。" + }, + "select": { + "label": "应用程序语言", + "description": "选择应用程序界面中使用的语言。" + } } }, "audio": { From ec329f3a3a81be4526f1225ffd358824ad44360f Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 18 Oct 2025 12:01:23 -0400 Subject: [PATCH 40/76] MediaMini+ActiveWindo: fix warning when no screen after suspend --- Modules/Bar/Widgets/ActiveWindow.qml | 14 +++++++------- Modules/Bar/Widgets/MediaMini.qml | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 4fdf3d2b..2058bf1a 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -35,9 +35,9 @@ Item { readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon readonly property string hideMode: (widgetSettings.hideMode !== undefined) ? widgetSettings.hideMode : widgetMetadata.hideMode readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : (widgetMetadata.scrollingMode !== undefined ? widgetMetadata.scrollingMode : "hover") - + // Maximum widget width with user settings support - readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) + readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen ? screen.width * 0.06 : 0) readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") @@ -66,22 +66,22 @@ Item { // Calculate the actual content width based on visible elements var contentWidth = 0 var margins = Style.marginS * scaling * 2 // Left and right margins - + // Icon width (if visible) if (showIcon) { contentWidth += 18 * scaling contentWidth += Style.marginS * scaling // Spacing after icon } - + // Text width (use the measured width) contentWidth += fullTitleMetrics.contentWidth - + // Additional small margin for text contentWidth += Style.marginXXS * 2 - + // Add container margins contentWidth += margins - + return Math.ceil(contentWidth) } diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 38a56a34..9831626e 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -39,7 +39,7 @@ Item { readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : widgetMetadata.scrollingMode // Maximum widget width with user settings support - readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) + readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen ? screen.width * 0.06 : 0) readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth readonly property bool hasActivePlayer: MediaService.currentPlayer !== null @@ -85,7 +85,7 @@ Item { // Calculate the actual content width based on visible elements var contentWidth = 0 var margins = Style.marginS * scaling * 2 // Left and right margins - + // Icon or album art width if (!hasActivePlayer || !showAlbumArt) { // Icon width @@ -94,19 +94,19 @@ Item { // Album art width contentWidth += 21 * scaling } - + // Spacing between icon/art and text contentWidth += Style.marginS * scaling - + // Text width (use the measured width) contentWidth += fullTitleMetrics.contentWidth - + // Additional small margin for text contentWidth += Style.marginXXS * 2 - + // Add container margins contentWidth += margins - + return Math.ceil(contentWidth) } From 3249febb0f3c444b1253615be85ba92323791e23 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 19:04:20 +0200 Subject: [PATCH 41/76] Matugen: fix hex_stripped for predefined color schemes NSearchableComboBox: fix warning --- Services/AppThemeService.qml | 10 +++++++++- Widgets/NSearchableComboBox.qml | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index fc1f01d0..8b6a8b38 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -162,7 +162,8 @@ Singleton { function generatePalette(primaryColor, secondaryColor, tertiaryColor, errorColor, backgroundColor, outlineColor, isDarkMode) { const c = hex => ({ "default": { - "hex": hex + "hex": hex, + "hex_stripped": hex.replace(/^#/, "") } }) @@ -383,6 +384,13 @@ Singleton { id: generateProcess workingDirectory: Quickshell.shellDir running: false + stdout: StdioCollector { + onStreamFinished: { + if (this.text) { + Logger.i("AppThemeService", "GenerateProcess stdout:", this.text) + } + } + } stderr: StdioCollector { onStreamFinished: { if (this.text) { diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index ffb4f4cc..a9a578f4 100644 --- a/Widgets/NSearchableComboBox.qml +++ b/Widgets/NSearchableComboBox.qml @@ -233,7 +233,7 @@ RowLayout { Layout.alignment: Qt.AlignRight Repeater { - model: badgeLocations + model: typeof badgeLocations !== 'undefined' ? badgeLocations : [] delegate: NBox { width: Style.baseWidgetSize * 0.7 From 80443bb74ef40f00aefa340f81afeeab679495c1 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:25:03 +0800 Subject: [PATCH 42/76] fix(languge): language defaults to an empty string --- Assets/settings-default.json | 2 +- Commons/Settings.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 850fc6f8..04aaab90 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -67,7 +67,7 @@ "animationDisabled": false, "compactLockScreen": false, "lockOnSuspend": true, - "language": "en" + "language": "" }, "location": { "name": "Tokyo", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 6799dc8e..937ab041 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -192,7 +192,7 @@ Singleton { property bool animationDisabled: false property bool compactLockScreen: false property bool lockOnSuspend: true - property string language: "en" + property string language: "" } // location From 63bd97e76f952d302dd3b38e3acc06fd8d648ff1 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:29:39 +0800 Subject: [PATCH 43/76] feat(i18n): Implement and refine language selection feature Introduces a language selection option in settings, allowing users to manually choose an application language or revert to automatic system locale detection. This includes UI updates, settings integration, and improved language detection logic. --- Assets/Translations/en.json | 3 +- Commons/I18n.qml | 55 ++++++++++++---------------- Modules/Settings/Tabs/GeneralTab.qml | 14 +++++-- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 5c0b9e5d..783dc34b 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -76,7 +76,8 @@ }, "select": { "label": "Application Language", - "description": "Select the language used in the application's interface." + "description": "Select the language used in the application's interface.", + "auto-detect": "Automatic" } } }, diff --git a/Commons/I18n.qml b/Commons/I18n.qml index fe2ab0a6..4ec5e86f 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -7,10 +7,11 @@ import qs.Commons Singleton { id: root - property string debugForceLanguage: "" + property bool isLoaded: false property string langCode: "" + property string systemDetectedLangCode: "" property var availableLanguages: [] property var translations: ({}) property var fallbackTranslations: ({}) @@ -158,48 +159,40 @@ Singleton { return } - if (Settings.isDebug && debugForceLanguage !== "") { - Logger.d("I18n", `Debug mode: forcing language to "${debugForceLanguage}"`) - if (availableLanguages.includes(debugForceLanguage)) { - setLanguage(debugForceLanguage) - return - } else { - Logger.w("I18n", `Debug language "${debugForceLanguage}" not available in [${availableLanguages.join(', ')}]`) - } - } + var detectedLang = "" - // Check for user-defined language setting - if (Settings.data.general.language !== "" && availableLanguages.includes(Settings.data.general.language)) { - Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`) - setLanguage(Settings.data.general.language) - return - } - - // Detect user's favorite locale - languages + // First, determine the system's preferred language for (var i = 0; i < Qt.locale().uiLanguages.length; i++) { const fullUserLang = Qt.locale().uiLanguages[i] - // Try full code match (such as zh CN, en US) if (availableLanguages.includes(fullUserLang)) { - Logger.d("I18n", `Exact match found: "${fullUserLang}"`) - setLanguage(fullUserLang) - return + detectedLang = fullUserLang + break } - // If full code match fails, try short code matching (such as zh, en) const shortUserLang = fullUserLang.substring(0, 2) if (availableLanguages.includes(shortUserLang)) { - Logger.d("I18n", `Short code match found: "${shortUserLang}" from "${fullUserLang}"`) - setLanguage(shortUserLang) - return + detectedLang = shortUserLang + break } - - Logger.d("I18n", `No match for system language: "${fullUserLang}"`) } - // Fallback to first available language (preferably "en" if available) - const fallbackLang = availableLanguages.includes("en") ? "en" : availableLanguages[0] - setLanguage(fallbackLang) + // If no system language is found among available languages, fallback + if (detectedLang === "") { + detectedLang = availableLanguages.includes("en") ? "en" : availableLanguages[0] + } + + root.systemDetectedLangCode = detectedLang + Logger.d("I18n", `System detected language: "${root.systemDetectedLangCode}"`) + + // Now, apply the language: user-defined, then system-detected + if (Settings.data.general.language !== "" && availableLanguages.includes(Settings.data.general.language)) { + Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`) + setLanguage(Settings.data.general.language) + } else { + Logger.d("I18n", `No user-defined language, using system detected: "${root.systemDetectedLangCode}"`) + setLanguage(root.systemDetectedLangCode) + } } // ------------------------------------------- diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index 3bd5de54..40b5276d 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -204,13 +204,19 @@ ColumnLayout { Layout.fillWidth: true label: I18n.tr("settings.general.language.select.label") description: I18n.tr("settings.general.language.select.description") - model: I18n.availableLanguages.map(function(langCode) { + model: [ + { "key": "", "name": I18n.tr("settings.general.language.select.auto-detect") + " (" + I18n.systemDetectedLangCode + ")" } + ].concat(I18n.availableLanguages.map(function(langCode) { return { "key": langCode, "name": langCode } - }) - currentKey: Settings.data.general.language !== "" ? Settings.data.general.language : I18n.langCode + })) + currentKey: Settings.data.general.language onSelected: key => { Settings.data.general.language = key - I18n.setLanguage(key) + if (key === "") { + I18n.detectLanguage() // Re-detect system language if "Automatic" is selected + } else { + I18n.setLanguage(key) // Set specific language + } } } } From 66de0222b098d41e51318cd7e3834a6087fa57cf Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:39:23 +0800 Subject: [PATCH 44/76] feat(i18n): Add German translation for language auto-detect --- Assets/Translations/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 5cffaab1..f5bc5c5c 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -76,7 +76,8 @@ }, "select": { "label": "Anwendungssprache", - "description": "Wählen Sie die in der Anwendungsoberfläche verwendete Sprache." + "description": "Wählen Sie die in der Anwendungsoberfläche verwendete Sprache.", + "auto-detect": "Automatisch" } } }, From e5b46c2e2b87844bffe81c3489445820a60b751f Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:40:59 +0800 Subject: [PATCH 45/76] feat(i18n): Add Spanish translation for language auto-detect --- Assets/Translations/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 7df19467..13f4a41e 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -76,7 +76,8 @@ }, "select": { "label": "Idioma de la aplicación", - "description": "Selecciona el idioma utilizado en la interfaz de la aplicación." + "description": "Selecciona el idioma utilizado en la interfaz de la aplicación.", + "auto-detect": "Automático" } } }, From f58391ce809b384301faaa28f736f523be433c47 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:42:03 +0800 Subject: [PATCH 46/76] feat(i18n): Add French translation for language auto-detect --- Assets/Translations/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index a4ab5fe1..53c45c13 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -76,7 +76,8 @@ }, "select": { "label": "Langue de l'application", - "description": "Sélectionnez la langue utilisée dans l'interface de l'application." + "description": "Sélectionnez la langue utilisée dans l'interface de l'application.", + "auto-detect": "Automatique" } } }, From 4474e7366b7278c140a10eef21fff39719435d62 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:43:18 +0800 Subject: [PATCH 47/76] feat(i18n): Add Portuguese translation for language auto-detect --- Assets/Translations/pt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 16f24122..70f6c643 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -76,7 +76,8 @@ }, "select": { "label": "Idioma da aplicação", - "description": "Selecione o idioma usado na interface da aplicação." + "description": "Selecione o idioma usado na interface da aplicação.", + "auto-detect": "Automático" } } }, From 00d8e18a45007f66e1a94aa214d0d33436e295d3 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:45:58 +0800 Subject: [PATCH 48/76] feat(i18n): Add Simplified Chinese translation for language auto-detect --- Assets/Translations/zh-CN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index c9532210..29ec3c67 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -76,7 +76,8 @@ }, "select": { "label": "应用程序语言", - "description": "选择应用程序界面中使用的语言。" + "description": "选择应用程序界面中使用的语言。", + "auto-detect": "自动检测" } } }, From ba787cf3909377b0492f7e809a40c4a70f6c752a Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 20:47:20 +0200 Subject: [PATCH 49/76] Reduce logging noise, fix small warning --- Services/AppThemeService.qml | 7 ++++--- Services/MatugenTemplates.qml | 9 ++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index 8b6a8b38..49d7bd60 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -387,14 +387,14 @@ Singleton { stdout: StdioCollector { onStreamFinished: { if (this.text) { - Logger.i("AppThemeService", "GenerateProcess stdout:", this.text) + Logger.d("AppThemeService", "GenerateProcess stdout:", this.text) } } } stderr: StdioCollector { onStreamFinished: { if (this.text) { - Logger.w("AppThemeService", "GenerateProcess stderr:", this.text) + Logger.d("AppThemeService", "GenerateProcess stderr:", this.text) } } } @@ -402,11 +402,12 @@ Singleton { Process { id: copyProcess + workingDirectory: Quickshell.shellDir running: false stderr: StdioCollector { onStreamFinished: { if (this.text) { - Logger.w("AppThemeService", "CopyProcess stderr:", this.text) + Logger.d("AppThemeService", "CopyProcess stderr:", this.text) } } } diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index 633f7fea..3475e444 100644 --- a/Services/MatugenTemplates.qml +++ b/Services/MatugenTemplates.qml @@ -47,10 +47,9 @@ Singleton { lines.push("# post_hook = \"myapp --reload-theme\"") lines.push("") lines.push("# Remove this section and add your own templates") - lines.push("[templates.placeholder]") - lines.push("input_path = \"" + Quickshell.shellDir + "/Assets/MatugenTemplates/noctalia.json\"") - lines.push("output_path = \"" + Settings.cacheDir + "placeholder.json\"") - lines.push("post_hook = \"echo 'User templates enabled - replace this placeholder with your own templates'\"") + lines.push("#[templates.placeholder]") + lines.push("#input_path = \"" + Quickshell.shellDir + "/Assets/MatugenTemplates/noctalia.json\"") + lines.push("#output_path = \"" + Settings.cacheDir + "placeholder.json\"") lines.push("") return lines.join("\n") + "\n" @@ -75,7 +74,7 @@ Singleton { // Write the config file Quickshell.execDetached(["sh", "-c", `echo '${configContent.replace(/'/g, "'\\''")}' > '${userConfigPath}'`]) - Logger.i("MatugenTemplates", "User templates config written to:", userConfigPath) + Logger.d("MatugenTemplates", "User templates config written to:", userConfigPath) } // -------------------------------- From 5c2e8ce81c4df772700ef3fe524baf0bc4ff780b Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 11:47:26 +0800 Subject: [PATCH 50/76] Fix: Correct misplaced templates.section in de.json --- Assets/Translations/de.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a3318920..ade02602 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -533,6 +533,10 @@ } }, "templates": { + "section": { + "label": "Vorlagen", + "description": "Farben auf externe Anwendungen anwenden." + }, "ui": { "label": "UI", "description": "Desktop-Umgebung und UI-Toolkit-Theming.", @@ -585,10 +589,6 @@ "label": "Benutzer-Vorlagen", "description": "Benutzerdefinierte Matugen-Konfiguration aktivieren. Eine Vorlagendatei wird beim ersten Aktivieren unter ~/.config/noctalia/user-templates.toml erstellt" } - }, - "section": { - "description": "Farben auf externe Anwendungen anwenden.", - "label": "Vorlagen" } } }, From 0c8ab0e9e845336beb149dc8cf7445474f811572 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 11:47:39 +0800 Subject: [PATCH 51/76] Fix: Correct misplaced templates.section and missing dark-mode.mode in es.json --- Assets/Translations/es.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 2db6da8f..236c7860 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -513,6 +513,13 @@ "switch": { "label": "Modo oscuro", "description": "Cambia a un tema más oscuro para una visualización más fácil por la noche." + }, + "mode": { + "label": "Modo oscuro automático", + "description": "Habilita el cambio automático entre el modo claro y oscuro.", + "off": "Desactivado", + "manual": "Manual", + "location": "Ubicación" } }, "predefined": { @@ -526,6 +533,10 @@ } }, "templates": { + "section": { + "label": "Plantillas", + "description": "Aplicar colores a aplicaciones externas." + }, "ui": { "label": "UI", "description": "Tematización del entorno de escritorio y de la interfaz de usuario.", @@ -578,10 +589,6 @@ "label": "Plantillas de usuario", "description": "Habilitar configuración de Matugen definida por el usuario. Se creará un archivo de plantilla en ~/.config/noctalia/user-templates.toml al activar por primera vez" } - }, - "section": { - "description": "Aplicar colores a aplicaciones externas.", - "label": "Plantillas" } } }, From a00840753b69a1867765d330abdead81a0a5037d Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 11:47:59 +0800 Subject: [PATCH 52/76] Fix: Correct misplaced templates.section and missing dark-mode.mode in fr.json --- Assets/Translations/fr.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8131592a..1c1bb259 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -513,6 +513,13 @@ "switch": { "label": "Mode sombre", "description": "Passe à un thème plus sombre pour une visualisation plus facile la nuit." + }, + "mode": { + "label": "Mode sombre automatique", + "description": "Active le passage automatique entre le mode clair et sombre.", + "off": "Désactivé", + "manual": "Manuel", + "location": "Emplacement" } }, "predefined": { @@ -526,6 +533,10 @@ } }, "templates": { + "section": { + "label": "Modèles", + "description": "Appliquer des couleurs aux applications externes." + }, "ui": { "label": "UI", "description": "Thématisation de l'environnement de bureau et de la boîte à outils d'interface utilisateur.", @@ -578,10 +589,6 @@ "label": "Modèles utilisateur", "description": "Activer la configuration Matugen définie par l'utilisateur. Un fichier modèle sera créé dans ~/.config/noctalia/user-templates.toml lors de la première activation" } - }, - "section": { - "description": "Appliquer des couleurs aux applications externes.", - "label": "Modèles" } } }, From 9c0a820c9e15ac5ab006d971c2131e0cf94434c8 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 11:48:21 +0800 Subject: [PATCH 53/76] Fix: Correct misplaced templates.section and missing dark-mode.mode in pt.json --- Assets/Translations/pt.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index dd3fe7f0..86d1b7be 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -475,6 +475,13 @@ "switch": { "label": "Modo escuro", "description": "Muda para um tema mais escuro para facilitar a visualização à noite." + }, + "mode": { + "label": "Modo escuro automático", + "description": "Ativa a troca automática entre o modo claro e escuro.", + "off": "Desativado", + "manual": "Manual", + "location": "Localização" } }, "predefined": { @@ -488,6 +495,10 @@ } }, "templates": { + "section": { + "label": "Modelos", + "description": "Aplicar cores a aplicações externas." + }, "ui": { "label": "UI", "description": "Tematização do ambiente de desktop e kit de ferramentas de UI.", @@ -540,10 +551,6 @@ "label": "Modelos do usuário", "description": "Ativa a configuração do Matugen definida pelo usuário. Um arquivo de modelo será criado em ~/.config/noctalia/user-templates.toml na primeira ativação" } - }, - "section": { - "description": "Aplicar cores a aplicações externas.", - "label": "Modelos" } } }, From c6f317417003e680bc2013826d0fc8ed5809953a Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 11:48:35 +0800 Subject: [PATCH 54/76] Fix: Correct misplaced templates.section and missing dark-mode.mode in zh-CN.json --- Assets/Translations/zh-CN.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 4738030b..2082e765 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -513,6 +513,13 @@ "switch": { "label": "深色模式", "description": "切换到更暗的主题,便于夜间观看。" + }, + "mode": { + "label": "深色模式切换方式", + "description": "启用明暗模式的自动切换功能。", + "off": "关闭", + "manual": "手动", + "location": "位置" } }, "predefined": { @@ -526,6 +533,10 @@ } }, "templates": { + "section": { + "label": "模板", + "description": "将颜色应用于外部应用程序。" + }, "ui": { "label": "用户界面", "description": "桌面环境和 UI 工具包主题。", @@ -578,10 +589,6 @@ "label": "用户模板", "description": "启用用户定义的 Matugen 配置。首次启用时将在 ~/.config/noctalia/user-templates.toml 创建模板文件" } - }, - "section": { - "description": "将颜色应用于外部应用程序。", - "label": "模板" } } }, From ecc97ca2556e5f28a3af97f835c695b04dd27799 Mon Sep 17 00:00:00 2001 From: Sighthesia Date: Sun, 19 Oct 2025 15:38:20 +0800 Subject: [PATCH 55/76] MediaMini: handle empty media content with active player for rounded background --- Modules/Bar/Widgets/MediaMini.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 9831626e..bf7520a1 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -120,6 +120,10 @@ Item { if (!hasActivePlayer) { return maxWidth } + // If there's an active player but no contenct, use a minimum height for rounded background + if (hasActivePlayer && fullTitleMetrics.contentWidth === 0) { + return Style.capsuleHeight + Style.marginS + } // Use content width but don't exceed user-set maximum width return Math.min(calculateContentWidth(), maxWidth) } From af7498155c4d8fbf4196562eb865d668db0413d0 Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 19 Oct 2025 16:46:53 +0200 Subject: [PATCH 56/76] LocationTab: add setting for first day of the week Calendar: replace MonthGrid with custom solution to allow changing first day of the week --- Assets/Translations/de.json | 8 ++ Assets/Translations/en.json | 8 ++ Assets/Translations/es.json | 8 ++ Assets/Translations/fr.json | 8 ++ Assets/Translations/pt.json | 8 ++ Assets/Translations/zh-CN.json | 8 ++ Assets/settings-default.json | 3 +- Commons/I18n.qml | 1 - Commons/Settings.qml | 1 + Modules/Bar/Calendar/CalendarPanel.qml | 127 +++++++++++++++++++------ Modules/Settings/Tabs/GeneralTab.qml | 28 +++--- Modules/Settings/Tabs/LocationTab.qml | 21 ++++ 12 files changed, 185 insertions(+), 44 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a3318920..807f4fb4 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -631,6 +631,14 @@ "week-numbers": { "label": "Wochennummern anzeigen", "description": "Zeigt die Woche des Jahres (z.B. Woche 38) im Kalender an." + }, + "first-day-of-week": { + "label": "Erster Tag der Woche", + "description": "Wählen Sie, welcher Tag die Kalenderwoche beginnt.", + "auto": "Systemstandard", + "monday": "Montag", + "saturday": "Samstag", + "sunday": "Sonntag" } } }, diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index cbad3dd8..765201aa 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -631,6 +631,14 @@ "week-numbers": { "label": "Show week numbers", "description": "Displays the week of the year (e.g., Week 38) in the calendar." + }, + "first-day-of-week": { + "label": "First day of week", + "description": "Choose which day starts the calendar week.", + "auto": "System default", + "monday": "Monday", + "saturday": "Saturday", + "sunday": "Sunday" } } }, diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 2db6da8f..0a4f77f4 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -624,6 +624,14 @@ "week-numbers": { "label": "Mostrar números de semana", "description": "Muestra la semana del año (ej., Semana 38) en el calendario." + }, + "first-day-of-week": { + "label": "Primer día de la semana", + "description": "Elige qué día comienza la semana del calendario.", + "auto": "Predeterminado del sistema", + "monday": "Lunes", + "saturday": "Sábado", + "sunday": "Domingo" } } }, diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8131592a..eb401542 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -624,6 +624,14 @@ "week-numbers": { "label": "Afficher les numéros de semaine", "description": "Affiche la semaine de l'année (ex: Semaine 38) dans le calendrier." + }, + "first-day-of-week": { + "label": "Premier jour de la semaine", + "description": "Choisissez quel jour commence la semaine du calendrier.", + "auto": "Par défaut du système", + "monday": "Lundi", + "saturday": "Samedi", + "sunday": "Dimanche" } } }, diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index dd3fe7f0..cdd14da2 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -586,6 +586,14 @@ "week-numbers": { "label": "Mostrar números da semana", "description": "Exibe a semana do ano (ex., Semana 38) no calendário." + }, + "first-day-of-week": { + "label": "Primeiro dia da semana", + "description": "Escolha qual dia inicia a semana do calendário.", + "auto": "Padrão do sistema", + "monday": "Segunda-feira", + "saturday": "Sábado", + "sunday": "Domingo" } } }, diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 4738030b..427e7c41 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -624,6 +624,14 @@ "week-numbers": { "label": "显示周数", "description": "在日历中显示一年中的第几周(例如:第 38 周)。" + }, + "first-day-of-week": { + "label": "每周第一天", + "description": "选择日历每周的起始日。", + "auto": "系统默认", + "monday": "星期一", + "saturday": "星期六", + "sunday": "星期日" } } }, diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 57033237..6e1b77a2 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -74,7 +74,8 @@ "weatherEnabled": true, "useFahrenheit": false, "use12hourFormat": false, - "showWeekNumberInCalendar": false + "showWeekNumberInCalendar": false, + "firstDayOfWeek": "auto" }, "screenRecorder": { "directory": "", diff --git a/Commons/I18n.qml b/Commons/I18n.qml index 4ec5e86f..59eeb922 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -8,7 +8,6 @@ import qs.Commons Singleton { id: root - property bool isLoaded: false property string langCode: "" property string systemDetectedLangCode: "" diff --git a/Commons/Settings.qml b/Commons/Settings.qml index f98e3e3e..236277b1 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -202,6 +202,7 @@ Singleton { property bool useFahrenheit: false property bool use12hourFormat: false property bool showWeekNumberInCalendar: false + property string firstDayOfWeek: "auto" // "auto", "monday", "saturday", "sunday" } // screen recorder diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index b882324d..3b8bdd45 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -22,7 +22,24 @@ NPanel { anchors.margins: Style.marginL spacing: Style.marginM - readonly property int firstDayOfWeek: Qt.locale().firstDayOfWeek + readonly property int firstDayOfWeek: { + var setting = Settings.data.location.firstDayOfWeek + if (!setting) + return Qt.Monday + + switch (setting) { + case "auto": + return Qt.locale().firstDayOfWeek + case "monday": + return Qt.Monday + case "saturday": + return 6 // Qt.Saturday + case "sunday": + return Qt.Sunday + default: + return Qt.Monday + } + } property bool isCurrentMonth: checkIsCurrentMonth() readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) @@ -278,7 +295,7 @@ NPanel { } } - // ... (rest of the file is unchanged) ... + // Weather forecast RowLayout { visible: weatherReady Layout.fillWidth: true @@ -446,38 +463,88 @@ NPanel { } } } - MonthGrid { + GridLayout { id: grid Layout.fillWidth: true Layout.fillHeight: true - spacing: Style.marginXXS - month: Time.date.getMonth() - year: Time.date.getFullYear() - locale: Qt.locale() - delegate: Item { - Rectangle { - width: Style.baseWidgetSize * 0.9 - height: Style.baseWidgetSize * 0.9 - anchors.centerIn: parent - radius: Style.radiusM - color: model.today ? Color.mSecondary : Color.transparent - NText { - anchors.centerIn: parent - text: model.day - color: { - if (model.today) - return Color.mOnSecondary - if (model.month === grid.month) - return Color.mOnSurface - return Color.mOnSurfaceVariant - } - opacity: model.month === grid.month ? 1.0 : 0.4 - pointSize: Style.fontSizeM - font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium + columns: 7 + rowSpacing: Style.marginXXS + columnSpacing: Style.marginXXS + + property int month: Time.date.getMonth() + property int year: Time.date.getFullYear() + + Repeater { + model: 42 // 6 rows × 7 days + delegate: Item { + id: cellItem + Layout.fillWidth: true + Layout.fillHeight: true + + required property int index + + property int cellDay: { + let firstOfMonth = new Date(grid.year, grid.month, 1) + let firstDayOfWeek = content.firstDayOfWeek + let firstOfMonthDayOfWeek = firstOfMonth.getDay() + let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7 + let startDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst) + let currentDate = new Date(startDate) + currentDate.setDate(startDate.getDate() + cellItem.index) + return currentDate.getDate() } - Behavior on color { - ColorAnimation { - duration: Style.animationFast + + property int cellMonth: { + let firstOfMonth = new Date(grid.year, grid.month, 1) + let firstDayOfWeek = content.firstDayOfWeek + let firstOfMonthDayOfWeek = firstOfMonth.getDay() + let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7 + let startDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst) + let currentDate = new Date(startDate) + currentDate.setDate(startDate.getDate() + cellItem.index) + return currentDate.getMonth() + } + + property int cellYear: { + let firstOfMonth = new Date(grid.year, grid.month, 1) + let firstDayOfWeek = content.firstDayOfWeek + let firstOfMonthDayOfWeek = firstOfMonth.getDay() + let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7 + let startDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst) + let currentDate = new Date(startDate) + currentDate.setDate(startDate.getDate() + cellItem.index) + return currentDate.getFullYear() + } + + property bool isToday: cellDay === Time.date.getDate() && cellMonth === Time.date.getMonth() && cellYear === Time.date.getFullYear() + property bool isCurrentMonth: cellMonth === grid.month + + Rectangle { + width: Style.baseWidgetSize * 0.9 + height: Style.baseWidgetSize * 0.9 + anchors.centerIn: parent + radius: Style.radiusM + color: parent.isToday ? Color.mSecondary : Color.transparent + + NText { + anchors.centerIn: parent + text: cellItem.cellDay + color: { + if (cellItem.isToday) + return Color.mOnSecondary + if (cellItem.isCurrentMonth) + return Color.mOnSurface + return Color.mOnSurfaceVariant + } + opacity: cellItem.isCurrentMonth ? 1.0 : 0.5 + pointSize: Style.fontSizeM + font.weight: cellItem.isToday ? Style.fontWeightBold : Style.fontWeightSemiBold + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } } } } diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index 40b5276d..4f084445 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -204,20 +204,24 @@ ColumnLayout { Layout.fillWidth: true label: I18n.tr("settings.general.language.select.label") description: I18n.tr("settings.general.language.select.description") - model: [ - { "key": "", "name": I18n.tr("settings.general.language.select.auto-detect") + " (" + I18n.systemDetectedLangCode + ")" } - ].concat(I18n.availableLanguages.map(function(langCode) { - return { "key": langCode, "name": langCode } - })) + model: [{ + "key": "", + "name": I18n.tr("settings.general.language.select.auto-detect") + " (" + I18n.systemDetectedLangCode + ")" + }].concat(I18n.availableLanguages.map(function (langCode) { + return { + "key": langCode, + "name": langCode + } + })) currentKey: Settings.data.general.language onSelected: key => { - Settings.data.general.language = key - if (key === "") { - I18n.detectLanguage() // Re-detect system language if "Automatic" is selected - } else { - I18n.setLanguage(key) // Set specific language - } - } + Settings.data.general.language = key + if (key === "") { + I18n.detectLanguage() // Re-detect system language if "Automatic" is selected + } else { + I18n.setLanguage(key) // Set specific language + } + } } } diff --git a/Modules/Settings/Tabs/LocationTab.qml b/Modules/Settings/Tabs/LocationTab.qml index 7b6c340b..0fb7f8a2 100644 --- a/Modules/Settings/Tabs/LocationTab.qml +++ b/Modules/Settings/Tabs/LocationTab.qml @@ -117,6 +117,27 @@ ColumnLayout { checked: Settings.data.location.showWeekNumberInCalendar onToggled: checked => Settings.data.location.showWeekNumberInCalendar = checked } + + NComboBox { + label: I18n.tr("settings.location.date-time.first-day-of-week.label") + description: I18n.tr("settings.location.date-time.first-day-of-week.description") + minimumWidth: 220 * Style.uiScaleRatio + model: [{ + "key": "auto", + "name": I18n.tr("settings.location.date-time.first-day-of-week.auto") + }, { + "key": "monday", + "name": I18n.tr("settings.location.date-time.first-day-of-week.monday") + }, { + "key": "saturday", + "name": I18n.tr("settings.location.date-time.first-day-of-week.saturday") + }, { + "key": "sunday", + "name": I18n.tr("settings.location.date-time.first-day-of-week.sunday") + }] + currentKey: Settings.data.location.firstDayOfWeek + onSelected: key => Settings.data.location.firstDayOfWeek = key + } } NDivider { From c6080b65bed30d5b398d1ddc9b42c2f47633268e Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 19 Oct 2025 17:09:11 +0200 Subject: [PATCH 57/76] Calendar: fix layout with week numbers enabled --- Modules/Bar/Calendar/CalendarPanel.qml | 109 +++++++++++++------------ 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 3b8bdd45..b6652675 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -22,24 +22,7 @@ NPanel { anchors.margins: Style.marginL spacing: Style.marginM - readonly property int firstDayOfWeek: { - var setting = Settings.data.location.firstDayOfWeek - if (!setting) - return Qt.Monday - - switch (setting) { - case "auto": - return Qt.locale().firstDayOfWeek - case "monday": - return Qt.Monday - case "saturday": - return 6 // Qt.Saturday - case "sunday": - return Qt.Sunday - default: - return Qt.Monday - } - } + readonly property int firstDayOfWeek: Qt.Monday // Always start week on Monday (use Qt.Sunday for Sunday, 6 for Saturday) property bool isCurrentMonth: checkIsCurrentMonth() readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) @@ -384,13 +367,19 @@ NPanel { } } } + + // Day headers row RowLayout { Layout.fillWidth: true spacing: 0 + + // Empty space for week number column Item { visible: Settings.data.location.showWeekNumberInCalendar Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0 } + + // Day name headers GridLayout { Layout.fillWidth: true columns: 7 @@ -418,51 +407,63 @@ NPanel { } } } + + // Calendar grid with week numbers RowLayout { Layout.fillWidth: true Layout.fillHeight: true - spacing: 0 - ColumnLayout { + spacing: Style.marginS + + // Week numbers column + Item { visible: Settings.data.location.showWeekNumberInCalendar - Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0 + Layout.preferredWidth: Style.baseWidgetSize * 0.7 Layout.fillHeight: true - spacing: 0 - Repeater { - model: 6 - Item { - Layout.fillWidth: true - Layout.fillHeight: true - NText { - anchors.centerIn: parent - color: Color.mOutline - pointSize: Style.fontSizeXXS - font.weight: Style.fontWeightMedium - text: { - let firstOfMonth = new Date(grid.year, grid.month, 1) - let firstDayOfWeek = content.firstDayOfWeek - let firstOfMonthDayOfWeek = firstOfMonth.getDay() - let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7 - if (daysBeforeFirst === 0) { - daysBeforeFirst = 7 + + ColumnLayout { + anchors.fill: parent + spacing: Style.marginXXS + + Repeater { + model: 6 + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + NText { + anchors.centerIn: parent + color: Color.mOutline + pointSize: Style.fontSizeXXS + font.weight: Style.fontWeightMedium + text: { + let firstOfMonth = new Date(grid.year, grid.month, 1) + let firstDayOfWeek = content.firstDayOfWeek + let firstOfMonthDayOfWeek = firstOfMonth.getDay() + let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7 + if (daysBeforeFirst === 0) { + daysBeforeFirst = 7 + } + let gridStartDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst) + let rowStartDate = new Date(gridStartDate) + rowStartDate.setDate(gridStartDate.getDate() + (index * 7)) + let thursday = new Date(rowStartDate) + if (firstDayOfWeek === 0) { + thursday.setDate(rowStartDate.getDate() + 4) + } else if (firstDayOfWeek === 1) { + thursday.setDate(rowStartDate.getDate() + 3) + } else { + let daysToThursday = (4 - firstDayOfWeek + 7) % 7 + thursday.setDate(rowStartDate.getDate() + daysToThursday) + } + return `${getISOWeekNumber(thursday)}` } - let gridStartDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst) - let rowStartDate = new Date(gridStartDate) - rowStartDate.setDate(gridStartDate.getDate() + (index * 7)) - let thursday = new Date(rowStartDate) - if (firstDayOfWeek === 0) { - thursday.setDate(rowStartDate.getDate() + 4) - } else if (firstDayOfWeek === 1) { - thursday.setDate(rowStartDate.getDate() + 3) - } else { - let daysToThursday = (4 - firstDayOfWeek + 7) % 7 - thursday.setDate(rowStartDate.getDate() + daysToThursday) - } - return `${getISOWeekNumber(thursday)}` } } } } } + + // Calendar days grid GridLayout { id: grid Layout.fillWidth: true @@ -536,9 +537,9 @@ NPanel { return Color.mOnSurface return Color.mOnSurfaceVariant } - opacity: cellItem.isCurrentMonth ? 1.0 : 0.5 + opacity: cellItem.isCurrentMonth ? 1.0 : 0.4 pointSize: Style.fontSizeM - font.weight: cellItem.isToday ? Style.fontWeightBold : Style.fontWeightSemiBold + font.weight: cellItem.isToday ? Style.fontWeightBold : Style.fontWeightMedium } Behavior on color { From 528e976f274176ef80d2ec0dcce0b9dd10fb7777 Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 19 Oct 2025 17:44:52 +0200 Subject: [PATCH 58/76] Revert "LocationTab: add setting for first day of the week" This reverts commit af7498155c4d8fbf4196562eb865d668db0413d0. --- Assets/Translations/de.json | 8 -- Assets/Translations/en.json | 8 -- Assets/Translations/es.json | 8 -- Assets/Translations/fr.json | 8 -- Assets/Translations/pt.json | 8 -- Assets/Translations/zh-CN.json | 8 -- Assets/settings-default.json | 3 +- Commons/I18n.qml | 1 + Commons/Settings.qml | 1 - Modules/Bar/Calendar/CalendarPanel.qml | 110 +++++++------------------ Modules/Settings/Tabs/GeneralTab.qml | 28 +++---- Modules/Settings/Tabs/LocationTab.qml | 21 ----- 12 files changed, 43 insertions(+), 169 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index b5fc5c9b..ade02602 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -631,14 +631,6 @@ "week-numbers": { "label": "Wochennummern anzeigen", "description": "Zeigt die Woche des Jahres (z.B. Woche 38) im Kalender an." - }, - "first-day-of-week": { - "label": "Erster Tag der Woche", - "description": "Wählen Sie, welcher Tag die Kalenderwoche beginnt.", - "auto": "Systemstandard", - "monday": "Montag", - "saturday": "Samstag", - "sunday": "Sonntag" } } }, diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 765201aa..cbad3dd8 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -631,14 +631,6 @@ "week-numbers": { "label": "Show week numbers", "description": "Displays the week of the year (e.g., Week 38) in the calendar." - }, - "first-day-of-week": { - "label": "First day of week", - "description": "Choose which day starts the calendar week.", - "auto": "System default", - "monday": "Monday", - "saturday": "Saturday", - "sunday": "Sunday" } } }, diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 37929c83..236c7860 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -631,14 +631,6 @@ "week-numbers": { "label": "Mostrar números de semana", "description": "Muestra la semana del año (ej., Semana 38) en el calendario." - }, - "first-day-of-week": { - "label": "Primer día de la semana", - "description": "Elige qué día comienza la semana del calendario.", - "auto": "Predeterminado del sistema", - "monday": "Lunes", - "saturday": "Sábado", - "sunday": "Domingo" } } }, diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 0feda9ff..1c1bb259 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -631,14 +631,6 @@ "week-numbers": { "label": "Afficher les numéros de semaine", "description": "Affiche la semaine de l'année (ex: Semaine 38) dans le calendrier." - }, - "first-day-of-week": { - "label": "Premier jour de la semaine", - "description": "Choisissez quel jour commence la semaine du calendrier.", - "auto": "Par défaut du système", - "monday": "Lundi", - "saturday": "Samedi", - "sunday": "Dimanche" } } }, diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 11008643..86d1b7be 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -593,14 +593,6 @@ "week-numbers": { "label": "Mostrar números da semana", "description": "Exibe a semana do ano (ex., Semana 38) no calendário." - }, - "first-day-of-week": { - "label": "Primeiro dia da semana", - "description": "Escolha qual dia inicia a semana do calendário.", - "auto": "Padrão do sistema", - "monday": "Segunda-feira", - "saturday": "Sábado", - "sunday": "Domingo" } } }, diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 4631a11d..2082e765 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -631,14 +631,6 @@ "week-numbers": { "label": "显示周数", "description": "在日历中显示一年中的第几周(例如:第 38 周)。" - }, - "first-day-of-week": { - "label": "每周第一天", - "description": "选择日历每周的起始日。", - "auto": "系统默认", - "monday": "星期一", - "saturday": "星期六", - "sunday": "星期日" } } }, diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 6e1b77a2..57033237 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -74,8 +74,7 @@ "weatherEnabled": true, "useFahrenheit": false, "use12hourFormat": false, - "showWeekNumberInCalendar": false, - "firstDayOfWeek": "auto" + "showWeekNumberInCalendar": false }, "screenRecorder": { "directory": "", diff --git a/Commons/I18n.qml b/Commons/I18n.qml index 59eeb922..4ec5e86f 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -8,6 +8,7 @@ import qs.Commons Singleton { id: root + property bool isLoaded: false property string langCode: "" property string systemDetectedLangCode: "" diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 236277b1..f98e3e3e 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -202,7 +202,6 @@ Singleton { property bool useFahrenheit: false property bool use12hourFormat: false property bool showWeekNumberInCalendar: false - property string firstDayOfWeek: "auto" // "auto", "monday", "saturday", "sunday" } // screen recorder diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index b6652675..f1b4a4d0 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -22,7 +22,7 @@ NPanel { anchors.margins: Style.marginL spacing: Style.marginM - readonly property int firstDayOfWeek: Qt.Monday // Always start week on Monday (use Qt.Sunday for Sunday, 6 for Saturday) + readonly property int firstDayOfWeek: Qt.locale().firstDayOfWeek property bool isCurrentMonth: checkIsCurrentMonth() readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) @@ -278,7 +278,7 @@ NPanel { } } - // Weather forecast + // ... (rest of the file is unchanged) ... RowLayout { visible: weatherReady Layout.fillWidth: true @@ -462,90 +462,38 @@ NPanel { } } } - - // Calendar days grid - GridLayout { + MonthGrid { id: grid Layout.fillWidth: true Layout.fillHeight: true - columns: 7 - rowSpacing: Style.marginXXS - columnSpacing: Style.marginXXS - - property int month: Time.date.getMonth() - property int year: Time.date.getFullYear() - - Repeater { - model: 42 // 6 rows × 7 days - delegate: Item { - id: cellItem - Layout.fillWidth: true - Layout.fillHeight: true - - required property int index - - property int cellDay: { - let firstOfMonth = new Date(grid.year, grid.month, 1) - let firstDayOfWeek = content.firstDayOfWeek - let firstOfMonthDayOfWeek = firstOfMonth.getDay() - let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7 - let startDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst) - let currentDate = new Date(startDate) - currentDate.setDate(startDate.getDate() + cellItem.index) - return currentDate.getDate() - } - - property int cellMonth: { - let firstOfMonth = new Date(grid.year, grid.month, 1) - let firstDayOfWeek = content.firstDayOfWeek - let firstOfMonthDayOfWeek = firstOfMonth.getDay() - let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7 - let startDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst) - let currentDate = new Date(startDate) - currentDate.setDate(startDate.getDate() + cellItem.index) - return currentDate.getMonth() - } - - property int cellYear: { - let firstOfMonth = new Date(grid.year, grid.month, 1) - let firstDayOfWeek = content.firstDayOfWeek - let firstOfMonthDayOfWeek = firstOfMonth.getDay() - let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7 - let startDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst) - let currentDate = new Date(startDate) - currentDate.setDate(startDate.getDate() + cellItem.index) - return currentDate.getFullYear() - } - - property bool isToday: cellDay === Time.date.getDate() && cellMonth === Time.date.getMonth() && cellYear === Time.date.getFullYear() - property bool isCurrentMonth: cellMonth === grid.month - - Rectangle { - width: Style.baseWidgetSize * 0.9 - height: Style.baseWidgetSize * 0.9 + spacing: Style.marginXXS + month: Time.date.getMonth() + year: Time.date.getFullYear() + locale: Qt.locale() + delegate: Item { + Rectangle { + width: Style.baseWidgetSize * 0.9 + height: Style.baseWidgetSize * 0.9 + anchors.centerIn: parent + radius: Style.radiusM + color: model.today ? Color.mSecondary : Color.transparent + NText { anchors.centerIn: parent - radius: Style.radiusM - color: parent.isToday ? Color.mSecondary : Color.transparent - - NText { - anchors.centerIn: parent - text: cellItem.cellDay - color: { - if (cellItem.isToday) - return Color.mOnSecondary - if (cellItem.isCurrentMonth) - return Color.mOnSurface - return Color.mOnSurfaceVariant - } - opacity: cellItem.isCurrentMonth ? 1.0 : 0.4 - pointSize: Style.fontSizeM - font.weight: cellItem.isToday ? Style.fontWeightBold : Style.fontWeightMedium + text: model.day + color: { + if (model.today) + return Color.mOnSecondary + if (model.month === grid.month) + return Color.mOnSurface + return Color.mOnSurfaceVariant } - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } + opacity: model.month === grid.month ? 1.0 : 0.4 + pointSize: Style.fontSizeM + font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium + } + Behavior on color { + ColorAnimation { + duration: Style.animationFast } } } diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index 4f084445..40b5276d 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -204,24 +204,20 @@ ColumnLayout { Layout.fillWidth: true label: I18n.tr("settings.general.language.select.label") description: I18n.tr("settings.general.language.select.description") - model: [{ - "key": "", - "name": I18n.tr("settings.general.language.select.auto-detect") + " (" + I18n.systemDetectedLangCode + ")" - }].concat(I18n.availableLanguages.map(function (langCode) { - return { - "key": langCode, - "name": langCode - } - })) + model: [ + { "key": "", "name": I18n.tr("settings.general.language.select.auto-detect") + " (" + I18n.systemDetectedLangCode + ")" } + ].concat(I18n.availableLanguages.map(function(langCode) { + return { "key": langCode, "name": langCode } + })) currentKey: Settings.data.general.language onSelected: key => { - Settings.data.general.language = key - if (key === "") { - I18n.detectLanguage() // Re-detect system language if "Automatic" is selected - } else { - I18n.setLanguage(key) // Set specific language - } - } + Settings.data.general.language = key + if (key === "") { + I18n.detectLanguage() // Re-detect system language if "Automatic" is selected + } else { + I18n.setLanguage(key) // Set specific language + } + } } } diff --git a/Modules/Settings/Tabs/LocationTab.qml b/Modules/Settings/Tabs/LocationTab.qml index 0fb7f8a2..7b6c340b 100644 --- a/Modules/Settings/Tabs/LocationTab.qml +++ b/Modules/Settings/Tabs/LocationTab.qml @@ -117,27 +117,6 @@ ColumnLayout { checked: Settings.data.location.showWeekNumberInCalendar onToggled: checked => Settings.data.location.showWeekNumberInCalendar = checked } - - NComboBox { - label: I18n.tr("settings.location.date-time.first-day-of-week.label") - description: I18n.tr("settings.location.date-time.first-day-of-week.description") - minimumWidth: 220 * Style.uiScaleRatio - model: [{ - "key": "auto", - "name": I18n.tr("settings.location.date-time.first-day-of-week.auto") - }, { - "key": "monday", - "name": I18n.tr("settings.location.date-time.first-day-of-week.monday") - }, { - "key": "saturday", - "name": I18n.tr("settings.location.date-time.first-day-of-week.saturday") - }, { - "key": "sunday", - "name": I18n.tr("settings.location.date-time.first-day-of-week.sunday") - }] - currentKey: Settings.data.location.firstDayOfWeek - onSelected: key => Settings.data.location.firstDayOfWeek = key - } } NDivider { From 7d564ae3124cf49244a9a68c6a35309e1fd40c89 Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 19 Oct 2025 17:45:24 +0200 Subject: [PATCH 59/76] Revert "Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell" This reverts commit 35400b9a968437d4d0018a59dc8c5dac98a8c0df, reversing changes made to af7498155c4d8fbf4196562eb865d668db0413d0. --- Assets/Translations/de.json | 8 ++++---- Assets/Translations/es.json | 15 ++++----------- Assets/Translations/fr.json | 15 ++++----------- Assets/Translations/pt.json | 15 ++++----------- Assets/Translations/zh-CN.json | 15 ++++----------- Modules/Bar/Widgets/MediaMini.qml | 4 ---- 6 files changed, 20 insertions(+), 52 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index ade02602..a3318920 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -533,10 +533,6 @@ } }, "templates": { - "section": { - "label": "Vorlagen", - "description": "Farben auf externe Anwendungen anwenden." - }, "ui": { "label": "UI", "description": "Desktop-Umgebung und UI-Toolkit-Theming.", @@ -589,6 +585,10 @@ "label": "Benutzer-Vorlagen", "description": "Benutzerdefinierte Matugen-Konfiguration aktivieren. Eine Vorlagendatei wird beim ersten Aktivieren unter ~/.config/noctalia/user-templates.toml erstellt" } + }, + "section": { + "description": "Farben auf externe Anwendungen anwenden.", + "label": "Vorlagen" } } }, diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 236c7860..2db6da8f 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -513,13 +513,6 @@ "switch": { "label": "Modo oscuro", "description": "Cambia a un tema más oscuro para una visualización más fácil por la noche." - }, - "mode": { - "label": "Modo oscuro automático", - "description": "Habilita el cambio automático entre el modo claro y oscuro.", - "off": "Desactivado", - "manual": "Manual", - "location": "Ubicación" } }, "predefined": { @@ -533,10 +526,6 @@ } }, "templates": { - "section": { - "label": "Plantillas", - "description": "Aplicar colores a aplicaciones externas." - }, "ui": { "label": "UI", "description": "Tematización del entorno de escritorio y de la interfaz de usuario.", @@ -589,6 +578,10 @@ "label": "Plantillas de usuario", "description": "Habilitar configuración de Matugen definida por el usuario. Se creará un archivo de plantilla en ~/.config/noctalia/user-templates.toml al activar por primera vez" } + }, + "section": { + "description": "Aplicar colores a aplicaciones externas.", + "label": "Plantillas" } } }, diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 1c1bb259..8131592a 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -513,13 +513,6 @@ "switch": { "label": "Mode sombre", "description": "Passe à un thème plus sombre pour une visualisation plus facile la nuit." - }, - "mode": { - "label": "Mode sombre automatique", - "description": "Active le passage automatique entre le mode clair et sombre.", - "off": "Désactivé", - "manual": "Manuel", - "location": "Emplacement" } }, "predefined": { @@ -533,10 +526,6 @@ } }, "templates": { - "section": { - "label": "Modèles", - "description": "Appliquer des couleurs aux applications externes." - }, "ui": { "label": "UI", "description": "Thématisation de l'environnement de bureau et de la boîte à outils d'interface utilisateur.", @@ -589,6 +578,10 @@ "label": "Modèles utilisateur", "description": "Activer la configuration Matugen définie par l'utilisateur. Un fichier modèle sera créé dans ~/.config/noctalia/user-templates.toml lors de la première activation" } + }, + "section": { + "description": "Appliquer des couleurs aux applications externes.", + "label": "Modèles" } } }, diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 86d1b7be..dd3fe7f0 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -475,13 +475,6 @@ "switch": { "label": "Modo escuro", "description": "Muda para um tema mais escuro para facilitar a visualização à noite." - }, - "mode": { - "label": "Modo escuro automático", - "description": "Ativa a troca automática entre o modo claro e escuro.", - "off": "Desativado", - "manual": "Manual", - "location": "Localização" } }, "predefined": { @@ -495,10 +488,6 @@ } }, "templates": { - "section": { - "label": "Modelos", - "description": "Aplicar cores a aplicações externas." - }, "ui": { "label": "UI", "description": "Tematização do ambiente de desktop e kit de ferramentas de UI.", @@ -551,6 +540,10 @@ "label": "Modelos do usuário", "description": "Ativa a configuração do Matugen definida pelo usuário. Um arquivo de modelo será criado em ~/.config/noctalia/user-templates.toml na primeira ativação" } + }, + "section": { + "description": "Aplicar cores a aplicações externas.", + "label": "Modelos" } } }, diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 2082e765..4738030b 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -513,13 +513,6 @@ "switch": { "label": "深色模式", "description": "切换到更暗的主题,便于夜间观看。" - }, - "mode": { - "label": "深色模式切换方式", - "description": "启用明暗模式的自动切换功能。", - "off": "关闭", - "manual": "手动", - "location": "位置" } }, "predefined": { @@ -533,10 +526,6 @@ } }, "templates": { - "section": { - "label": "模板", - "description": "将颜色应用于外部应用程序。" - }, "ui": { "label": "用户界面", "description": "桌面环境和 UI 工具包主题。", @@ -589,6 +578,10 @@ "label": "用户模板", "description": "启用用户定义的 Matugen 配置。首次启用时将在 ~/.config/noctalia/user-templates.toml 创建模板文件" } + }, + "section": { + "description": "将颜色应用于外部应用程序。", + "label": "模板" } } }, diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index bf7520a1..9831626e 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -120,10 +120,6 @@ Item { if (!hasActivePlayer) { return maxWidth } - // If there's an active player but no contenct, use a minimum height for rounded background - if (hasActivePlayer && fullTitleMetrics.contentWidth === 0) { - return Style.capsuleHeight + Style.marginS - } // Use content width but don't exceed user-set maximum width return Math.min(calculateContentWidth(), maxWidth) } From 2362dca8b6b1526253cf7963ee4970738dcdaaff Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 19 Oct 2025 17:46:49 +0200 Subject: [PATCH 60/76] Revert "Calendar: fix layout with week numbers enabled" This reverts commit c6080b65bed30d5b398d1ddc9b42c2f47633268e. --- Modules/Bar/Calendar/CalendarPanel.qml | 84 +++++++++++--------------- 1 file changed, 34 insertions(+), 50 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index f1b4a4d0..b882324d 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -367,19 +367,13 @@ NPanel { } } } - - // Day headers row RowLayout { Layout.fillWidth: true spacing: 0 - - // Empty space for week number column Item { visible: Settings.data.location.showWeekNumberInCalendar Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0 } - - // Day name headers GridLayout { Layout.fillWidth: true columns: 7 @@ -407,56 +401,46 @@ NPanel { } } } - - // Calendar grid with week numbers RowLayout { Layout.fillWidth: true Layout.fillHeight: true - spacing: Style.marginS - - // Week numbers column - Item { + spacing: 0 + ColumnLayout { visible: Settings.data.location.showWeekNumberInCalendar - Layout.preferredWidth: Style.baseWidgetSize * 0.7 + Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0 Layout.fillHeight: true - - ColumnLayout { - anchors.fill: parent - spacing: Style.marginXXS - - Repeater { - model: 6 - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - NText { - anchors.centerIn: parent - color: Color.mOutline - pointSize: Style.fontSizeXXS - font.weight: Style.fontWeightMedium - text: { - let firstOfMonth = new Date(grid.year, grid.month, 1) - let firstDayOfWeek = content.firstDayOfWeek - let firstOfMonthDayOfWeek = firstOfMonth.getDay() - let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7 - if (daysBeforeFirst === 0) { - daysBeforeFirst = 7 - } - let gridStartDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst) - let rowStartDate = new Date(gridStartDate) - rowStartDate.setDate(gridStartDate.getDate() + (index * 7)) - let thursday = new Date(rowStartDate) - if (firstDayOfWeek === 0) { - thursday.setDate(rowStartDate.getDate() + 4) - } else if (firstDayOfWeek === 1) { - thursday.setDate(rowStartDate.getDate() + 3) - } else { - let daysToThursday = (4 - firstDayOfWeek + 7) % 7 - thursday.setDate(rowStartDate.getDate() + daysToThursday) - } - return `${getISOWeekNumber(thursday)}` + spacing: 0 + Repeater { + model: 6 + Item { + Layout.fillWidth: true + Layout.fillHeight: true + NText { + anchors.centerIn: parent + color: Color.mOutline + pointSize: Style.fontSizeXXS + font.weight: Style.fontWeightMedium + text: { + let firstOfMonth = new Date(grid.year, grid.month, 1) + let firstDayOfWeek = content.firstDayOfWeek + let firstOfMonthDayOfWeek = firstOfMonth.getDay() + let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7 + if (daysBeforeFirst === 0) { + daysBeforeFirst = 7 } + let gridStartDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst) + let rowStartDate = new Date(gridStartDate) + rowStartDate.setDate(gridStartDate.getDate() + (index * 7)) + let thursday = new Date(rowStartDate) + if (firstDayOfWeek === 0) { + thursday.setDate(rowStartDate.getDate() + 4) + } else if (firstDayOfWeek === 1) { + thursday.setDate(rowStartDate.getDate() + 3) + } else { + let daysToThursday = (4 - firstDayOfWeek + 7) % 7 + thursday.setDate(rowStartDate.getDate() + daysToThursday) + } + return `${getISOWeekNumber(thursday)}` } } } From 419cd42b12879620cee26d5a0cf003a6f224d573 Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Sun, 19 Oct 2025 13:06:15 -0400 Subject: [PATCH 61/76] Add a clear button, and optional icon to NTextInput --- Assets/Translations/de.json | 3 + Assets/Translations/en.json | 4 +- Assets/Translations/es.json | 3 + Assets/Translations/fr.json | 3 + Assets/Translations/pt.json | 3 + Assets/Translations/zh-CN.json | 3 + Widgets/NTextInput.qml | 153 ++++++++++++++++++++++----------- 7 files changed, 120 insertions(+), 52 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a3318920..907757a3 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -927,6 +927,9 @@ }, "cancel": "Abbrechen", "apply": "Anwenden" + }, + "text-input": { + "clear": "Löschen" } }, "bar": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index cbad3dd8..000f36bc 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -910,6 +910,9 @@ }, "cancel": "Cancel", "apply": "Apply" + }, + "text-input": { + "clear": "Clear" } }, "bar": { @@ -1233,7 +1236,6 @@ "scan-again": "Scan again" } }, - "tooltips": { "refresh": "Refresh", "close": "Close", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 2db6da8f..c95ed9da 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -903,6 +903,9 @@ }, "cancel": "Cancelar", "apply": "Aplicar" + }, + "text-input": { + "clear": "Borrar" } }, "bar": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8131592a..b27af887 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -903,6 +903,9 @@ }, "cancel": "Annuler", "apply": "Appliquer" + }, + "text-input": { + "clear": "Effacer" } }, "bar": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index dd3fe7f0..52bab731 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -903,6 +903,9 @@ }, "cancel": "Cancelar", "apply": "Aplicar" + }, + "text-input": { + "clear": "Limpar" } }, "bar": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 4738030b..c6d34fcd 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -903,6 +903,9 @@ }, "cancel": "取消", "apply": "应用" + }, + "text-input": { + "clear": "清除" } }, "bar": { diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index adb83cd3..784bd199 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -9,6 +9,7 @@ ColumnLayout { property string label: "" property string description: "" + property string inputIconName: "" property bool readOnly: false property bool enabled: true property color labelColor: Color.mOnSurface @@ -106,74 +107,124 @@ ColumnLayout { id: inputContainer anchors.fill: parent anchors.leftMargin: Style.marginM - anchors.rightMargin: Style.marginM + // anchors.rightMargin: Style.marginM + clip: true z: 1 - TextField { - id: input - + RowLayout { anchors.fill: parent - verticalAlignment: TextInput.AlignVCenter - echoMode: TextInput.Normal - readOnly: root.readOnly - enabled: root.enabled - color: Color.mOnSurface - placeholderTextColor: Qt.alpha(Color.mOnSurfaceVariant, 0.6) + NIcon { + id: inputIcon + icon: root.inputIconName - selectByMouse: true + visible: root.inputIconName !== "" + enabled: false - topPadding: 0 - bottomPadding: 0 - leftPadding: 0 - rightPadding: 0 + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.topMargin: 0 + anchors.bottomMargin: 0 + anchors.leftMargin: 0 + anchors.rightMargin: 0 + } - background: null + TextField { + id: input - font.family: root.fontFamily - font.pointSize: root.fontSize * Style.uiScaleRatio - font.weight: root.fontWeight + anchors.left: inputIcon.visible ? inputIcon.right : parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: clearButton.left + anchors.leftMargin: inputIcon.visible ? Style.marginS : 0 - onEditingFinished: root.editingFinished() + verticalAlignment: TextInput.AlignVCenter - // Override mouse handling to prevent propagation - MouseArea { - id: textFieldMouse - anchors.fill: parent - acceptedButtons: Qt.AllButtons - preventStealing: true - propagateComposedEvents: false - cursorShape: Qt.IBeamCursor + echoMode: TextInput.Normal + readOnly: root.readOnly + enabled: root.enabled + color: Color.mOnSurface + placeholderTextColor: Qt.alpha(Color.mOnSurfaceVariant, 0.6) - property int selectionStart: 0 + selectByMouse: true - onPressed: mouse => { - mouse.accepted = true - input.forceActiveFocus() - var pos = input.positionAt(mouse.x, mouse.y) - input.cursorPosition = pos - selectionStart = pos - } + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 - onPositionChanged: mouse => { - if (mouse.buttons & Qt.LeftButton) { - mouse.accepted = true - var pos = input.positionAt(mouse.x, mouse.y) - input.select(selectionStart, pos) - } - } + background: null - onDoubleClicked: mouse => { - mouse.accepted = true - input.selectAll() - } + font.family: root.fontFamily + font.pointSize: root.fontSize * Style.uiScaleRatio + font.weight: root.fontWeight - onReleased: mouse => { + onEditingFinished: root.editingFinished() + + // Override mouse handling to prevent propagation + MouseArea { + id: textFieldMouse + anchors.fill: parent + acceptedButtons: Qt.AllButtons + preventStealing: true + propagateComposedEvents: false + cursorShape: Qt.IBeamCursor + + property int selectionStart: 0 + + onPressed: mouse => { mouse.accepted = true + input.forceActiveFocus() + var pos = input.positionAt(mouse.x, mouse.y) + input.cursorPosition = pos + selectionStart = pos } - onWheel: wheel => { - wheel.accepted = true - } + + onPositionChanged: mouse => { + if (mouse.buttons & Qt.LeftButton) { + mouse.accepted = true + var pos = input.positionAt(mouse.x, mouse.y) + input.select(selectionStart, pos) + } + } + + onDoubleClicked: mouse => { + mouse.accepted = true + input.selectAll() + } + + onReleased: mouse => { + mouse.accepted = true + } + onWheel: wheel => { + wheel.accepted = true + } + } + } + NIconButton { + id: clearButton + icon: "x" + tooltipText: I18n.tr("widgets.text-input.clear") + + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.topMargin: 0 + anchors.bottomMargin: 0 + anchors.leftMargin: 0 + anchors.rightMargin: 0 + border.width: 0 + + colorBg: Color.transparent + colorBgHover: Color.transparent + colorFgHover: Color.mTertiary + + visible: input.text.length > 0 && !root.readOnly + enabled: input.text.length > 0 && !root.readOnly + + onClicked: { + input.clear() + input.forceActiveFocus() + } } } } From 75417c1fa595a46a0131fb978309a1e595b341ae Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Sun, 19 Oct 2025 13:16:49 -0400 Subject: [PATCH 62/76] Fixed warnings because I was using Anchors and Layout at the same time --- Widgets/NTextInput.qml | 102 +++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 56 deletions(-) diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index 784bd199..7d81ecc0 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -75,31 +75,31 @@ ColumnLayout { propagateComposedEvents: false onPressed: mouse => { - mouse.accepted = true - // Focus the input and position cursor - input.forceActiveFocus() - var inputPos = mapToItem(inputContainer, mouse.x, mouse.y) - if (inputPos.x >= 0 && inputPos.x <= inputContainer.width) { - var textPos = inputPos.x - Style.marginM - if (textPos >= 0 && textPos <= input.width) { - input.cursorPosition = input.positionAt(textPos, input.height / 2) - } - } - } + mouse.accepted = true + // Focus the input and position cursor + input.forceActiveFocus() + var inputPos = mapToItem(inputContainer, mouse.x, mouse.y) + if (inputPos.x >= 0 && inputPos.x <= inputContainer.width) { + var textPos = inputPos.x - Style.marginM + if (textPos >= 0 && textPos <= input.width) { + input.cursorPosition = input.positionAt(textPos, input.height / 2) + } + } + } onReleased: mouse => { - mouse.accepted = true - } + mouse.accepted = true + } onDoubleClicked: mouse => { - mouse.accepted = true - input.selectAll() - } + mouse.accepted = true + input.selectAll() + } onPositionChanged: mouse => { - mouse.accepted = true - } + mouse.accepted = true + } onWheel: wheel => { - wheel.accepted = true - } + wheel.accepted = true + } } // Container for the actual text field @@ -113,6 +113,7 @@ ColumnLayout { RowLayout { anchors.fill: parent + spacing: 0 NIcon { id: inputIcon @@ -121,22 +122,15 @@ ColumnLayout { visible: root.inputIconName !== "" enabled: false - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.topMargin: 0 - anchors.bottomMargin: 0 - anchors.leftMargin: 0 - anchors.rightMargin: 0 + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: visible ? Style.marginS : 0 } TextField { id: input - anchors.left: inputIcon.visible ? inputIcon.right : parent.left - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: clearButton.left - anchors.leftMargin: inputIcon.visible ? Style.marginS : 0 + Layout.fillWidth: true + Layout.fillHeight: true verticalAlignment: TextInput.AlignVCenter @@ -173,32 +167,32 @@ ColumnLayout { property int selectionStart: 0 onPressed: mouse => { - mouse.accepted = true - input.forceActiveFocus() - var pos = input.positionAt(mouse.x, mouse.y) - input.cursorPosition = pos - selectionStart = pos - } + mouse.accepted = true + input.forceActiveFocus() + var pos = input.positionAt(mouse.x, mouse.y) + input.cursorPosition = pos + selectionStart = pos + } onPositionChanged: mouse => { - if (mouse.buttons & Qt.LeftButton) { - mouse.accepted = true - var pos = input.positionAt(mouse.x, mouse.y) - input.select(selectionStart, pos) - } - } + if (mouse.buttons & Qt.LeftButton) { + mouse.accepted = true + var pos = input.positionAt(mouse.x, mouse.y) + input.select(selectionStart, pos) + } + } onDoubleClicked: mouse => { - mouse.accepted = true - input.selectAll() - } + mouse.accepted = true + input.selectAll() + } onReleased: mouse => { - mouse.accepted = true - } + mouse.accepted = true + } onWheel: wheel => { - wheel.accepted = true - } + wheel.accepted = true + } } } NIconButton { @@ -206,12 +200,8 @@ ColumnLayout { icon: "x" tooltipText: I18n.tr("widgets.text-input.clear") - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.topMargin: 0 - anchors.bottomMargin: 0 - anchors.leftMargin: 0 - anchors.rightMargin: 0 + Layout.alignment: Qt.AlignVCenter + border.width: 0 colorBg: Color.transparent From 87af2e86cc739fb310d11db6fc31c57ab4145004 Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Sun, 19 Oct 2025 13:27:49 -0400 Subject: [PATCH 63/76] Add magnifying glass to NSearchableComboBox --- Widgets/NSearchableComboBox.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index a9a578f4..7d662278 100644 --- a/Widgets/NSearchableComboBox.qml +++ b/Widgets/NSearchableComboBox.qml @@ -173,6 +173,7 @@ RowLayout { // Search input NTextInput { id: searchInput + inputIconName: "search" Layout.fillWidth: true placeholderText: root.searchPlaceholder text: root.searchText From 6bfd93f7cc7fa59ae39ecfec5f3ed8de59fff07a Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Sun, 19 Oct 2025 20:20:45 -0400 Subject: [PATCH 64/76] combine searchInput and LocationInput elegantly in NFilePicker --- Widgets/NFilePicker.qml | 295 ++++++++++++++++++---------------------- 1 file changed, 134 insertions(+), 161 deletions(-) diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index cc318f02..261f31be 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -39,8 +39,8 @@ Popup { function openFilePicker() { if (!root.currentPath) root.currentPath = root.initialPath - shouldResetSelection = true - open() + shouldResetSelection = true + open() } function getFileIcon(fileName) { @@ -92,18 +92,18 @@ Popup { function formatFileSize(bytes) { if (bytes === 0) return "0 B" - const k = 1024, sizes = ["B", "KB", "MB", "GB", "TB"] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i] + const k = 1024, sizes = ["B", "KB", "MB", "GB", "TB"] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i] } function confirmSelection() { if (filePickerPanel.currentSelection.length === 0) return - root.selectedPaths = filePickerPanel.currentSelection - root.accepted(filePickerPanel.currentSelection) - root.close() + root.selectedPaths = filePickerPanel.currentSelection + root.accepted(filePickerPanel.currentSelection) + root.close() } function updateFilteredModel() { @@ -126,14 +126,14 @@ Popup { if (root.selectionMode === "folders" && !fileIsDir) continue - if (searchText === "" || fileName.toLowerCase().includes(searchText)) { - filteredModel.append({ - "fileName": fileName, - "filePath": filePath, - "fileIsDir": fileIsDir, - "fileSize": fileSize - }) - } + if (searchText === "" || fileName.toLowerCase().includes(searchText)) { + filteredModel.append({ + "fileName": fileName, + "filePath": filePath, + "fileIsDir": fileIsDir, + "fileSize": fileSize + }) + } } } @@ -165,19 +165,19 @@ Popup { focus: true Keys.onPressed: event => { - if (event.modifiers & Qt.ControlModifier && event.key === Qt.Key_F) { - filePickerPanel.showSearchBar = !filePickerPanel.showSearchBar - if (filePickerPanel.showSearchBar) - Qt.callLater(() => searchInput.forceActiveFocus()) - event.accepted = true - } else if (event.key === Qt.Key_Escape && filePickerPanel.showSearchBar) { - filePickerPanel.showSearchBar = false - filePickerPanel.searchText = "" - filePickerPanel.filterText = "" - root.updateFilteredModel() - event.accepted = true - } - } + if (event.modifiers & Qt.ControlModifier && event.key === Qt.Key_F) { + filePickerPanel.showSearchBar = !filePickerPanel.showSearchBar + if (filePickerPanel.showSearchBar) + Qt.callLater(() => searchInput.forceActiveFocus()) + event.accepted = true + } else if (event.key === Qt.Key_Escape && filePickerPanel.showSearchBar) { + filePickerPanel.showSearchBar = false + filePickerPanel.searchText = "" + filePickerPanel.filterText = "" + root.updateFilteredModel() + event.accepted = true + } + } ColumnLayout { anchors.fill: parent @@ -296,6 +296,10 @@ Popup { text: root.currentPath placeholderText: "Enter path..." Layout.fillWidth: true + + visible: !filePickerPanel.showSearchBar + enabled: !filePickerPanel.showSearchBar + onEditingFinished: { const newPath = text.trim() if (newPath !== "" && newPath !== root.currentPath) { @@ -314,6 +318,30 @@ Popup { } } + // Search bar + NTextInput { + id: searchInput + inputIconName: "search" + placeholderText: I18n.tr("widget.file-picker.search-placeholder") + Layout.fillWidth: true + + visible: filePickerPanel.showSearchBar + enabled: filePickerPanel.showSearchBar + + text: filePickerPanel.searchText + onTextChanged: { + filePickerPanel.searchText = text + filePickerPanel.filterText = text + root.updateFilteredModel() + } + Keys.onEscapePressed: { + filePickerPanel.showSearchBar = false + filePickerPanel.searchText = "" + filePickerPanel.filterText = "" + root.updateFilteredModel() + } + } + NIconButton { icon: filePickerPanel.viewMode ? "filepicker-list" : "filepicker-layout-grid" tooltipText: filePickerPanel.viewMode ? "List View" : "Grid View" @@ -336,61 +364,6 @@ Popup { } } - // Search bar - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 45 - color: Color.mSurfaceVariant - radius: Style.radiusS - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS) - visible: filePickerPanel.showSearchBar - - RowLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: Style.marginS - anchors.rightMargin: Style.marginS - spacing: Style.marginS - - NIcon { - icon: "filepicker-search" - color: Color.mOnSurfaceVariant - pointSize: Style.fontSizeS - } - NTextInput { - id: searchInput - placeholderText: I18n.tr("widget.file-picker.search-placeholder") - Layout.fillWidth: true - text: filePickerPanel.searchText - onTextChanged: { - filePickerPanel.searchText = text - filePickerPanel.filterText = text - root.updateFilteredModel() - } - Keys.onEscapePressed: { - filePickerPanel.showSearchBar = false - filePickerPanel.searchText = "" - filePickerPanel.filterText = "" - root.updateFilteredModel() - } - } - NIconButton { - icon: "filepicker-x" - tooltipText: I18n.tr("tooltips.clear") - baseSize: Style.baseWidgetSize * 0.6 - visible: filePickerPanel.searchText.length > 0 - onClicked: { - searchInput.text = "" - filePickerPanel.searchText = "" - filePickerPanel.filterText = "" - root.updateFilteredModel() - } - } - } - } - // File list area Rectangle { Layout.fillWidth: true @@ -500,11 +473,11 @@ Popup { bottomMargin: Style.marginS ScrollBar.vertical: scrollBarComponent.createObject(gridView, { - "parent": gridView, - "x": gridView.mirrored ? 0 : gridView.width - width, - "y": 0, - "height": gridView.height - }) + "parent": gridView, + "x": gridView.mirrored ? 0 : gridView.width - width, + "y": 0, + "height": gridView.height + }) delegate: Rectangle { id: gridItem @@ -560,8 +533,8 @@ Popup { property bool isImage: { if (model.fileIsDir) return false - const ext = model.fileName.split('.').pop().toLowerCase() - return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'].includes(ext) + const ext = model.fileName.split('.').pop().toLowerCase() + return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'].includes(ext) } Image { @@ -601,10 +574,10 @@ Popup { color: { if (isSelected) return Color.mSecondary - else if (mouseArea.containsMouse) - return model.fileIsDir ? Color.mOnTertiary : Color.mOnTertiary - else - return model.fileIsDir ? Color.mPrimary : Color.mOnSurfaceVariant + else if (mouseArea.containsMouse) + return model.fileIsDir ? Color.mOnTertiary : Color.mOnTertiary + else + return model.fileIsDir ? Color.mPrimary : Color.mOnSurfaceVariant } anchors.centerIn: parent visible: !iconContainer.isImage || thumbnail.status !== Image.Ready @@ -635,10 +608,10 @@ Popup { color: { if (isSelected) return Color.mSecondary - else if (mouseArea.containsMouse) - return Color.mOnTertiary - else - return Color.mOnSurfaceVariant + else if (mouseArea.containsMouse) + return Color.mOnTertiary + else + return Color.mOnSurfaceVariant } pointSize: Style.fontSizeS font.weight: isSelected ? Style.fontWeightBold : Style.fontWeightRegular @@ -657,37 +630,37 @@ Popup { acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - // In folder mode, single click selects the folder - if (root.selectionMode === "folders") { - filePickerPanel.currentSelection = [model.filePath] - } - // In file mode, single click on folder does nothing (must double-click to enter) - } else { - // Single click on file selects it (only in file mode) - if (root.selectionMode === "files") { - filePickerPanel.currentSelection = [model.filePath] - } - } - } - } + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // In folder mode, single click selects the folder + if (root.selectionMode === "folders") { + filePickerPanel.currentSelection = [model.filePath] + } + // In file mode, single click on folder does nothing (must double-click to enter) + } else { + // Single click on file selects it (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath] + } + } + } + } onDoubleClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - // Double-click on folder always navigates into it - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath - } else { - // Double-click on file selects and confirms (only in file mode) - if (root.selectionMode === "files") { - filePickerPanel.currentSelection = [model.filePath] - root.confirmSelection() - } - } - } - } + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // Double-click on folder always navigates into it + folderModel.folder = "file://" + model.filePath + root.currentPath = model.filePath + } else { + // Double-click on file selects and confirms (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath] + root.confirmSelection() + } + } + } + } } } } @@ -707,9 +680,9 @@ Popup { color: { if (filePickerPanel.currentSelection.includes(model.filePath)) return Color.mSecondary - if (mouseArea.containsMouse) - return Color.mTertiary - return Color.transparent + if (mouseArea.containsMouse) + return Color.mTertiary + return Color.transparent } radius: Style.radiusS Behavior on color { @@ -755,37 +728,37 @@ Popup { acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - // In folder mode, single click selects the folder - if (root.selectionMode === "folders") { - filePickerPanel.currentSelection = [model.filePath] - } - // In file mode, single click on folder does nothing (must double-click to enter) - } else { - // Single click on file selects it (only in file mode) - if (root.selectionMode === "files") { - filePickerPanel.currentSelection = [model.filePath] - } - } - } - } + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // In folder mode, single click selects the folder + if (root.selectionMode === "folders") { + filePickerPanel.currentSelection = [model.filePath] + } + // In file mode, single click on folder does nothing (must double-click to enter) + } else { + // Single click on file selects it (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath] + } + } + } + } onDoubleClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - // Double-click on folder always navigates into it - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath - } else { - // Double-click on file selects and confirms (only in file mode) - if (root.selectionMode === "files") { - filePickerPanel.currentSelection = [model.filePath] - root.confirmSelection() - } - } - } - } + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // Double-click on folder always navigates into it + folderModel.folder = "file://" + model.filePath + root.currentPath = model.filePath + } else { + // Double-click on file selects and confirms (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath] + root.confirmSelection() + } + } + } + } } } } @@ -843,7 +816,7 @@ Popup { Component.onCompleted: { if (!root.currentPath) root.currentPath = root.initialPath - folderModel.folder = "file://" + root.currentPath + folderModel.folder = "file://" + root.currentPath } } } From 25c26a63340c16a289a3541264783dcbc979efee Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Sun, 19 Oct 2025 23:46:21 -0400 Subject: [PATCH 65/76] Small color tweak to make clear button less intrusive --- Widgets/NTextInput.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index 7d81ecc0..c3fe1c9d 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -206,6 +206,7 @@ ColumnLayout { colorBg: Color.transparent colorBgHover: Color.transparent + colorFg: Color.mOnSurface colorFgHover: Color.mTertiary visible: input.text.length > 0 && !root.readOnly From 53a8706f603fbb24663d80d7362ea523584ac0bf Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Sun, 19 Oct 2025 23:52:27 -0400 Subject: [PATCH 66/76] Another small color tweak --- Widgets/NTextInput.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index c3fe1c9d..6043a51d 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -207,7 +207,7 @@ ColumnLayout { colorBg: Color.transparent colorBgHover: Color.transparent colorFg: Color.mOnSurface - colorFgHover: Color.mTertiary + colorFgHover: Color.mError visible: input.text.length > 0 && !root.readOnly enabled: input.text.length > 0 && !root.readOnly From d6958aca9ea4864348626221a994a3f0c0ed9e79 Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Mon, 20 Oct 2025 00:16:05 -0400 Subject: [PATCH 67/76] Used Il8n Search placeholder for search box in nFilePicker --- Widgets/NFilePicker.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index 261f31be..b9bd7863 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -322,7 +322,7 @@ Popup { NTextInput { id: searchInput inputIconName: "search" - placeholderText: I18n.tr("widget.file-picker.search-placeholder") + placeholderText: I18n.tr("placeholders.search") Layout.fillWidth: true visible: filePickerPanel.showSearchBar From 165ef4c9bce5a10600de2738335cb2feabc55149 Mon Sep 17 00:00:00 2001 From: Sridou Date: Sun, 19 Oct 2025 17:20:51 +0530 Subject: [PATCH 68/76] matugen theming for vicinae launcher do not overwrite logo if it exists --- Assets/MatugenTemplates/vicinae.toml | 140 +++++++++++++++++++++++ Assets/Translations/en.json | 4 + Bin/colors-apply.sh | 9 +- Commons/Settings.qml | 1 + Modules/Settings/Tabs/ColorSchemeTab.qml | 19 +++ Services/MatugenTemplates.qml | 8 ++ Services/ProgramCheckerService.qml | 4 +- 7 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 Assets/MatugenTemplates/vicinae.toml diff --git a/Assets/MatugenTemplates/vicinae.toml b/Assets/MatugenTemplates/vicinae.toml new file mode 100644 index 00000000..e4cc6da6 --- /dev/null +++ b/Assets/MatugenTemplates/vicinae.toml @@ -0,0 +1,140 @@ +# Vicinae Matugen Theme Template +# Used LLM for initial generation, then modified to a satisfactory level + +[meta] +name = "Matugen" +description = "Material You theme generated by Matugen - {{mode}} variant" +variant = "{{mode}}" +icon = "noctalia.svg" + +# ============================================================================ +# Core Colors +# ============================================================================ +# Foundation colors that define the entire theme's personality +# Uses Material You's semantic color system for perfect harmony + +[colors.core] +accent = "{{colors.primary.default.hex}}" +accent_foreground = "{{colors.on_primary.default.hex}}" +background = "{{colors.surface.default.hex}}" +foreground = "{{colors.on_surface.default.hex}}" +secondary_background = "{{colors.surface_container.default.hex}}" +border = "{{colors.outline_variant.default.hex}}" + +# ============================================================================ +# Window-Specific Colors +# ============================================================================ +# Different borders for visual hierarchy between window types + +[colors.main_window] +border = "{{colors.outline_variant.default.hex}}" + +[colors.settings_window] +border = "{{colors.outline.default.hex}}" + +# ============================================================================ +# Accent Colors +# ============================================================================ +# Complete color palette using Material You's harmonious color generation +# Maps to various UI elements throughout Vicinae + +[colors.accents] +blue = "{{colors.primary.default.hex}}" +green = "{{colors.tertiary.default.hex}}" +magenta = "{{colors.secondary.default.hex}}" +orange = "{{colors.error.default.hex}}" +red = "{{colors.error.default.hex}}" +yellow = "{{colors.tertiary.default.hex}}" +cyan = "{{colors.primary.default.hex}}" +purple = "{{colors.secondary.default.hex}}" + +# ============================================================================ +# Text Colors +# ============================================================================ +# Semantic text colors for all states and contexts + +[colors.text] +default = "{{colors.on_surface.default.hex}}" +muted = "{{colors.on_surface_variant.default.hex}}" +danger = "{{colors.error.default.hex}}" +success = "{{colors.tertiary.default.hex}}" +placeholder = "{{colors.on_surface_variant.default.hex}}" + +[colors.text.selection] +background = "{{colors.primary.default.hex}}" +foreground = "{{colors.on_primary.default.hex}}" + +[colors.text.links] +default = "{{colors.primary.default.hex}}" +visited = "{{colors.secondary.default.hex}}" + +# ============================================================================ +# Input Fields +# ============================================================================ +# Text inputs, search boxes with proper focus states + +[colors.input] +border = "{{colors.outline.default.hex}}" +border_focus = "{{colors.primary.default.hex}}" +border_error = "{{colors.error.default.hex}}" + +# ============================================================================ +# Buttons +# ============================================================================ +# All button states with subtle hover effects and focus indicators + +[colors.button.primary] +background = "{{colors.surface_container_high.default.hex}}" +foreground = "{{colors.on_surface.default.hex}}" + +[colors.button.primary.hover] +background = { name = "{{colors.surface_container_highest.default.hex}}", opacity = 0.85 } + +[colors.button.primary.focus] +outline = "colors.core.accent" + +# ============================================================================ +# Lists +# ============================================================================ +# Horizontal list views with elevation-based selection + +[colors.list.item.hover] +background = { name = "{{colors.surface_container_high.default.hex}}", opacity = 0.7 } +foreground = "{{colors.on_surface.default.hex}}" + +[colors.list.item.selection] +background = { name = "{{colors.primary_container.default.hex}}", opacity = 0.5 } +foreground = "{{colors.on_primary_container.default.hex}}" +secondary_background = "{{colors.primary_container.default.hex}}" +secondary_foreground = "{{colors.on_primary_container.default.hex}}" + +# ============================================================================ +# Grid Items +# ============================================================================ +# Grid layouts (icon grids, tiles) with outline-based selection + +[colors.grid.item] +background = "{{colors.surface_container.default.hex}}" + +[colors.grid.item.hover] +outline = { name = "{{colors.primary.default.hex}}", opacity = 0.6 } + +[colors.grid.item.selection] +outline = { name = "{{colors.primary.default.hex}}", opacity = 0.9 } + +# ============================================================================ +# Scrollbars +# ============================================================================ +# Subtle scrollbar styling + +[colors.scrollbars] +background = { name = "{{colors.on_surface.default.hex}}", opacity = 0.2 } + +# ============================================================================ +# Loading Indicators +# ============================================================================ +# Progress bars and spinners for loading states + +[colors.loading] +bar = "{{colors.primary.default.hex}}" +spinner = "{{colors.primary.default.hex}}" diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index cbad3dd8..35b99773 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -573,6 +573,10 @@ "description": "Write {filepath} and reload", "description-missing": "Requires {app} to be installed" }, + "vicinae": { + "description": "Write {filepath} and reload", + "description-missing": "Requires {app} to be installed" + }, "discord": { "description": "Write {filepath} for {client}", "description-missing": "No Discord client detected. Install vencord, vesktop, webcord, armcord, equibop, lightcord, or dorion." diff --git a/Bin/colors-apply.sh b/Bin/colors-apply.sh index 4c23afa1..00fe43b6 100755 --- a/Bin/colors-apply.sh +++ b/Bin/colors-apply.sh @@ -72,6 +72,13 @@ case "$APP_NAME" in fi ;; + vicinae) + echo "🎨 Applying 'matugen' theme to vicinae..." + + # Apply the theme + vicinae theme set matugen + ;; + pywalfox) echo "🎨 Updating pywalfox themes..." pywalfox update @@ -84,4 +91,4 @@ case "$APP_NAME" in ;; esac -echo "✅ Command sent for $APP_NAME." \ No newline at end of file +echo "✅ Command sent for $APP_NAME." diff --git a/Commons/Settings.qml b/Commons/Settings.qml index f98e3e3e..2a392176 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -381,6 +381,7 @@ Singleton { property bool discord_lightcord: false property bool discord_dorion: false property bool pywalfox: false + property bool vicinae: false property bool enableUserTemplates: false } diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 7cfcf595..314cc07d 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -664,6 +664,25 @@ ColumnLayout { } } } + NCheckbox { + label: "Vicinae" + description: ProgramCheckerService.vicinaeAvailable + ? I18n.tr("settings.color-scheme.templates.programs.vicinae.description", { + "filepath": "~/.local/share/vicinae/themes/matugen.toml" + }) + : I18n.tr("settings.color-scheme.templates.programs.vicinae.description-missing", { + "app": "vicinae" + }) + checked: Settings.data.templates.vicinae + enabled: ProgramCheckerService.vicinaeAvailable + opacity: ProgramCheckerService.vicinaeAvailable ? 1.0 : 0.6 + onToggled: checked => { + if (ProgramCheckerService.vicinaeAvailable) { + Settings.data.templates.vicinae = checked + AppThemeService.generate() + } + } +} } // Miscellaneous diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index 3475e444..19066213 100644 --- a/Services/MatugenTemplates.qml +++ b/Services/MatugenTemplates.qml @@ -151,6 +151,14 @@ Singleton { }], "input": "fuzzel.conf", "postHook": AppThemeService.colorsApplyScript + " fuzzel" + }, { + "name": "vicinae", + "templates": [{ + "version": "vicinae", + "output": "~/.local/share/vicinae/themes/matugen.toml" + }], + "input": "vicinae.toml", + "postHook": "cp -n " + Quickshell.shellDir + "/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && " + AppThemeService.colorsApplyScript + " vicinae" }, { "name": "pywalfox", "templates": [{ diff --git a/Services/ProgramCheckerService.qml b/Services/ProgramCheckerService.qml index 790fa71a..ca06dc0c 100644 --- a/Services/ProgramCheckerService.qml +++ b/Services/ProgramCheckerService.qml @@ -16,6 +16,7 @@ Singleton { property bool ghosttyAvailable: false property bool footAvailable: false property bool fuzzelAvailable: false + property bool vicinaeAvailable: false property bool gpuScreenRecorderAvailable: false property bool wlsunsetAvailable: false property bool app2unitAvailable: false @@ -96,7 +97,8 @@ Singleton { "kittyAvailable": ["which", "kitty"], "ghosttyAvailable": ["which", "ghostty"], "footAvailable": ["which", "foot"], - "fuzzelAvailable": ["which", "fuzzel"], + "fuzzelAvailable": ["which", "fuzzel"], + "vicinaeAvailable": ["which", "vicinae"], "app2unitAvailable": ["which", "app2unit"], "gpuScreenRecorderAvailable": ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"], "wlsunsetAvailable": ["which", "wlsunset"] From 96a71d8607c25d68e6f32b521bc948111e9d6a0f Mon Sep 17 00:00:00 2001 From: Sridou Date: Mon, 20 Oct 2025 11:42:03 +0530 Subject: [PATCH 69/76] added translations for vicinae --- Assets/Translations/de.json | 4 ++++ Assets/Translations/es.json | 4 ++++ Assets/Translations/fr.json | 4 ++++ Assets/Translations/pt.json | 4 ++++ Assets/Translations/zh-CN.json | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a3318920..868171a8 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -569,6 +569,10 @@ "description": "Schreibt {filepath} und lädt neu", "description-missing": "Erfordert fuzzel Starter" }, + "vicinae": { + "description": "Schreibt {filepath} und lädt neu", + "description-missing": "Erfordert {app} Starter" + }, "discord": { "description": "Schreibt {filepath} für {client}", "description-missing": "Kein Discord-Client erkannt. Installieren Sie vencord, vesktop, webcord, armcord, equibop, lightcord oder dorion." diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 2db6da8f..3db89e79 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -562,6 +562,10 @@ "description": "Escribir {filepath} y recargar", "description-missing": "Requiere que {app} esté instalado" }, + "vicinae": { + "description": "Escribir {filepath} y recargar", + "description-missing": "Requiere que {app} esté instalado" + }, "discord": { "description": "Escribir {filepath} para {client}", "description-missing": "No se detectó cliente de Discord. Instala vencord, vesktop, webcord, armcord, equibop, lightcord o dorion." diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8131592a..15533f02 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -562,6 +562,10 @@ "description": "Écrire ~/.config/fuzzel/themes/noctalia et recharger", "description-missing": "Nécessite que le lanceur fuzzel soit installé" }, + "vicinae": { + "description": "Écrire {filepath} et recharger", + "description-missing": "Nécessite que le lanceur {app} soit installé" + }, "discord": { "description": "Écrire {filepath} pour {client}", "description-missing": "Aucun client Discord détecté. Installez vencord, vesktop, webcord, armcord, equibop, lightcord ou dorion." diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index dd3fe7f0..2e8d3e8a 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -524,6 +524,10 @@ "description": "Escrever {filepath} e recarregar", "description-missing": "Requer que o {app} esteja instalado" }, + "vicinae": { + "description": "Escrever {filepath} e recarregar", + "description-missing": "Requer que o {app} esteja instalado" + }, "discord": { "description": "Escrever {filepath} para {client}", "description-missing": "Nenhum cliente Discord detectado. Instale vencord, vesktop, webcord, armcord, equibop, lightcord ou dorion." diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 4738030b..6a2ccb10 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -562,6 +562,10 @@ "description": "写入 {filepath} 并重新加载", "description-missing": "需要安装 {app}" }, + "vicinae": { + "description": "写入 {filepath} 并重新加载", + "description-missing": "需要安装 {app}" + }, "discord": { "description": "为 {client} 写入 {filepath}", "description-missing": "未检测到 Discord 客户端。请安装 vencord、vesktop、webcord、armcord、equibop、lightcord 或 dorion。" From ce3e422ea9e802d2b82eaf46e87a899768a7c7ad Mon Sep 17 00:00:00 2001 From: lysec Date: Mon, 20 Oct 2025 14:04:20 +0200 Subject: [PATCH 70/76] NFilePicker: add missing tooltip translation --- Assets/Translations/de.json | 2 ++ Assets/Translations/en.json | 2 ++ Assets/Translations/es.json | 2 ++ Assets/Translations/fr.json | 2 ++ Assets/Translations/pt.json | 2 ++ Assets/Translations/zh-CN.json | 2 ++ 6 files changed, 12 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a653d0f6..8e7cc3a5 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1268,6 +1268,8 @@ "tooltips": { "refresh": "Aktualisieren", "close": "Schließen", + "up": "Nach oben", + "home": "Home", "refresh-wallpaper-list": "Hintergrundbild-Liste aktualisieren", "refresh-devices": "Geräte aktualisieren", "forget-network": "Netzwerk vergessen", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index eccee011..88acd5c9 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1243,6 +1243,8 @@ "tooltips": { "refresh": "Refresh", "close": "Close", + "up": "Up", + "home": "Home", "refresh-wallpaper-list": "Refresh wallpaper list", "refresh-devices": "Refresh devices", "forget-network": "Forget network", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 5f5c6035..68d2010c 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1244,6 +1244,8 @@ "tooltips": { "refresh": "Actualizar", "close": "Cerrar", + "up": "Arriba", + "home": "Inicio", "refresh-wallpaper-list": "Actualizar lista de fondos de pantalla", "refresh-devices": "Actualizar dispositivos", "forget-network": "Olvidar red", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index c6780814..b20b2308 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1244,6 +1244,8 @@ "tooltips": { "refresh": "Actualiser", "close": "Fermer", + "up": "Remonter", + "home": "Accueil", "refresh-wallpaper-list": "Actualiser la liste des fonds d'écran", "refresh-devices": "Actualiser les appareils", "forget-network": "Oublier le réseau", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 20f40553..d78a4ec6 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1244,6 +1244,8 @@ "tooltips": { "refresh": "Atualizar", "close": "Fechar", + "up": "Acima", + "home": "Início", "refresh-wallpaper-list": "Atualizar lista de papéis de parede", "refresh-devices": "Atualizar dispositivos", "forget-network": "Esquecer rede", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 8bc00986..0159a41a 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1244,6 +1244,8 @@ "tooltips": { "refresh": "刷新", "close": "关闭", + "up": "向上", + "home": "主目录", "refresh-wallpaper-list": "刷新壁纸列表", "refresh-devices": "刷新设备", "forget-network": "忘记网络", From b9d9a07e07153dce62cea1569e46890b67600898 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Mon, 20 Oct 2025 20:45:05 +0800 Subject: [PATCH 71/76] fix(theme): Add vicinae to preset theme generation --- Services/AppThemeService.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index 49d7bd60..ac5eb72a 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -66,6 +66,13 @@ Singleton { "outputs": [{ "path": "~/.config/vesktop/themes/noctalia.theme.css" }] + }, + "vicinae": { + "input": "vicinae.toml", + "outputs": [{ + "path": "~/.local/share/vicinae/themes/matugen.toml" + }], + "postProcess": () => `cp -n ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${colorsApplyScript} vicinae\n` } }) From 92c5a70f8fec20a1541302f678bff4461f389f6a Mon Sep 17 00:00:00 2001 From: lysec Date: Mon, 20 Oct 2025 15:28:36 +0200 Subject: [PATCH 72/76] i18n/de: partly better translation --- Assets/Translations/de.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 8e7cc3a5..01311b44 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -5,13 +5,13 @@ "profile": { "section": { "label": "Profil", - "description": "Bearbeiten Sie Ihre Benutzerdaten und Ihren Avatar." + "description": "Hier kannst du deinen Avatar ändern." }, "picture": { - "label": "{user}s Profilbild", - "description": "Ihr Profilbild, das in der gesamten Benutzeroberfläche angezeigt wird." + "label": "{user}s Avatar", + "description": "Der Avatar, der in der gesamten Benutzeroberfläche angezeigt wird." }, - "select-avatar": "Avatar-Bild auswählen" + "select-avatar": "Avatar auswählen" }, "screen-corners": { "section": { @@ -35,7 +35,7 @@ "lockscreen": { "section": { "label": "Sperrbildschirm", - "description": "Sperrbildschirm-Verhalten konfigurieren." + "description": "Hier kannst du deinen Sperrbildschirm konfigurieren." }, "lock-on-suspend": { "label": "Beim Standby sperren", @@ -45,15 +45,15 @@ "fonts": { "section": { "label": "Schriftarten", - "description": "Wählen Sie die in der Benutzeroberfläche verwendeten Schriftarten." + "description": "Wählen die Schriftart an die auf der Benutzeroberfläche angezgit wird." }, "default": { - "label": "Standard-Schriftart", + "label": "Standard Schriftart", "description": "Hauptschriftart für die gesamte Benutzeroberfläche.", "placeholder": "Standard-Schriftart auswählen...", "search-placeholder": "Schriftarten suchen...", "scale": { - "description": "Vergrößern oder verkleinern Sie die Größe des Standardtextes.", + "description": "Hier kannst du die größe des Standardtextes ändern.", "label": "Standardmäßige Schriftgröße" } }, @@ -63,7 +63,7 @@ "placeholder": "Monospace-Schriftart auswählen...", "search-placeholder": "Monospace-Schriftarten suchen...", "scale": { - "description": "Die Größe des nichtproportionalen Textes vergrößern oder verkleinern.", + "description": "Hier kannst du die größe des Monotextes ändern", "label": "Schriftgröße mit fester Breite" } }, @@ -72,11 +72,11 @@ "language": { "section": { "label": "Sprache", - "description": "Wählen Sie Ihre bevorzugte Sprache für die Anwendung." + "description": "Hier kannst du die Sprache von Noctalia ändern." }, "select": { "label": "Anwendungssprache", - "description": "Wählen Sie die in der Anwendungsoberfläche verwendete Sprache.", + "description": "Wählen die in der Anwendungsoberfläche verwendete Sprache.", "auto-detect": "Automatisch" } } From 4ee82ecbc3ca8245ca9be99f7f6e455ee238055d Mon Sep 17 00:00:00 2001 From: Absurd <158203519+4fd485@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:19:43 +0200 Subject: [PATCH 73/76] Slightly more Translatio --- Assets/Translations/de.json | 50 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 01311b44..396c0e6d 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -243,13 +243,13 @@ "widgets": { "section": { "label": "Widget-Positionierung", - "description": "Widgets per Drag & Drop neu anordnen. Abzeichen zeigen die Verwendung an: [L]inks, [M]itte, [R]echts." + "description": "Widgets per Drag & Drop neu anordnen. Symoble zeigen die Verwendung an: [L]inks, [M]itte, [R]echts." } }, "monitors": { "section": { "label": "Monitor-Anzeige", - "description": "Statusleiste auf bestimmten Monitoren anzeigen. Standard ist alle, wenn keine ausgewählt sind." + "description": "Leiste auf bestimmten Monitoren anzeigen. Standardmäßig auf allen." } }, "tray": { @@ -319,7 +319,7 @@ }, "clipboard-history": { "label": "Zwischenablage-Verlauf aktivieren", - "description": "Zugriff auf zuvor kopierte Elemente über den Starter." + "description": "Zugriff auf zuvor kopierte Elemente über den Launcher." }, "sort-by-usage": { "label": "Nach Häufigkeit sortieren", @@ -343,8 +343,8 @@ "description": "Erscheinungsbild und Verhalten von Benachrichtigungen konfigurieren." }, "do-not-disturb": { - "label": "Nicht stören", - "description": "Alle Benachrichtigungs-Popups deaktivieren, wenn aktiviert." + "label": "Bitte Nicht stören", + "description": "Alle Benachrichtigungs-Popups deaktivieren." }, "enable-osd": { "label": "Bildschirmanzeige aktivieren", @@ -384,35 +384,35 @@ "monitors": { "section": { "label": "Monitor-Anzeige", - "description": "Benachrichtigungen auf bestimmten Monitoren anzeigen. Standard ist alle, wenn keine ausgewählt sind." + "description": "Benachrichtigungen auf bestimmten Monitoren anzeigen. Standardmäßig werden sie auf allen Monitoren angezeigt" } } }, "osd": { - "title": "Bildschirmanzeige", + "title": "On-Screen Display", "description": "Bildschirm-Overlays wie Lautstärke- und Helligkeitsanzeigen konfigurieren.", "section": { "general": { "label": "Allgemein", - "description": "Sichtbarkeit und Verhalten der OSD konfigurieren." + "description": "Sichtbarkeit und Verhalten vom On-Screen Display konfigurieren." } }, "enabled": { - "label": "Bildschirmanzeige aktivieren", + "label": "On-Screen Display aktivieren", "description": "Lautstärke- und Helligkeitsänderungen in Echtzeit anzeigen." }, "always-on-top": { "label": "Immer im Vordergrund", - "description": "Bildschirmanzeige über Vollbildfenstern und anderen Ebenen anzeigen." + "description": "On-Screen Display über Vollbildfenstern und anderen Ebenen anzeigen." }, "location": { "label": "Position", - "description": "Wo Bildschirmanzeigen erscheinen." + "description": "Wo On-Screen Displays erscheinen." }, "duration": { "section": { "label": "Automatisches Ausblenden", - "description": "Wie lange die OSD sichtbar bleibt, bevor sie automatisch ausgeblendet wird." + "description": "Wie lange das On-Screen Display sichtbar bleibt, bevor es automatisch ausgeblendet wird." }, "auto-hide": { "label": "Ausblenden nach", @@ -422,7 +422,7 @@ "monitors": { "section": { "label": "Monitor-Anzeige", - "description": "OSD auf bestimmten Monitoren anzeigen. Standard ist alle, wenn keine ausgewählt sind." + "description": "On-Screen Display auf bestimmten Monitoren anzeigen. Standardmäßig auf allen angezeigt." } } }, @@ -471,7 +471,7 @@ "description": "Dauer der Übergangsanimationen in Sekunden." }, "edge-smoothness": { - "label": "Übergangskante weichzeichnen", + "label": "Übergangseffect weichzeichnen", "description": "Wendet einen weichen, gefiederten Effekt auf die Kante von Übergängen an." } }, @@ -551,15 +551,15 @@ "description": "Terminal-Emulator-Theming.", "kitty": { "description": "Schreibt {filepath} und lädt neu", - "description-missing": "Erfordert kitty Terminal" + "description-missing": "Erfordert {app} Terminal" }, "ghostty": { "description": "Schreibt {filepath} und lädt neu", - "description-missing": "Erfordert ghostty Terminal" + "description-missing": "Erfordert {app} Terminal" }, "foot": { "description": "Schreibt {filepath} und lädt neu", - "description-missing": "Erfordert foot Terminal" + "description-missing": "Erfordert {app} Terminal" } }, "programs": { @@ -567,11 +567,11 @@ "description": "Anwendungsspezifisches Theming.", "fuzzel": { "description": "Schreibt {filepath} und lädt neu", - "description-missing": "Erfordert fuzzel Starter" + "description-missing": "Erfordert die Installation von {app}" }, "vicinae": { "description": "Schreibt {filepath} und lädt neu", - "description-missing": "Erfordert {app} Starter" + "description-missing": "Erfordert die Installation von {app}" }, "discord": { "description": "Schreibt {filepath} für {client}", @@ -579,7 +579,7 @@ }, "pywalfox": { "description": "Schreibt {filepath} und führt pywalfox update aus", - "description-missing": "Erfordert pywalfox Paket" + "description-missing": "Erfordert die Installation von {app} " } }, "misc": { @@ -605,7 +605,7 @@ }, "search": { "label": "Nach einem Standort suchen", - "description": "z.B. Berlin, Deutschland", + "description": "z.B. Dortmund, Deutschland", "placeholder": "Standortnamen eingeben" } }, @@ -634,7 +634,7 @@ }, "week-numbers": { "label": "Wochennummern anzeigen", - "description": "Zeigt die Woche des Jahres (z.B. Woche 38) im Kalender an." + "description": "Zeigt die Kalender Wochen an (z.B. Woche 38)" } } }, @@ -727,7 +727,7 @@ "description_plural": "Ein Dankeschön an unsere {count} großartigen Mitwirkenden!" } }, - "support": "Unterstützen Sie uns" + "support": "Unterstütz uns" }, "hooks": { "title": "Hooks", @@ -836,7 +836,7 @@ "mainly-clear": "Überwiegend klar", "partly-cloudy": "Teilweise bewölkt", "overcast": "Bedeckt", - "fog": "Nebel", + "fog": "Nebelig", "drizzle": "Nieselregen", "snow": "Schnee", "rain-showers": "Regenschauer", @@ -852,7 +852,7 @@ "select-file": "Datei auswählen", "cancel": "Abbrechen", "search-placeholder": "Dateien und Ordner suchen...", - "select-current": "Aktuelle auswählen", + "select-current": "Aktuelles Objekt auswählen", "title": "Dateiauswahl" }, "datetime-tokens": { From 8652fdb731f4379116dc793f62da2016c4ce7cb4 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 20 Oct 2025 12:22:30 -0400 Subject: [PATCH 74/76] added missing translation --- Assets/Translations/es.json | 7 +++++++ Assets/Translations/fr.json | 7 +++++++ Assets/Translations/pt.json | 7 +++++++ Assets/Translations/zh-CN.json | 7 +++++++ 4 files changed, 28 insertions(+) diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 68d2010c..aa00ce0a 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -513,6 +513,13 @@ "switch": { "label": "Modo oscuro", "description": "Cambia a un tema más oscuro para una visualización más fácil por la noche." + }, + "mode": { + "description": "Permite el cambio automático entre el modo claro y el modo oscuro.", + "label": "Programación del modo oscuro", + "location": "Ubicación", + "manual": "Manual", + "off": "Apagado" } }, "predefined": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index b20b2308..d15b0c42 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -513,6 +513,13 @@ "switch": { "label": "Mode sombre", "description": "Passe à un thème plus sombre pour une visualisation plus facile la nuit." + }, + "mode": { + "description": "Active la commutation automatique entre le mode clair et le mode sombre.", + "label": "Programmation du mode sombre", + "location": "Emplacement", + "manual": "Manuel", + "off": "Éteint" } }, "predefined": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index d78a4ec6..b72251e7 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -475,6 +475,13 @@ "switch": { "label": "Modo escuro", "description": "Muda para um tema mais escuro para facilitar a visualização à noite." + }, + "mode": { + "description": "Ativa a mudança automática entre o modo claro e o modo escuro.", + "label": "Agendamento do modo escuro", + "location": "Localização", + "manual": "Manual", + "off": "Desligado" } }, "predefined": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 0159a41a..d9891679 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -513,6 +513,13 @@ "switch": { "label": "深色模式", "description": "切换到更暗的主题,便于夜间观看。" + }, + "mode": { + "description": "启用自动切换浅色和深色模式。", + "label": "深色模式计划", + "location": "位置", + "manual": "手册", + "off": "关" } }, "predefined": { From 621b37cd1f481f5f5fcf7622ade566050a0f8468 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 20 Oct 2025 12:22:38 -0400 Subject: [PATCH 75/76] autofmt --- Commons/I18n.qml | 1 - Modules/Settings/Tabs/ColorSchemeTab.qml | 34 ++-- Modules/Settings/Tabs/GeneralTab.qml | 28 +-- Services/AppThemeService.qml | 4 +- Services/ProgramCheckerService.qml | 4 +- Widgets/NFilePicker.qml | 212 +++++++++++------------ Widgets/NTextInput.qml | 78 ++++----- 7 files changed, 181 insertions(+), 180 deletions(-) diff --git a/Commons/I18n.qml b/Commons/I18n.qml index 4ec5e86f..59eeb922 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -8,7 +8,6 @@ import qs.Commons Singleton { id: root - property bool isLoaded: false property string langCode: "" property string systemDetectedLangCode: "" diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 314cc07d..81146436 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -665,24 +665,22 @@ ColumnLayout { } } NCheckbox { - label: "Vicinae" - description: ProgramCheckerService.vicinaeAvailable - ? I18n.tr("settings.color-scheme.templates.programs.vicinae.description", { - "filepath": "~/.local/share/vicinae/themes/matugen.toml" - }) - : I18n.tr("settings.color-scheme.templates.programs.vicinae.description-missing", { - "app": "vicinae" - }) - checked: Settings.data.templates.vicinae - enabled: ProgramCheckerService.vicinaeAvailable - opacity: ProgramCheckerService.vicinaeAvailable ? 1.0 : 0.6 - onToggled: checked => { - if (ProgramCheckerService.vicinaeAvailable) { - Settings.data.templates.vicinae = checked - AppThemeService.generate() - } - } -} + label: "Vicinae" + description: ProgramCheckerService.vicinaeAvailable ? I18n.tr("settings.color-scheme.templates.programs.vicinae.description", { + "filepath": "~/.local/share/vicinae/themes/matugen.toml" + }) : I18n.tr("settings.color-scheme.templates.programs.vicinae.description-missing", { + "app": "vicinae" + }) + checked: Settings.data.templates.vicinae + enabled: ProgramCheckerService.vicinaeAvailable + opacity: ProgramCheckerService.vicinaeAvailable ? 1.0 : 0.6 + onToggled: checked => { + if (ProgramCheckerService.vicinaeAvailable) { + Settings.data.templates.vicinae = checked + AppThemeService.generate() + } + } + } } // Miscellaneous diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index 40b5276d..4f084445 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -204,20 +204,24 @@ ColumnLayout { Layout.fillWidth: true label: I18n.tr("settings.general.language.select.label") description: I18n.tr("settings.general.language.select.description") - model: [ - { "key": "", "name": I18n.tr("settings.general.language.select.auto-detect") + " (" + I18n.systemDetectedLangCode + ")" } - ].concat(I18n.availableLanguages.map(function(langCode) { - return { "key": langCode, "name": langCode } - })) + model: [{ + "key": "", + "name": I18n.tr("settings.general.language.select.auto-detect") + " (" + I18n.systemDetectedLangCode + ")" + }].concat(I18n.availableLanguages.map(function (langCode) { + return { + "key": langCode, + "name": langCode + } + })) currentKey: Settings.data.general.language onSelected: key => { - Settings.data.general.language = key - if (key === "") { - I18n.detectLanguage() // Re-detect system language if "Automatic" is selected - } else { - I18n.setLanguage(key) // Set specific language - } - } + Settings.data.general.language = key + if (key === "") { + I18n.detectLanguage() // Re-detect system language if "Automatic" is selected + } else { + I18n.setLanguage(key) // Set specific language + } + } } } diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index ac5eb72a..c54662ce 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -70,8 +70,8 @@ Singleton { "vicinae": { "input": "vicinae.toml", "outputs": [{ - "path": "~/.local/share/vicinae/themes/matugen.toml" - }], + "path": "~/.local/share/vicinae/themes/matugen.toml" + }], "postProcess": () => `cp -n ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${colorsApplyScript} vicinae\n` } }) diff --git a/Services/ProgramCheckerService.qml b/Services/ProgramCheckerService.qml index ca06dc0c..153b35c9 100644 --- a/Services/ProgramCheckerService.qml +++ b/Services/ProgramCheckerService.qml @@ -97,8 +97,8 @@ Singleton { "kittyAvailable": ["which", "kitty"], "ghosttyAvailable": ["which", "ghostty"], "footAvailable": ["which", "foot"], - "fuzzelAvailable": ["which", "fuzzel"], - "vicinaeAvailable": ["which", "vicinae"], + "fuzzelAvailable": ["which", "fuzzel"], + "vicinaeAvailable": ["which", "vicinae"], "app2unitAvailable": ["which", "app2unit"], "gpuScreenRecorderAvailable": ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"], "wlsunsetAvailable": ["which", "wlsunset"] diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index b9bd7863..4b82e77d 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -39,8 +39,8 @@ Popup { function openFilePicker() { if (!root.currentPath) root.currentPath = root.initialPath - shouldResetSelection = true - open() + shouldResetSelection = true + open() } function getFileIcon(fileName) { @@ -92,18 +92,18 @@ Popup { function formatFileSize(bytes) { if (bytes === 0) return "0 B" - const k = 1024, sizes = ["B", "KB", "MB", "GB", "TB"] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i] + const k = 1024, sizes = ["B", "KB", "MB", "GB", "TB"] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i] } function confirmSelection() { if (filePickerPanel.currentSelection.length === 0) return - root.selectedPaths = filePickerPanel.currentSelection - root.accepted(filePickerPanel.currentSelection) - root.close() + root.selectedPaths = filePickerPanel.currentSelection + root.accepted(filePickerPanel.currentSelection) + root.close() } function updateFilteredModel() { @@ -126,14 +126,14 @@ Popup { if (root.selectionMode === "folders" && !fileIsDir) continue - if (searchText === "" || fileName.toLowerCase().includes(searchText)) { - filteredModel.append({ - "fileName": fileName, - "filePath": filePath, - "fileIsDir": fileIsDir, - "fileSize": fileSize - }) - } + if (searchText === "" || fileName.toLowerCase().includes(searchText)) { + filteredModel.append({ + "fileName": fileName, + "filePath": filePath, + "fileIsDir": fileIsDir, + "fileSize": fileSize + }) + } } } @@ -165,19 +165,19 @@ Popup { focus: true Keys.onPressed: event => { - if (event.modifiers & Qt.ControlModifier && event.key === Qt.Key_F) { - filePickerPanel.showSearchBar = !filePickerPanel.showSearchBar - if (filePickerPanel.showSearchBar) - Qt.callLater(() => searchInput.forceActiveFocus()) - event.accepted = true - } else if (event.key === Qt.Key_Escape && filePickerPanel.showSearchBar) { - filePickerPanel.showSearchBar = false - filePickerPanel.searchText = "" - filePickerPanel.filterText = "" - root.updateFilteredModel() - event.accepted = true - } - } + if (event.modifiers & Qt.ControlModifier && event.key === Qt.Key_F) { + filePickerPanel.showSearchBar = !filePickerPanel.showSearchBar + if (filePickerPanel.showSearchBar) + Qt.callLater(() => searchInput.forceActiveFocus()) + event.accepted = true + } else if (event.key === Qt.Key_Escape && filePickerPanel.showSearchBar) { + filePickerPanel.showSearchBar = false + filePickerPanel.searchText = "" + filePickerPanel.filterText = "" + root.updateFilteredModel() + event.accepted = true + } + } ColumnLayout { anchors.fill: parent @@ -473,11 +473,11 @@ Popup { bottomMargin: Style.marginS ScrollBar.vertical: scrollBarComponent.createObject(gridView, { - "parent": gridView, - "x": gridView.mirrored ? 0 : gridView.width - width, - "y": 0, - "height": gridView.height - }) + "parent": gridView, + "x": gridView.mirrored ? 0 : gridView.width - width, + "y": 0, + "height": gridView.height + }) delegate: Rectangle { id: gridItem @@ -533,8 +533,8 @@ Popup { property bool isImage: { if (model.fileIsDir) return false - const ext = model.fileName.split('.').pop().toLowerCase() - return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'].includes(ext) + const ext = model.fileName.split('.').pop().toLowerCase() + return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'].includes(ext) } Image { @@ -574,10 +574,10 @@ Popup { color: { if (isSelected) return Color.mSecondary - else if (mouseArea.containsMouse) - return model.fileIsDir ? Color.mOnTertiary : Color.mOnTertiary - else - return model.fileIsDir ? Color.mPrimary : Color.mOnSurfaceVariant + else if (mouseArea.containsMouse) + return model.fileIsDir ? Color.mOnTertiary : Color.mOnTertiary + else + return model.fileIsDir ? Color.mPrimary : Color.mOnSurfaceVariant } anchors.centerIn: parent visible: !iconContainer.isImage || thumbnail.status !== Image.Ready @@ -608,10 +608,10 @@ Popup { color: { if (isSelected) return Color.mSecondary - else if (mouseArea.containsMouse) - return Color.mOnTertiary - else - return Color.mOnSurfaceVariant + else if (mouseArea.containsMouse) + return Color.mOnTertiary + else + return Color.mOnSurfaceVariant } pointSize: Style.fontSizeS font.weight: isSelected ? Style.fontWeightBold : Style.fontWeightRegular @@ -630,37 +630,37 @@ Popup { acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - // In folder mode, single click selects the folder - if (root.selectionMode === "folders") { - filePickerPanel.currentSelection = [model.filePath] - } - // In file mode, single click on folder does nothing (must double-click to enter) - } else { - // Single click on file selects it (only in file mode) - if (root.selectionMode === "files") { - filePickerPanel.currentSelection = [model.filePath] - } - } - } - } + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // In folder mode, single click selects the folder + if (root.selectionMode === "folders") { + filePickerPanel.currentSelection = [model.filePath] + } + // In file mode, single click on folder does nothing (must double-click to enter) + } else { + // Single click on file selects it (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath] + } + } + } + } onDoubleClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - // Double-click on folder always navigates into it - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath - } else { - // Double-click on file selects and confirms (only in file mode) - if (root.selectionMode === "files") { - filePickerPanel.currentSelection = [model.filePath] - root.confirmSelection() - } - } - } - } + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // Double-click on folder always navigates into it + folderModel.folder = "file://" + model.filePath + root.currentPath = model.filePath + } else { + // Double-click on file selects and confirms (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath] + root.confirmSelection() + } + } + } + } } } } @@ -680,9 +680,9 @@ Popup { color: { if (filePickerPanel.currentSelection.includes(model.filePath)) return Color.mSecondary - if (mouseArea.containsMouse) - return Color.mTertiary - return Color.transparent + if (mouseArea.containsMouse) + return Color.mTertiary + return Color.transparent } radius: Style.radiusS Behavior on color { @@ -728,37 +728,37 @@ Popup { acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - // In folder mode, single click selects the folder - if (root.selectionMode === "folders") { - filePickerPanel.currentSelection = [model.filePath] - } - // In file mode, single click on folder does nothing (must double-click to enter) - } else { - // Single click on file selects it (only in file mode) - if (root.selectionMode === "files") { - filePickerPanel.currentSelection = [model.filePath] - } - } - } - } + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // In folder mode, single click selects the folder + if (root.selectionMode === "folders") { + filePickerPanel.currentSelection = [model.filePath] + } + // In file mode, single click on folder does nothing (must double-click to enter) + } else { + // Single click on file selects it (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath] + } + } + } + } onDoubleClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - // Double-click on folder always navigates into it - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath - } else { - // Double-click on file selects and confirms (only in file mode) - if (root.selectionMode === "files") { - filePickerPanel.currentSelection = [model.filePath] - root.confirmSelection() - } - } - } - } + if (mouse.button === Qt.LeftButton) { + if (model.fileIsDir) { + // Double-click on folder always navigates into it + folderModel.folder = "file://" + model.filePath + root.currentPath = model.filePath + } else { + // Double-click on file selects and confirms (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath] + root.confirmSelection() + } + } + } + } } } } @@ -816,7 +816,7 @@ Popup { Component.onCompleted: { if (!root.currentPath) root.currentPath = root.initialPath - folderModel.folder = "file://" + root.currentPath + folderModel.folder = "file://" + root.currentPath } } } diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index 6043a51d..f11f9ca4 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -75,31 +75,31 @@ ColumnLayout { propagateComposedEvents: false onPressed: mouse => { - mouse.accepted = true - // Focus the input and position cursor - input.forceActiveFocus() - var inputPos = mapToItem(inputContainer, mouse.x, mouse.y) - if (inputPos.x >= 0 && inputPos.x <= inputContainer.width) { - var textPos = inputPos.x - Style.marginM - if (textPos >= 0 && textPos <= input.width) { - input.cursorPosition = input.positionAt(textPos, input.height / 2) - } - } - } + mouse.accepted = true + // Focus the input and position cursor + input.forceActiveFocus() + var inputPos = mapToItem(inputContainer, mouse.x, mouse.y) + if (inputPos.x >= 0 && inputPos.x <= inputContainer.width) { + var textPos = inputPos.x - Style.marginM + if (textPos >= 0 && textPos <= input.width) { + input.cursorPosition = input.positionAt(textPos, input.height / 2) + } + } + } onReleased: mouse => { - mouse.accepted = true - } + mouse.accepted = true + } onDoubleClicked: mouse => { - mouse.accepted = true - input.selectAll() - } + mouse.accepted = true + input.selectAll() + } onPositionChanged: mouse => { - mouse.accepted = true - } + mouse.accepted = true + } onWheel: wheel => { - wheel.accepted = true - } + wheel.accepted = true + } } // Container for the actual text field @@ -167,32 +167,32 @@ ColumnLayout { property int selectionStart: 0 onPressed: mouse => { - mouse.accepted = true - input.forceActiveFocus() - var pos = input.positionAt(mouse.x, mouse.y) - input.cursorPosition = pos - selectionStart = pos - } + mouse.accepted = true + input.forceActiveFocus() + var pos = input.positionAt(mouse.x, mouse.y) + input.cursorPosition = pos + selectionStart = pos + } onPositionChanged: mouse => { - if (mouse.buttons & Qt.LeftButton) { - mouse.accepted = true - var pos = input.positionAt(mouse.x, mouse.y) - input.select(selectionStart, pos) - } - } + if (mouse.buttons & Qt.LeftButton) { + mouse.accepted = true + var pos = input.positionAt(mouse.x, mouse.y) + input.select(selectionStart, pos) + } + } onDoubleClicked: mouse => { - mouse.accepted = true - input.selectAll() - } + mouse.accepted = true + input.selectAll() + } onReleased: mouse => { - mouse.accepted = true - } + mouse.accepted = true + } onWheel: wheel => { - wheel.accepted = true - } + wheel.accepted = true + } } } NIconButton { From 73267d1d37b60c963fc4f938acab1eef8a655fe7 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 20 Oct 2025 13:33:46 -0400 Subject: [PATCH 76/76] Settings + SetupWizard - Added a Lock screen settings tabs - Added button in settings/general tab to re-run the setup wizard - Fixed missing translations - Fixed bug when matugen not installed in setup wizard - Added enabled property for NToggle --- Assets/Translations/de.json | 28 ++++++++--------- Assets/Translations/en.json | 26 +++++++-------- Assets/Translations/es.json | 28 ++++++++--------- Assets/Translations/fr.json | 28 ++++++++--------- Assets/Translations/pt.json | 28 ++++++++--------- Assets/Translations/zh-CN.json | 28 ++++++++--------- Commons/TablerIcons.qml | 1 + Modules/Settings/SettingsPanel.qml | 11 +++++++ Modules/Settings/Tabs/ColorSchemeTab.qml | 2 +- Modules/Settings/Tabs/GeneralTab.qml | 25 ++++----------- Modules/Settings/Tabs/LockScreenTab.qml | 31 ++++++++++++++++++ Modules/Settings/Tabs/UserInterfaceTab.qml | 7 ----- Modules/SetupWizard/SetupAppearanceStep.qml | 35 ++------------------- Widgets/NToggle.qml | 8 +++++ 14 files changed, 138 insertions(+), 148 deletions(-) create mode 100644 Modules/Settings/Tabs/LockScreenTab.qml diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 396c0e6d..6907ed91 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -32,16 +32,6 @@ "reset": "Eckenradius des Bildschirms zurücksetzen" } }, - "lockscreen": { - "section": { - "label": "Sperrbildschirm", - "description": "Hier kannst du deinen Sperrbildschirm konfigurieren." - }, - "lock-on-suspend": { - "label": "Beim Standby sperren", - "description": "Bildschirm automatisch sperren, wenn das System in den Standby-Modus wechselt." - } - }, "fonts": { "section": { "label": "Schriftarten", @@ -79,7 +69,8 @@ "description": "Wählen die in der Anwendungsoberfläche verwendete Sprache.", "auto-detect": "Automatisch" } - } + }, + "launch-setup-wizard": "Starte den Setup-Assistenten" }, "audio": { "title": "Audio", @@ -802,10 +793,6 @@ "label": "Eckenradius", "reset": "Rahmenradius zurücksetzen" }, - "compact-lockscreen": { - "description": "Zeige nur die Login-Eingabe und Systemsteuerung, blende Wetter- und Medien-Widgets aus.", - "label": "Kompakter Sperrbildschirm" - }, "dim-desktop": { "description": "Den Desktop abdunkeln, wenn Bedienfelder oder Menüs geöffnet sind.", "label": "Dim Desktop" @@ -824,6 +811,17 @@ "description": "Tooltips in der gesamten Benutzeroberfläche aktivieren oder deaktivieren.", "label": "Tooltips anzeigen" } + }, + "lock-screen": { + "compact-lockscreen": { + "description": "Zeige nur die Login-Eingabe und Systemsteuerung, blende Wetter- und Medien-Widgets aus.", + "label": "Kompakter Sperrbildschirm" + }, + "lock-on-suspend": { + "description": "Den Bildschirm beim Suspendieren des Systems automatisch sperren.", + "label": "Sperren beim Ruhezustand" + }, + "title": "Sperrbildschirm" } }, "general": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 88acd5c9..f8c8286f 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -2,6 +2,7 @@ "settings": { "general": { "title": "General", + "launch-setup-wizard": "Launch the setup wizard", "profile": { "section": { "label": "Profile", @@ -32,16 +33,6 @@ "reset": "Reset screen corners radius" } }, - "lockscreen": { - "section": { - "label": "Lock screen", - "description": "Configure lock screen behavior." - }, - "lock-on-suspend": { - "label": "Lock on suspend", - "description": "Automatically lock the screen when suspending the system." - } - }, "fonts": { "reset-scaling": "Reset scaling", "section": { @@ -806,10 +797,6 @@ "label": "Dim desktop", "description": "Dim the desktop when panels or menus are open." }, - "compact-lockscreen": { - "label": "Compact lock screen", - "description": "Show only the login input and system controls, hiding weather and media widgets." - }, "border-radius": { "label": "Border radius", "description": "Controls the corner roundness of windows, buttons, and other elements.", @@ -824,6 +811,17 @@ "label": "Disable UI Animations", "description": "Disable all animations for a faster, more responsive experience." } + }, + "lock-screen": { + "title": "Lock screen", + "compact-lockscreen": { + "label": "Compact lock screen", + "description": "Show only the login input and system controls, hiding weather and media widgets." + }, + "lock-on-suspend": { + "label": "Lock on suspend", + "description": "Automatically lock the screen when suspending the system." + } } }, "widgets": { diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index aa00ce0a..c6311f6d 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -32,16 +32,6 @@ "reset": "Restablecer el radio de las esquinas de la pantalla" } }, - "lockscreen": { - "section": { - "label": "Pantalla de bloqueo", - "description": "Configura el comportamiento de la pantalla de bloqueo." - }, - "lock-on-suspend": { - "label": "Bloquear al suspender", - "description": "Bloquear automáticamente la pantalla al suspender el sistema." - } - }, "fonts": { "section": { "label": "Fuentes", @@ -79,7 +69,8 @@ "description": "Selecciona el idioma utilizado en la interfaz de la aplicación.", "auto-detect": "Automático" } - } + }, + "launch-setup-wizard": "Inicie el asistente de configuración" }, "audio": { "title": "Audio", @@ -802,10 +793,6 @@ "label": "Radio de borde", "reset": "Restablecer el radio del borde" }, - "compact-lockscreen": { - "description": "Mostrar solo el campo de inicio de sesión y los controles del sistema, ocultando los widgets del clima y multimedia.", - "label": "Pantalla de bloqueo compacta" - }, "dim-desktop": { "description": "Atenuar el escritorio cuando los paneles o menús estén abiertos.", "label": "Dim escritorio" @@ -824,6 +811,17 @@ "description": "Activar o desactivar los avisos emergentes en toda la interfaz.", "label": "Mostrar sugerencias" } + }, + "lock-screen": { + "compact-lockscreen": { + "description": "Mostrar solo el campo de inicio de sesión y los controles del sistema, ocultando los widgets del clima y multimedia.", + "label": "Pantalla de bloqueo compacta" + }, + "lock-on-suspend": { + "description": "Bloquear la pantalla automáticamente al suspender el sistema.", + "label": "Bloquear al suspender" + }, + "title": "Pantalla de bloqueo" } }, "widgets": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index d15b0c42..c50cf02e 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -32,16 +32,6 @@ "reset": "Réinitialiser le rayon des coins de l'écran" } }, - "lockscreen": { - "section": { - "label": "Écran de verrouillage", - "description": "Configurer le comportement de l'écran de verrouillage." - }, - "lock-on-suspend": { - "label": "Verrouiller lors de la suspension", - "description": "Verrouiller automatiquement l'écran lors de la mise en veille du système." - } - }, "fonts": { "section": { "label": "Polices", @@ -79,7 +69,8 @@ "description": "Sélectionnez la langue utilisée dans l'interface de l'application.", "auto-detect": "Automatique" } - } + }, + "launch-setup-wizard": "Lancer l'assistant d'installation" }, "audio": { "title": "Audio", @@ -802,10 +793,6 @@ "label": "Rayon de bordure", "reset": "Réinitialiser le rayon de la bordure" }, - "compact-lockscreen": { - "description": "Afficher uniquement le champ de saisie de connexion et les commandes système, en masquant les widgets météo et multimédia.", - "label": "Écran de verrouillage compact" - }, "dim-desktop": { "description": "Atténuer le bureau lorsque des panneaux ou des menus sont ouverts.", "label": "Dim bureau" @@ -824,6 +811,17 @@ "description": "Activer ou désactiver les info-bulles dans toute l'interface.", "label": "Afficher les infobulles" } + }, + "lock-screen": { + "compact-lockscreen": { + "description": "Afficher uniquement le champ de saisie de connexion et les commandes système, en masquant les widgets météo et multimédia.", + "label": "Écran de verrouillage compact" + }, + "lock-on-suspend": { + "description": "Verrouiller automatiquement l'écran lors de la mise en veille du système.", + "label": "Verrouiller à la suspension" + }, + "title": "Écran de verrouillage" } }, "widgets": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index b72251e7..ccc2cfa1 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -32,16 +32,6 @@ "reset": "Redefinir raio dos cantos da tela" } }, - "lockscreen": { - "section": { - "label": "Tela de bloqueio", - "description": "Configure o comportamento da tela de bloqueio." - }, - "lock-on-suspend": { - "label": "Bloquear ao suspender", - "description": "Bloquear automaticamente a tela ao suspender o sistema." - } - }, "fonts": { "section": { "label": "Fontes", @@ -79,7 +69,8 @@ "description": "Selecione o idioma usado na interface da aplicação.", "auto-detect": "Automático" } - } + }, + "launch-setup-wizard": "Iniciar o assistente de configuração" }, "audio": { "title": "Áudio", @@ -802,10 +793,6 @@ "label": "Raio da borda", "reset": "Redefinir raio da borda" }, - "compact-lockscreen": { - "description": "Mostrar apenas a entrada de login e os controles do sistema, ocultando widgets de clima e mídia.", - "label": "Tela de bloqueio compacta" - }, "dim-desktop": { "description": "Escurecer a área de trabalho quando painéis ou menus estiverem abertos.", "label": "Dim área de trabalho" @@ -824,6 +811,17 @@ "description": "Ativar ou desativar dicas de ferramentas em toda a interface.", "label": "Mostrar dicas de ferramenta" } + }, + "lock-screen": { + "compact-lockscreen": { + "description": "Mostrar apenas a entrada de login e os controles do sistema, ocultando os widgets de clima e mídia.", + "label": "Tela de bloqueio compacta" + }, + "lock-on-suspend": { + "description": "Bloquear a tela automaticamente ao suspender o sistema.", + "label": "Bloquear ao suspender" + }, + "title": "Tela de bloqueio" } }, "widgets": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index d9891679..e46e952e 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -32,16 +32,6 @@ "reset": "重置屏幕圆角半径" } }, - "lockscreen": { - "section": { - "label": "锁屏", - "description": "配置锁屏行为。" - }, - "lock-on-suspend": { - "label": "挂起时锁定", - "description": "在系统挂起时自动锁定屏幕。" - } - }, "fonts": { "reset-scaling": "恢复默认缩放", "section": { @@ -79,7 +69,8 @@ "description": "选择应用程序界面中使用的语言。", "auto-detect": "自动检测" } - } + }, + "launch-setup-wizard": "启动安装向导" }, "audio": { "title": "音频", @@ -802,10 +793,6 @@ "label": "边框半径", "reset": "重置边框半径" }, - "compact-lockscreen": { - "description": "仅显示登录输入和系统控制,隐藏天气和媒体小部件。", - "label": "紧凑型锁屏" - }, "dim-desktop": { "description": "当面板或菜单打开时,桌面变暗。", "label": "昏暗的桌面" @@ -824,6 +811,17 @@ "description": "启用或禁用整个界面的工具提示。", "label": "显示工具提示" } + }, + "lock-screen": { + "compact-lockscreen": { + "description": "仅显示登录输入和系统控制,隐藏天气和媒体小部件。", + "label": "紧凑型锁屏" + }, + "lock-on-suspend": { + "description": "系统挂起时自动锁定屏幕。", + "label": "挂起时锁定" + }, + "title": "锁屏" } }, "widgets": { diff --git a/Commons/TablerIcons.qml b/Commons/TablerIcons.qml index a840d72f..59b226d3 100644 --- a/Commons/TablerIcons.qml +++ b/Commons/TablerIcons.qml @@ -119,6 +119,7 @@ Singleton { "settings-notifications": "bell", "settings-osd": "picture-in-picture", "settings-about": "info-square-rounded", + "settings-lock-screen": "lock", "bluetooth": "bluetooth", "bt-device-generic": "bluetooth", "bt-device-headphones": "headphones", diff --git a/Modules/Settings/SettingsPanel.qml b/Modules/Settings/SettingsPanel.qml index e5b64566..47ebc40a 100644 --- a/Modules/Settings/SettingsPanel.qml +++ b/Modules/Settings/SettingsPanel.qml @@ -26,6 +26,7 @@ NPanel { Audio, Bar, ColorScheme, + LockScreen, ControlCenter, OSD, Display, @@ -118,6 +119,11 @@ NPanel { id: userInterfaceTab UserInterfaceTab {} } + Component { + id: lockScreenTab + LockScreenTab {} + } + // Order *DOES* matter function updateTabsModel() { let newTabs = [{ @@ -150,6 +156,11 @@ NPanel { "label": "settings.launcher.title", "icon": "settings-launcher", "source": launcherTab + }, { + "id": SettingsPanel.Tab.LockScreen, + "label": "settings.lock-screen.title", + "icon": "settings-lock-screen", + "source": lockScreenTab }, { "id": SettingsPanel.Tab.Audio, "label": "settings.audio.title", diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 81146436..bbd02a8e 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -159,7 +159,6 @@ ColumnLayout { label: I18n.tr("settings.color-scheme.dark-mode.switch.label") description: I18n.tr("settings.color-scheme.dark-mode.switch.description") checked: Settings.data.colorSchemes.darkMode - enabled: true onToggled: checked => { Settings.data.colorSchemes.darkMode = checked root.cacheVersion++ // Force UI update for dark/light variants @@ -241,6 +240,7 @@ ColumnLayout { NToggle { label: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label") description: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.description") + enabled: ProgramCheckerService.matugenAvailable checked: Settings.data.colorSchemes.useWallpaperColors onToggled: checked => { if (checked) { diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index 4f084445..d750f3c7 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -230,26 +230,13 @@ ColumnLayout { Layout.topMargin: Style.marginXL Layout.bottomMargin: Style.marginXL } - ColumnLayout { - spacing: Style.marginL - Layout.fillWidth: true - NHeader { - label: I18n.tr("settings.general.lockscreen.section.label") - description: I18n.tr("settings.general.lockscreen.section.description") + NButton { + visible: !DistroService.isNixOS + text: I18n.tr("settings.general.launch-setup-wizard") + onClicked: { + setupWizardLoader.active = false + setupWizardLoader.active = true } - - NToggle { - label: I18n.tr("settings.general.lockscreen.lock-on-suspend.label") - description: I18n.tr("settings.general.lockscreen.lock-on-suspend.description") - checked: Settings.data.general.lockOnSuspend - onToggled: Settings.data.general.lockOnSuspend = checked - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL - Layout.bottomMargin: Style.marginXL } } diff --git a/Modules/Settings/Tabs/LockScreenTab.qml b/Modules/Settings/Tabs/LockScreenTab.qml new file mode 100644 index 00000000..2b9d0ea7 --- /dev/null +++ b/Modules/Settings/Tabs/LockScreenTab.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +ColumnLayout { + id: root + + NToggle { + label: I18n.tr("settings.lock-screen.lock-on-suspend.label") + description: I18n.tr("settings.lock-screen.lock-on-suspend.description") + checked: Settings.data.general.lockOnSuspend + onToggled: Settings.data.general.lockOnSuspend = checked + } + + NToggle { + label: I18n.tr("settings.lock-screen.compact-lockscreen.label") + description: I18n.tr("settings.lock-screen.compact-lockscreen.description") + checked: Settings.data.general.compactLockScreen + onToggled: checked => Settings.data.general.compactLockScreen = checked + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL + Layout.bottomMargin: Style.marginXL + } +} diff --git a/Modules/Settings/Tabs/UserInterfaceTab.qml b/Modules/Settings/Tabs/UserInterfaceTab.qml index 13557c29..cffb7489 100644 --- a/Modules/Settings/Tabs/UserInterfaceTab.qml +++ b/Modules/Settings/Tabs/UserInterfaceTab.qml @@ -33,13 +33,6 @@ ColumnLayout { onToggled: checked => Settings.data.ui.tooltipsEnabled = checked } - NToggle { - label: I18n.tr("settings.user-interface.compact-lockscreen.label") - description: I18n.tr("settings.user-interface.compact-lockscreen.description") - checked: Settings.data.general.compactLockScreen - onToggled: checked => Settings.data.general.compactLockScreen = checked - } - NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginL diff --git a/Modules/SetupWizard/SetupAppearanceStep.qml b/Modules/SetupWizard/SetupAppearanceStep.qml index e247e070..a59b2ad3 100644 --- a/Modules/SetupWizard/SetupAppearanceStep.qml +++ b/Modules/SetupWizard/SetupAppearanceStep.qml @@ -124,14 +124,14 @@ ColumnLayout { spacing: 2 NText { - text: I18n.tr("settings.color-scheme.color-source.dark-mode.label") + text: I18n.tr("settings.color-scheme.dark-mode.switch.label") pointSize: Style.fontSizeL font.weight: Style.fontWeightBold color: Color.mOnSurface } NText { - text: I18n.tr("settings.color-scheme.color-source.dark-mode.description") + text: I18n.tr("settings.color-scheme.dark-mode.switch.description") pointSize: Style.fontSizeS color: Color.mOnSurfaceVariant wrapMode: Text.WordWrap @@ -167,7 +167,7 @@ ColumnLayout { color: Color.mSurface NIcon { - icon: "color-picker" + icon: ProgramCheckerService.matugenAvailable ? "color-picker" : "alert-triangle" pointSize: Style.fontSizeL color: Color.mPrimary anchors.centerIn: parent @@ -196,7 +196,6 @@ ColumnLayout { NToggle { enabled: ProgramCheckerService.matugenAvailable - opacity: ProgramCheckerService.matugenAvailable ? 1.0 : 0.6 checked: Settings.data.colorSchemes.useWallpaperColors && ProgramCheckerService.matugenAvailable onToggled: checked => { if (!ProgramCheckerService.matugenAvailable) @@ -214,34 +213,6 @@ ColumnLayout { } } - // Matugen not available notice - RowLayout { - Layout.fillWidth: true - spacing: Style.marginS - visible: !ProgramCheckerService.matugenAvailable - - Rectangle { - width: 28 - height: 28 - radius: Style.radiusM - color: Color.mSurface - NIcon { - icon: "alert-triangle" - pointSize: Style.fontSizeL - color: Color.mPrimary - anchors.centerIn: parent - } - } - NText { - text: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.description") - // Reuse description; availability is visually indicated - pointSize: Style.fontSizeS - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - } - // Matugen scheme type (visible when wallpaper colors enabled and matugen available) ColumnLayout { Layout.fillWidth: true diff --git a/Widgets/NToggle.qml b/Widgets/NToggle.qml index 18259ed6..45ad8bc3 100644 --- a/Widgets/NToggle.qml +++ b/Widgets/NToggle.qml @@ -9,6 +9,7 @@ RowLayout { property string label: "" property string description: "" + property bool enabled: true property bool checked: false property bool hovering: false property int baseSize: Math.round(Style.baseWidgetSize * 0.8 * Style.uiScaleRatio) @@ -18,6 +19,7 @@ RowLayout { signal exited Layout.fillWidth: true + opacity: enabled ? 1.0 : 0.6 NLabel { label: root.label @@ -71,14 +73,20 @@ RowLayout { cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: { + if (!enabled) + return hovering = true root.entered() } onExited: { + if (!enabled) + return hovering = false root.exited() } onClicked: { + if (!enabled) + return root.toggled(!root.checked) } }