diff --git a/.gitignore b/.gitignore index e69de29b..2d6d8923 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,2 @@ +.qmlls.ini +.zed 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/de.json b/Assets/Translations/de.json index 26bd0515..5c1dfeec 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,15 +35,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" } }, @@ -53,12 +53,24 @@ "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" } }, "reset-scaling": "Skalierung zurücksetzen" - } + }, + "language": { + "section": { + "label": "Sprache", + "description": "Hier kannst du die Sprache von Noctalia ändern." + }, + "select": { + "label": "Anwendungssprache", + "description": "Wählen die in der Anwendungsoberfläche verwendete Sprache.", + "auto-detect": "Automatisch" + } + }, + "launch-setup-wizard": "Starte den Setup-Assistenten" }, "audio": { "title": "Audio", @@ -222,13 +234,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": { @@ -261,6 +273,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)." @@ -294,7 +310,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", @@ -318,8 +334,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", @@ -359,35 +375,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", @@ -397,7 +413,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." } } }, @@ -446,7 +462,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." } }, @@ -475,10 +491,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 +500,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", @@ -517,15 +542,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": { @@ -533,7 +558,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 die Installation von {app}" }, "discord": { "description": "Schreibt {filepath} für {client}", @@ -541,7 +570,7 @@ }, "pywalfox": { "description": "Schreibt {filepath} und führt pywalfox update aus", - "description-missing": "Erfordert pywalfox Paket" + "description-missing": "Erfordert die Installation von {app} " } }, "misc": { @@ -549,7 +578,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": { @@ -567,7 +596,7 @@ }, "search": { "label": "Nach einem Standort suchen", - "description": "z.B. Berlin, Deutschland", + "description": "z.B. Dortmund, Deutschland", "placeholder": "Standortnamen eingeben" } }, @@ -596,7 +625,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)" } } }, @@ -689,7 +718,7 @@ "description_plural": "Ein Dankeschön an unsere {count} großartigen Mitwirkenden!" } }, - "support": "Unterstützen Sie uns" + "support": "Unterstütz uns" }, "hooks": { "title": "Hooks", @@ -746,7 +775,7 @@ "label": "Widgets für Kurzbefehle" }, "sectionLeft": "Links", - "sectionRight": "Richtig" + "sectionRight": "Rechts" } }, "user-interface": { @@ -768,10 +797,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" @@ -790,6 +815,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": { @@ -802,7 +838,7 @@ "mainly-clear": "Überwiegend klar", "partly-cloudy": "Teilweise bewölkt", "overcast": "Bedeckt", - "fog": "Nebel", + "fog": "Nebelig", "drizzle": "Nieselregen", "snow": "Schnee", "rain-showers": "Regenschauer", @@ -818,7 +854,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": { @@ -897,6 +933,9 @@ }, "cancel": "Abbrechen", "apply": "Anwenden" + }, + "text-input": { + "clear": "Löschen" } }, "bar": { @@ -922,9 +961,13 @@ "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." + }, + "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", @@ -1075,6 +1118,14 @@ "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." + }, + "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." @@ -1219,6 +1270,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 65e46e63..b4814d84 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", @@ -58,6 +59,17 @@ "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.", + "auto-detect": "Automatic" + } } }, "audio": { @@ -259,7 +271,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", @@ -475,10 +491,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 +500,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", @@ -539,6 +564,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." @@ -553,7 +582,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" } } } @@ -768,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.", @@ -790,6 +815,17 @@ "label": "Open panels in overlay layer", "description": "Panels will appear above fullscreen windows" } + }, + "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": { @@ -880,6 +916,9 @@ }, "cancel": "Cancel", "apply": "Apply" + }, + "text-input": { + "clear": "Clear" } }, "bar": { @@ -905,9 +944,13 @@ "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." + }, + "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", @@ -1058,6 +1101,14 @@ "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." + }, + "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." @@ -1191,10 +1242,11 @@ "scan-again": "Scan again" } }, - "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 8a663fd1..10bc18bc 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -58,7 +58,19 @@ } }, "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.", + "auto-detect": "Automático" + } + }, + "launch-setup-wizard": "Inicie el asistente de configuración" }, "audio": { "title": "Audio", @@ -261,6 +273,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)." @@ -475,10 +491,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 +500,19 @@ "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." + }, + "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": { "section": { "label": "Esquemas de colores predefinidos", @@ -535,6 +560,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." @@ -549,7 +578,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": { @@ -768,10 +797,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" @@ -790,6 +815,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": { @@ -880,6 +916,9 @@ }, "cancel": "Cancelar", "apply": "Aplicar" + }, + "text-input": { + "clear": "Borrar" } }, "bar": { @@ -905,9 +944,13 @@ "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." + }, + "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", @@ -1058,6 +1101,14 @@ "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." + }, + "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." @@ -1202,6 +1253,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 190573f5..790220ed 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -58,7 +58,19 @@ } }, "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.", + "auto-detect": "Automatique" + } + }, + "launch-setup-wizard": "Lancer l'assistant d'installation" }, "audio": { "title": "Audio", @@ -261,6 +273,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)." @@ -475,10 +491,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 +500,19 @@ "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." + }, + "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": { "section": { "label": "Jeux de couleurs prédéfinis", @@ -535,6 +560,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." @@ -549,7 +578,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": { @@ -768,10 +797,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" @@ -790,6 +815,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": { @@ -880,6 +916,9 @@ }, "cancel": "Annuler", "apply": "Appliquer" + }, + "text-input": { + "clear": "Effacer" } }, "bar": { @@ -905,9 +944,13 @@ "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." + }, + "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", @@ -1054,6 +1097,14 @@ "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." + }, + "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." @@ -1202,6 +1253,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 69f96962..0be6fdf2 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -58,7 +58,19 @@ } }, "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.", + "auto-detect": "Automático" + } + }, + "launch-setup-wizard": "Iniciar o assistente de configuração" }, "audio": { "title": "Áudio", @@ -261,6 +273,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)." @@ -437,10 +453,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 +462,19 @@ "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." + }, + "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": { "section": { "label": "Esquemas de cores predefinidos", @@ -497,6 +522,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." @@ -511,7 +540,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": { @@ -768,10 +797,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" @@ -790,6 +815,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": { @@ -880,6 +916,9 @@ }, "cancel": "Cancelar", "apply": "Aplicar" + }, + "text-input": { + "clear": "Limpar" } }, "bar": { @@ -905,9 +944,13 @@ "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." + }, + "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", @@ -1058,6 +1101,14 @@ "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." + }, + "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." @@ -1202,6 +1253,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 c6c52c06..90c4cecf 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -58,7 +58,19 @@ "description": "增大或减小等宽文本的尺寸" } } - } + }, + "language": { + "section": { + "label": "语言", + "description": "选择您偏好的应用程序语言。" + }, + "select": { + "label": "应用程序语言", + "description": "选择应用程序界面中使用的语言。", + "auto-detect": "自动检测" + } + }, + "launch-setup-wizard": "启动安装向导" }, "audio": { "title": "音频", @@ -261,6 +273,10 @@ "label": "Dock 浮动距离", "description": "调整距离屏幕边缘的浮动距离。" }, + "icon-size": { + "label": "Dock 大小", + "description": "调整 Dock 的整体大小。" + }, "colorize-icons": { "label": "着色图标", "description": "将主题颜色应用到 Dock 应用图标(仅限非聚焦应用)。" @@ -475,10 +491,6 @@ "label": "颜色来源", "description": "Noctalia 颜色的主要设置。" }, - "dark-mode": { - "label": "深色模式", - "description": "切换到更暗的主题,便于夜间观看。" - }, "use-wallpaper-colors": { "label": "使用壁纸颜色", "description": "使用 Matugen 从壁纸生成颜色方案。自动提取颜色以创建一致的主题。" @@ -488,6 +500,19 @@ "description": "为 Matugen 选择配色方案生成算法。" } }, + "dark-mode": { + "switch": { + "label": "深色模式", + "description": "切换到更暗的主题,便于夜间观看。" + }, + "mode": { + "description": "启用自动切换浅色和深色模式。", + "label": "深色模式计划", + "location": "位置", + "manual": "手册", + "off": "关" + } + }, "predefined": { "section": { "label": "预定义配色方案", @@ -535,6 +560,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。" @@ -549,7 +578,7 @@ "description": "其他配置选项。", "user-templates": { "label": "用户模板", - "description": "启用来自 ~/.config/matugen/config.toml 的用户定义 Matugen 配置" + "description": "启用用户定义的 Matugen 配置。首次启用时将在 ~/.config/noctalia/user-templates.toml 创建模板文件" } }, "section": { @@ -768,10 +797,6 @@ "label": "边框半径", "reset": "重置边框半径" }, - "compact-lockscreen": { - "description": "仅显示登录输入和系统控制,隐藏天气和媒体小部件。", - "label": "紧凑型锁屏" - }, "dim-desktop": { "description": "当面板或菜单打开时,桌面变暗。", "label": "昏暗的桌面" @@ -790,6 +815,17 @@ "description": "启用或禁用整个界面的工具提示。", "label": "显示工具提示" } + }, + "lock-screen": { + "compact-lockscreen": { + "description": "仅显示登录输入和系统控制,隐藏天气和媒体小部件。", + "label": "紧凑型锁屏" + }, + "lock-on-suspend": { + "description": "系统挂起时自动锁定屏幕。", + "label": "挂起时锁定" + }, + "title": "锁屏" } }, "widgets": { @@ -880,6 +916,9 @@ }, "cancel": "取消", "apply": "应用" + }, + "text-input": { + "clear": "清除" } }, "bar": { @@ -905,9 +944,13 @@ "label": "滚动模式", "description": "控制何时为长窗口标题启用文本滚动。" }, - "width": { - "description": "控制小部件的水平尺寸。", - "label": "小部件宽度" + "max-width": { + "label": "最大宽度", + "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。" + }, + "use-fixed-width": { + "label": "使用固定宽度", + "description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。" }, "colorize-icons": { "label": "着色图标", @@ -1058,6 +1101,14 @@ "label": "可视化器类型", "description": "选择要显示的音频可视化器样式。" }, + "max-width": { + "label": "最大宽度", + "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。" + }, + "use-fixed-width": { + "label": "使用固定宽度", + "description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。" + }, "scrolling-mode": { "label": "滚动模式", "description": "控制何时为长曲目标题启用文本滚动。" @@ -1202,6 +1253,8 @@ "tooltips": { "refresh": "刷新", "close": "关闭", + "up": "向上", + "home": "主目录", "refresh-wallpaper-list": "刷新壁纸列表", "refresh-devices": "刷新设备", "forget-network": "忘记网络", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 2c1ca7bb..904e3961 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -65,7 +65,9 @@ "screenRadiusRatio": 1, "animationSpeed": 1, "animationDisabled": false, - "compactLockScreen": false + "compactLockScreen": false, + "lockOnSuspend": true, + "language": "" }, "location": { "name": "Tokyo", @@ -171,7 +173,8 @@ "onlySameOutput": true, "monitors": [], "pinnedApps": [], - "colorizeIcons": false + "colorizeIcons": false, + "size": 1 }, "network": { "wifiEnabled": true @@ -255,4 +258,4 @@ "battery": { "chargingMode": 0 } -} \ No newline at end of file +} 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/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 diff --git a/Commons/I18n.qml b/Commons/I18n.qml index 6f49f097..59eeb922 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -7,10 +7,10 @@ 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,41 +158,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 = "" - // 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/Commons/Settings.qml b/Commons/Settings.qml index 62695685..08005398 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -191,6 +191,8 @@ Singleton { property real animationSpeed: 1.0 property bool animationDisabled: false property bool compactLockScreen: false + property bool lockOnSuspend: true + property string language: "" } // location @@ -292,6 +294,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") @@ -355,6 +358,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 } @@ -376,6 +382,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/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/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 145395d8..2058bf1a 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -35,7 +35,10 @@ 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 ? 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") readonly property bool hasFocusedWindow: CompositorService.getFocusedWindow() !== null @@ -43,7 +46,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 +62,43 @@ 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 using fixed width mode, always use maxWidth + if (useFixedWidth) { + return maxWidth + } + // Otherwise, adapt to content + 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 +157,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/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 470bc37e..9831626e 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -38,8 +38,9 @@ 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 ? screen.width * 0.06 : 0) + 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") @@ -60,7 +61,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 +81,49 @@ 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 using fixed width mode, always use maxWidth + if (useFixedWidth) { + return maxWidth + } + // Otherwise, adapt to content + 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 +139,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/Dock/Dock.qml b/Modules/Dock/Dock.qml index 56a932e5..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 @@ -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 @@ -261,12 +255,6 @@ Variants { } } - // Rectangle { - // anchors.fill: parent - // color: "#000FF0" - // z: -1 - // } - // Wrapper item for scale/opacity animations Item { id: dockContainerWrapper diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml index 83afaf5f..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: 360 * Style.uiScaleRatio + preferredWidth: 400 * Style.uiScaleRatio + preferredHeight: 340 * 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() @@ -263,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 } } } @@ -349,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) { diff --git a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml index 643684dd..514733dd 100644 --- a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -17,7 +17,8 @@ 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 valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons Component.onCompleted: { @@ -31,7 +32,8 @@ 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.useFixedWidth = valueUseFixedWidth settings.colorizeIcons = valueColorizeIcons return settings } @@ -73,10 +75,18 @@ 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 + } + + 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 { diff --git a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml index 1d03c87f..5e109feb 100644 --- a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml @@ -19,6 +19,8 @@ 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 + property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth Component.onCompleted: { if (widgetData && widgetData.hideMode !== undefined) { @@ -33,6 +35,8 @@ ColumnLayout { settings.showVisualizer = valueShowVisualizer settings.visualizerType = valueVisualizerType settings.scrollingMode = valueScrollingMode + settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth + settings.useFixedWidth = valueUseFixedWidth return settings } @@ -87,6 +91,22 @@ 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 + } + + 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/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 ddf48db1..bbd02a8e 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 @@ -138,20 +156,91 @@ 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 => { Settings.data.colorSchemes.darkMode = checked root.cacheVersion++ // Force UI update for dark/light variants } } + NComboBox { + label: I18n.tr("settings.color-scheme.dark-mode.mode.label") + description: I18n.tr("settings.color-scheme.dark-mode.mode.description") + + model: [{ + "name": I18n.tr("settings.color-scheme.dark-mode.mode.off"), + "key": "off" + }, { + "name": I18n.tr("settings.color-scheme.dark-mode.mode.manual"), + "key": "manual" + }, { + "name": I18n.tr("settings.color-scheme.dark-mode.mode.location"), + "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") description: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.description") + enabled: ProgramCheckerService.matugenAvailable checked: Settings.data.colorSchemes.useWallpaperColors onToggled: checked => { if (checked) { @@ -575,6 +664,23 @@ 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 @@ -590,6 +696,9 @@ ColumnLayout { checked: Settings.data.templates.enableUserTemplates onToggled: checked => { Settings.data.templates.enableUserTemplates = checked + if (checked) { + MatugenTemplates.writeUserTemplatesToml() + } AppThemeService.generate() } } 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") diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index c4554145..d750f3c7 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -189,4 +189,54 @@ ColumnLayout { Layout.topMargin: Style.marginXL Layout.bottomMargin: Style.marginXL } + + // 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: [{ + "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 + } + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL + Layout.bottomMargin: Style.marginXL + } + + NButton { + visible: !DistroService.isNixOS + text: I18n.tr("settings.general.launch-setup-wizard") + onClicked: { + setupWizardLoader.active = false + setupWizardLoader.active = true + } + } } 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 ec9a289d..e3190927 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 - } - NToggle { label: I18n.tr("settings.user-interface.panels-overlay.label") description: I18n.tr("settings.user-interface.panels-overlay.description") 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/Modules/Tooltip/Tooltip.qml b/Modules/Tooltip/Tooltip.qml index 43a6850c..00c139b6 100644 --- a/Modules/Tooltip/Tooltip.qml +++ b/Modules/Tooltip/Tooltip.qml @@ -321,11 +321,36 @@ PopupWindow { completeHide() } - // Update text function for binding support + // Update text function function updateText(newText) { if (visible && targetItem) { text = newText - positionAndShow() + + // 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() + } + }) } } 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 --- diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index dd24327a..c54662ce 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` } }) @@ -150,7 +157,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 @@ -159,7 +169,8 @@ Singleton { function generatePalette(primaryColor, secondaryColor, tertiaryColor, errorColor, backgroundColor, outlineColor, isDarkMode) { const c = hex => ({ "default": { - "hex": hex + "hex": hex, + "hex_stripped": hex.replace(/^#/, "") } }) @@ -335,8 +346,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, "'\\''") } // -------------------------------------------------------------------------------- @@ -346,10 +391,17 @@ Singleton { id: generateProcess workingDirectory: Quickshell.shellDir running: false + stdout: StdioCollector { + onStreamFinished: { + if (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) } } } @@ -357,11 +409,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/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index bd134591..a5823db6 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -44,7 +44,8 @@ Singleton { "hideMode": "hidden", "scrollingMode"// "visible", "hidden", "transparent" : "hover", - "width": 145, + "maxWidth": 145, + "useFixedWidth": false, "colorizeIcons": false }, "Battery": { @@ -88,6 +89,8 @@ Singleton { "hideMode": "hidden", "scrollingMode"// "visible", "hidden", "transparent" : "hover", + "maxWidth": 145, + "useFixedWidth": false, "showAlbumArt": false, "showVisualizer": false, "visualizerType": "linear" diff --git a/Services/DarkModeService.qml b/Services/DarkModeService.qml new file mode 100644 index 00000000..ad3eef00 --- /dev/null +++ b/Services/DarkModeService.qml @@ -0,0 +1,169 @@ +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 + enabled: Settings.data.colorSchemes.schedulingMode == "location" + function onWeatherChanged() { + if (LocationService.data.weather !== null) { + const changes = root.collectWeatherChanges(LocationService.data.weather) + if (!root.initComplete) { + root.initComplete = true + root.applyCurrentMode(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.collectWeatherChanges(LocationService.data.weather) + root.scheduleNextMode(changes) + } + } + } + + function init() { + Logger.i("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]), + "darkMode": false + }) + changes.push({ + "time": Date.parse(weather.daily.sunset[i]), + "darkMode": true + }) + } + + return changes + } + + function applyCurrentMode(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.d("DarkModeService", `Reset: darkmode=${lastChange.darkMode}`) + } + } + + function scheduleNextMode(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.d("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`) + } + } +} 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/Services/LocationService.qml b/Services/LocationService.qml index 0886e142..7c2df5d4 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() @@ -190,7 +190,7 @@ Singleton { // -------------------------------- function _fetchWeather(latitude, longitude, errorCallback) { Logger.d("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/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index 75c83db4..19066213 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,50 @@ 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("") + + 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.d("MatugenTemplates", "User templates config written to:", userConfigPath) + } + // -------------------------------- function addWallpaperBasedTemplates(lines, mode) { // Noctalia colors @@ -106,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": [{ @@ -115,9 +168,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" @@ -208,4 +261,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() + } + } + } } diff --git a/Services/ProgramCheckerService.qml b/Services/ProgramCheckerService.qml index 790fa71a..153b35c9 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 @@ -97,6 +98,7 @@ Singleton { "ghosttyAvailable": ["which", "ghostty"], "footAvailable": ["which", "foot"], "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/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) } } diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index 34b27996..41c5daf4 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -8,7 +8,7 @@ Singleton { id: root // Public properties - property string baseVersion: "2.18.1" + property string baseVersion: "2.18.2" property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index cc318f02..4b82e77d 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -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("placeholders.search") + 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 diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index ffb4f4cc..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 @@ -233,7 +234,7 @@ RowLayout { Layout.alignment: Qt.AlignRight Repeater { - model: badgeLocations + model: typeof badgeLocations !== 'undefined' ? badgeLocations : [] delegate: NBox { width: Style.baseWidgetSize * 0.7 diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index adb83cd3..f11f9ca4 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,115 @@ 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 + spacing: 0 - 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 + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: visible ? Style.marginS : 0 + } - background: null + TextField { + id: input - font.family: root.fontFamily - font.pointSize: root.fontSize * Style.uiScaleRatio - font.weight: root.fontWeight + Layout.fillWidth: true + Layout.fillHeight: true - 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 + + font.family: root.fontFamily + font.pointSize: root.fontSize * Style.uiScaleRatio + font.weight: root.fontWeight + + 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 + } + + 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() } - 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") - onReleased: mouse => { - mouse.accepted = true - } - onWheel: wheel => { - wheel.accepted = true - } + Layout.alignment: Qt.AlignVCenter + + border.width: 0 + + colorBg: Color.transparent + colorBgHover: Color.transparent + colorFg: Color.mOnSurface + colorFgHover: Color.mError + + visible: input.text.length > 0 && !root.readOnly + enabled: input.text.length > 0 && !root.readOnly + + onClicked: { + input.clear() + input.forceActiveFocus() + } } } } 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) } } diff --git a/shell.qml b/shell.qml index 26022135..16227fd1 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() @@ -190,16 +191,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 + } + } }