diff --git a/Assets/ColorScheme/Eldritch/Eldritch.json b/Assets/ColorScheme/Eldritch/Eldritch.json new file mode 100644 index 00000000..3fedfeef --- /dev/null +++ b/Assets/ColorScheme/Eldritch/Eldritch.json @@ -0,0 +1,34 @@ +{ + "dark": { + "mPrimary": "#37f499", + "mOnPrimary": "#171928", + "mSecondary": "#04d1f9", + "mOnSecondary": "#171928", + "mTertiary": "#a48cf2", + "mOnTertiary": "#171928", + "mError": "#f16c75", + "mOnError": "#171928", + "mSurface": "#212337", + "mOnSurface": "#ebfafa", + "mSurfaceVariant": "#292e42", + "mOnSurfaceVariant": "#ABB4DA", + "mOutline": "#3b4261", + "mShadow": "#414868" + }, + "light": { + "mPrimary": "#37f499", + "mOnPrimary": "#171928", + "mSecondary": "#04d1f9", + "mOnSecondary": "#171928", + "mTertiary": "#a48cf2", + "mOnTertiary": "#171928", + "mError": "#f16c75", + "mOnError": "#171928", + "mSurface": "#ffffff", + "mOnSurface": "#171928", + "mSurfaceVariant": "#f2f4f8", + "mOnSurfaceVariant": "#3b4261", + "mOutline": "#b0b6c3", + "mShadow": "#e0e3e8" + } +} diff --git a/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-dark b/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-dark new file mode 100644 index 00000000..dc0c5b58 --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-dark @@ -0,0 +1,23 @@ +[colors] +foreground=ebfafa +background=212337 +regular0=21222c +regular1=f9515d +regular2=37f499 +regular3=e9f941 +regular4=9071f4 +regular5=f265b5 +regular6=04d1f9 +regular7=ebfafa +bright0=7081d0 +bright1=f16c75 +bright2=69F8B3 +bright3=f1fc79 +bright4=a48cf2 +bright5=FD92CE +bright6=66e4fd +bright7=ffffff +selection-foreground=ebfafa +selection-background=bf4f8e +cursor=37f499 f8f8f2 + diff --git a/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-light b/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-light new file mode 100644 index 00000000..90602968 --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-light @@ -0,0 +1,22 @@ +[colors] +foreground=212337 +background=ebfafa +regular0=ebfafa # black → white +regular1=ba1a1a # red (darker) +regular2=1a7f4c # green (darker) +regular3=9e8c13 # yellow (darker) +regular4=3a3e8c # blue (darker) +regular5=8c2a6c # magenta (darker) +regular6=1a6c8c # cyan (darker) +regular7=212337 # white → black +bright0=7081d0 # keep as accent +bright1=d23b3b # lighter red +bright2=37f499 # original green +bright3=e9f941 # original yellow +bright4=9071f4 # original blue +bright5=f265b5 # original magenta +bright6=04d1f9 # original cyan +bright7=212337 # black +selection-foreground=212337 +selection-background=bf4f8e +cursor=1a7f4c 212337 diff --git a/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-dark b/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-dark new file mode 100644 index 00000000..e6b33937 --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-dark @@ -0,0 +1,21 @@ +palette = 0=#21222c +palette = 1=#f9515d +palette = 2=#37f499 +palette = 3=#e9f941 +palette = 4=#9071f4 +palette = 5=#f265b5 +palette = 6=#04d1f9 +palette = 7=#ebfafa +palette = 8=#7081d0 +palette = 9=#f16c75 +palette = 10=#69F8B3 +palette = 11=#f1fc79 +palette = 12=#a48cf2 +palette = 13=#FD92CE +palette = 14=#66e4fd +palette = 15=#ffffff +background = 212337 +foreground = ebfafa +cursor-color = 37f499 +selection-background = bf4f8e +selection-foreground = ebfafa diff --git a/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-light b/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-light new file mode 100644 index 00000000..24b6cba5 --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-light @@ -0,0 +1,22 @@ +palette = 0=#f8f8fa # base background (was dark, now light) +palette = 1=#d7263d # red (less saturated) +palette = 2=#1eb980 # green (cooler, lighter) +palette = 3=#f7c948 # yellow (warmer) +palette = 4=#5e60ce # blue (lighter) +palette = 5=#c77dff # magenta (lighter) +palette = 6=#38a1db # cyan (lighter) +palette = 7=#21222c # base foreground (was background, now dark) +palette = 8=#bfc9e3 # bright black (light gray) +palette = 9=#f9515d # bright red +palette = 10=#37f499 # bright green +palette = 11=#e9f941 # bright yellow +palette = 12=#9071f4 # bright blue +palette = 13=#f265b5 # bright magenta +palette = 14=#04d1f9 # bright cyan +palette = 15=#21222c # bright white (dark for contrast) +background = f8f8fa +foreground = 21222c +cursor-color = 5e60ce +selection-background = d7e3fa +selection-foreground = 21222c + diff --git a/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-dark.conf b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-dark.conf new file mode 100644 index 00000000..8decd1c9 --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-dark.conf @@ -0,0 +1,22 @@ +color0 #21222c +color1 #f9515d +color2 #37f499 +color3 #e9f941 +color4 #9071f4 +color5 #f265b5 +color6 #04d1f9 +color7 #ebfafa +color8 #7081d0 +color9 #f16c75 +color10 #69F8B3 +color11 #f1fc79 +color12 #a48cf2 +color13 #FD92CE +color14 #66e4fd +color15 #ffffff +background #212337 +selection_foreground #ebfafa +cursor #37f499 +cursor_text_color #f8f8f2 +foreground #ebfafa +selection_background #bf4f8e diff --git a/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-light.conf b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-light.conf new file mode 100644 index 00000000..eb75e925 --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-light.conf @@ -0,0 +1,22 @@ +color0 #ebfafa +color1 #f9515d +color2 #37f499 +color3 #e9f941 +color4 #9071f4 +color5 #f265b5 +color6 #04d1f9 +color7 #212337 +color8 #7081d0 +color9 #f16c75 +color10 #69F8B3 +color11 #f1fc79 +color12 #a48cf2 +color13 #FD92CE +color14 #66e4fd +color15 #323449 +background #ebfafa +selection_foreground #ebfafa +cursor #212337 +cursor_text_color #ebfafa +foreground #212337 +selection_background #bf4f8e diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 6a5bb0bd..ae9d2261 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1322,7 +1322,8 @@ "bottom_left": "Unten links", "bottom_right": "Unten rechts", "bottom_center": "Unten mittig", - "top_center": "Oben mittig" + "top_center": "Oben mittig", + "center": "Mitte" }, "quickSettingsStyle": { "modern": "Modern", @@ -1622,5 +1623,34 @@ "label": "Letztes Control-Center-Widget entfernt", "description": "Das Control-Center-Widget wurde aus der Leiste entfernt. Um es erneut über die Leiste zu öffnen, fügen Sie das Widget wieder hinzu. Sie können es auch durch Rechtsklick auf die Leiste öffnen." } + }, + "setup": { + "customize": { + "header": "Erlebnis anpassen", + "subheader": "Leistenposition, Dichte, Skalierung und mehr einstellen." + }, + "appearance": { + "header": "Erscheinungsbild", + "subheader": "Dunkelmodus und Farbquellen wählen (Matugen oder vordefiniert)." + }, + "wallpaper": { + "header": "Wähle dein Hintergrundbild", + "subheader": "Bestimme die Stimmung mit einem schönen Hintergrund.", + "select-prompt": "Wähle unten ein Hintergrundbild", + "preview-error": "Bild konnte nicht geladen werden", + "none-in-dir": "Keine Hintergrundbilder im Verzeichnis gefunden", + "no-dir": "Kein Hintergrundbild-Verzeichnis ausgewählt", + "no-valid": "Keine gültigen Bilddateien gefunden in: {dir}", + "choose-dir": "Wähle ein Verzeichnis mit deinen Hintergrundbildern", + "dir": { + "label": "Hintergrundbild-Verzeichnis", + "description": "Wähle den Ordner mit deinen Hintergrundbildern", + "browse": "Ordner auswählen", + "select-title": "Hintergrundbild-Ordner wählen" + } + }, + "welcome": { + "note": "Nur ein paar Grundeinstellungen – alle Optionen findest du in den Einstellungen" + } } -} \ No newline at end of file +} diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 7f6cf5c7..7ea6c7cc 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1305,7 +1305,8 @@ "bottom_left": "Bottom left", "bottom_right": "Bottom right", "bottom_center": "Bottom center", - "top_center": "Top center" + "top_center": "Top center", + "center": "Center" }, "quickSettingsStyle": { "modern": "Modern", @@ -1622,5 +1623,34 @@ "lifespan": "Extended lifespan ({percent}%)", "disabled": "Battery manager disabled" } + }, + "setup": { + "customize": { + "header": "Customize your experience", + "subheader": "Adjust bar position, density, scaling and more." + }, + "appearance": { + "header": "Appearance", + "subheader": "Choose dark mode and color sources (Matugen or predefined)." + }, + "wallpaper": { + "header": "Choose your wallpaper", + "subheader": "Set the mood with a beautiful background.", + "select-prompt": "Select a wallpaper below", + "preview-error": "Failed to load image", + "none-in-dir": "No wallpapers found in directory", + "no-dir": "No wallpaper directory selected", + "no-valid": "No valid image files found in: {dir}", + "choose-dir": "Choose a directory containing your wallpaper images", + "dir": { + "label": "Wallpaper directory", + "description": "Choose the folder containing your wallpapers", + "browse": "Browse for wallpaper folder", + "select-title": "Select wallpaper folder" + } + }, + "welcome": { + "note": "Just a few basics to get you started - full options are in Settings" + } } -} \ No newline at end of file +} diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 9ced10d2..fe04122f 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1305,7 +1305,8 @@ "bottom_left": "Inferior izquierda", "bottom_right": "Inferior derecha", "bottom_center": "Inferior central", - "top_center": "Superior central" + "top_center": "Superior central", + "center": "Centro" }, "quickSettingsStyle": { "modern": "Moderno", @@ -1622,5 +1623,34 @@ "lifespan": "Vida útil prolongada ({percent}%)", "disabled": "Administrador de batería deshabilitado" } + }, + "setup": { + "customize": { + "header": "Personaliza tu experiencia", + "subheader": "Ajusta la posición de la barra, densidad, escala y más." + }, + "appearance": { + "header": "Apariencia", + "subheader": "Elige modo oscuro y fuentes de color (Matugen o predefinido)." + }, + "wallpaper": { + "header": "Elige tu fondo", + "subheader": "Define el ambiente con un bonito fondo.", + "select-prompt": "Selecciona un fondo abajo", + "preview-error": "No se pudo cargar la imagen", + "none-in-dir": "No se encontraron fondos en el directorio", + "no-dir": "No se seleccionó un directorio de fondos", + "no-valid": "No se encontraron imágenes válidas en: {dir}", + "choose-dir": "Elige un directorio que contenga tus fondos", + "dir": { + "label": "Directorio de fondos", + "description": "Elige la carpeta que contiene tus fondos", + "browse": "Seleccionar carpeta", + "select-title": "Seleccionar carpeta de fondos" + } + }, + "welcome": { + "note": "Solo algunos conceptos básicos para empezar: todas las opciones están en Configuración." + } } -} \ No newline at end of file +} diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 1d2d9568..96d668d2 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1305,7 +1305,8 @@ "bottom_left": "En bas à gauche", "bottom_right": "En bas à droite", "bottom_center": "En bas au centre", - "top_center": "En haut au centre" + "top_center": "En haut au centre", + "center": "Centre" }, "quickSettingsStyle": { "modern": "Moderne", @@ -1622,5 +1623,34 @@ "lifespan": "Durée de vie prolongée ({percent}%)", "disabled": "Gestionnaire de batterie désactivé" } + }, + "setup": { + "customize": { + "header": "Personnaliser votre expérience", + "subheader": "Ajustez la position de la barre, la densité, l'échelle et plus encore." + }, + "appearance": { + "header": "Apparence", + "subheader": "Choisissez le mode sombre et la source des couleurs (Matugen ou prédéfinie)." + }, + "wallpaper": { + "header": "Choisissez votre fond d'écran", + "subheader": "Définissez l'ambiance avec un joli fond.", + "select-prompt": "Sélectionnez un fond ci-dessous", + "preview-error": "Échec du chargement de l'image", + "none-in-dir": "Aucun fond d'écran trouvé dans le répertoire", + "no-dir": "Aucun répertoire de fonds d'écran sélectionné", + "no-valid": "Aucun fichier image valide trouvé dans : {dir}", + "choose-dir": "Choisissez un répertoire contenant vos fonds d'écran", + "dir": { + "label": "Répertoire des fonds d'écran", + "description": "Choisissez le dossier contenant vos fonds d'écran", + "browse": "Parcourir le dossier", + "select-title": "Sélectionner le dossier des fonds d'écran" + } + }, + "welcome": { + "note": "Quelques réglages de base pour démarrer — toutes les options sont dans Paramètres" + } } -} \ No newline at end of file +} diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 118939a8..55121c93 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1303,7 +1303,8 @@ "bottom_left": "Inferior esquerdo", "bottom_right": "Inferior direito", "bottom_center": "Centro inferior", - "top_center": "Centro superior" + "top_center": "Centro superior", + "center": "Centro" }, "quickSettingsStyle": { "modern": "Moderno", @@ -1615,18 +1616,41 @@ "discharging-rate": "Taxa de descarregamento: {rate} W.", "charging": "Carregando.", "discharging": "Descarregando.", - "battery-manager": { - "title": "Limite da bateria", - "set-success-desc": "Limite da bateria definido para {percent}%", - "initial-setup": "Configuração inicial necessária", - "set-failed": "Falha ao definir o limite da bateria", - "install-success": "Instalado com sucesso", - "install-missing": "Arquivos necessários ausentes", - "install-unsupported": "Sistema não suportado", - "install-failed": "Falha na instalação", - "uninstall-setup": "Desinstalando, autenticação necessária", - "uninstall-success": "Desinstalado com sucesso", - "uninstall-failed": "Falha na desinstalação" + "panel": { + "balanced": "Balanceado ({percent}%)", + "disabled": "Gerenciador de bateria desativado", + "full": "Capacidade máxima ({percent}%)", + "lifespan": "Vida útil prolongada ({percent}%)", + "title": "Limite de carga" + } + }, + "setup": { + "customize": { + "header": "Personalizar a sua experiência", + "subheader": "Ajuste a posição da barra, densidade, escala e mais." + }, + "appearance": { + "header": "Aparência", + "subheader": "Escolha o modo escuro e as fontes de cores (Matugen ou predefinidas)." + }, + "wallpaper": { + "header": "Escolha o seu papel de parede", + "subheader": "Defina o ambiente com um belo fundo.", + "select-prompt": "Selecione um papel de parede abaixo", + "preview-error": "Falha ao carregar a imagem", + "none-in-dir": "Nenhum papel de parede encontrado no diretório", + "no-dir": "Nenhum diretório de papéis de parede selecionado", + "no-valid": "Nenhuma imagem válida encontrada em: {dir}", + "choose-dir": "Escolha um diretório contendo seus papéis de parede", + "dir": { + "label": "Diretório de papéis de parede", + "description": "Escolha a pasta que contém seus papéis de parede", + "browse": "Procurar pasta", + "select-title": "Selecionar pasta de papéis de parede" + } + }, + "welcome": { + "note": "Apenas alguns ajustes básicos para começar - o restante está em Configurações" } } -} \ No newline at end of file +} diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index d64db2ff..884d6cc1 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1305,7 +1305,8 @@ "bottom_left": "左下角", "bottom_right": "右下角", "bottom_center": "底部居中", - "top_center": "顶部居中" + "top_center": "顶部居中", + "center": "居中" }, "quickSettingsStyle": { "modern": "现代", @@ -1622,5 +1623,34 @@ "lifespan": "延长寿命 ({percent}%)", "disabled": "电池管理器已禁用" } + }, + "setup": { + "customize": { + "header": "自定义体验", + "subheader": "调整状态栏位置、密度、缩放等。" + }, + "appearance": { + "header": "外观", + "subheader": "选择深色模式与配色来源(Matugen 或预设)。" + }, + "wallpaper": { + "header": "选择你的壁纸", + "subheader": "用精美壁纸营造氛围。", + "select-prompt": "在下方选择一张壁纸", + "preview-error": "图片加载失败", + "none-in-dir": "目录中未找到壁纸", + "no-dir": "未选择壁纸目录", + "no-valid": "在 {dir} 中未找到有效图片文件", + "choose-dir": "选择包含壁纸图片的目录", + "dir": { + "label": "壁纸目录", + "description": "选择存放壁纸的文件夹", + "browse": "浏览文件夹", + "select-title": "选择壁纸文件夹" + } + }, + "welcome": { + "note": "先进行一些基础设置——更多选项可在“设置”中找到" + } } -} \ No newline at end of file +} diff --git a/Assets/noctalia.svg b/Assets/noctalia.svg new file mode 100644 index 00000000..10646225 --- /dev/null +++ b/Assets/noctalia.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Assets/settings-default.json b/Assets/settings-default.json index b8a82259..8d3d6ce8 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -1,5 +1,6 @@ { - "settingsVersion": 15, + "settingsVersion": 16, + "setupCompleted": false, "bar": { "position": "top", "backgroundOpacity": 1, diff --git a/Commons/Settings.qml b/Commons/Settings.qml index d0308487..22a1c2ff 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -14,6 +14,7 @@ Singleton { readonly property alias data: adapter property bool isLoaded: false property bool directoriesCreated: false + property int settingsVersion: 16 // Define our app directories // Default config directory: ~/.config/noctalia @@ -100,6 +101,9 @@ Singleton { // Emit the signal root.settingsLoaded() + + // Finally, update our local settings version + adapter.settingsVersion = settingsVersion } } onLoadFailed: function (error) { @@ -125,7 +129,8 @@ Singleton { JsonAdapter { id: adapter - property int settingsVersion: 15 + property int settingsVersion: root.settingsVersion + property bool setupCompleted: false // bar property JsonObject bar: JsonObject { diff --git a/Commons/TablerIcons.qml b/Commons/TablerIcons.qml index 3522e30d..a840d72f 100644 --- a/Commons/TablerIcons.qml +++ b/Commons/TablerIcons.qml @@ -128,6 +128,12 @@ Singleton { "bt-device-watch": "device-watch", "bt-device-speaker": "device-speaker", "bt-device-tv": "device-tv", + "antenna-bars-1": "antenna-bars-1", + "antenna-bars-2": "antenna-bars-2", + "antenna-bars-3": "antenna-bars-3", + "antenna-bars-4": "antenna-bars-4", + "antenna-bars-5": "antenna-bars-5", + "antenna-bars-off": "antenna-bars-off", "noctalia": "noctalia", "hyprland": "hyprland", "filepicker-folder": "folder", diff --git a/Modules/Bar/Bluetooth/BluetoothDevicesList.qml b/Modules/Bar/Bluetooth/BluetoothDevicesList.qml index 8e144c31..311bfeb4 100644 --- a/Modules/Bar/Bluetooth/BluetoothDevicesList.qml +++ b/Modules/Bar/Bluetooth/BluetoothDevicesList.qml @@ -109,7 +109,7 @@ ColumnLayout { NIcon { visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked - text: BluetoothService.getSignalIcon(modelData) + icon: BluetoothService.getSignalIcon(modelData) pointSize: Style.fontSizeXS color: getContentColor(Color.mOnSurface) } diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 599dddb6..45cf6a9c 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -46,8 +46,8 @@ NPanel { // Positioning readonly property string controlCenterPosition: Settings.data.controlCenter.position - panelAnchorHorizontalCenter: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_center") - panelAnchorVerticalCenter: false + panelAnchorHorizontalCenter: controlCenterPosition !== "close_to_bar_button" && (controlCenterPosition.endsWith("_center") || controlCenterPosition === "center") + panelAnchorVerticalCenter: controlCenterPosition === "center" panelAnchorLeft: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_left") panelAnchorRight: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_right") panelAnchorBottom: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.startsWith("bottom_") diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index b38d521e..969c8769 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -336,7 +336,6 @@ ColumnLayout { NIcon { icon: "check" pointSize: Style.fontSizeXS - font.weight: Style.fontWeightBold color: Color.mOnSecondary anchors.centerIn: parent } diff --git a/Modules/Settings/Tabs/ControlCenterTab.qml b/Modules/Settings/Tabs/ControlCenterTab.qml index 4bf62cfb..5fbbbf9e 100644 --- a/Modules/Settings/Tabs/ControlCenterTab.qml +++ b/Modules/Settings/Tabs/ControlCenterTab.qml @@ -143,6 +143,9 @@ ColumnLayout { }, { "key": "top_center", "name": I18n.tr("options.control-center.position.top_center") + }, { + "key": "center", + "name": I18n.tr("options.control-center.position.center") }] currentKey: Settings.data.controlCenter.position onSelected: function (key) { diff --git a/Modules/SetupWizard/SetupAppearanceStep.qml b/Modules/SetupWizard/SetupAppearanceStep.qml new file mode 100644 index 00000000..32903902 --- /dev/null +++ b/Modules/SetupWizard/SetupAppearanceStep.qml @@ -0,0 +1,557 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell.Io +import qs.Commons +import qs.Services +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + function extractSchemeName(path) { + var basename = path.split('/').pop() + return basename.replace('.json', '') + } + + // Cache for scheme colors (mirrors ColorSchemeTab approach) + property var schemeColorsCache: ({}) + property int cacheVersion: 0 + + function getSchemeColor(schemeName, key) { + try { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var data = schemeColorsCache[schemeName] + if (data && data[mode] && data[mode][key]) + return data[mode][key] + } catch (e) { + + } + return Color.mSurfaceVariant + } + + // Match ColorSchemeTab helpers + function schemeLoaded(schemeName, jsonData) { + var value = jsonData || {} + schemeColorsCache[schemeName] = value + cacheVersion++ + Logger.log("SetupAppearanceStep", `Loaded scheme ${schemeName}`) + } + + Connections { + target: ColorSchemeService + function onSchemesChanged() { + Logger.log("SetupAppearanceStep", `Color schemes changed: ${ColorSchemeService.schemes.length}`) + schemeColorsCache = {} + cacheVersion++ + } + } + + // Beautiful header with icon + RowLayout { + Layout.fillWidth: true + Layout.bottomMargin: Style.marginL + spacing: Style.marginM + + Rectangle { + width: 40 + height: 40 + radius: Style.radiusL + color: Color.mSurfaceVariant + opacity: 0.6 + + NIcon { + icon: "palette" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: I18n.tr("setup.appearance.header") + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mPrimary + } + + NText { + text: I18n.tr("setup.appearance.subheader") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + } + + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + contentWidth: availableWidth + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded + + ColumnLayout { + width: parent.width + spacing: Style.marginM + + // Dark Mode Toggle + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "moon" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("settings.color-scheme.color-source.dark-mode.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("settings.color-scheme.color-source.dark-mode.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + NToggle { + checked: Settings.data.colorSchemes.darkMode + onToggled: checked => Settings.data.colorSchemes.darkMode = checked + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Wallpaper Colors Toggle + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "color-picker" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + NToggle { + enabled: ProgramCheckerService.matugenAvailable + opacity: ProgramCheckerService.matugenAvailable ? 1.0 : 0.6 + checked: Settings.data.colorSchemes.useWallpaperColors && ProgramCheckerService.matugenAvailable + onToggled: checked => { + if (!ProgramCheckerService.matugenAvailable) + return + if (checked) { + Settings.data.colorSchemes.useWallpaperColors = true + AppThemeService.generate() + } else { + Settings.data.colorSchemes.useWallpaperColors = false + if (Settings.data.colorSchemes.predefinedScheme) { + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) + } + } + } + } + } + + // 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 + spacing: Style.marginM + visible: Settings.data.colorSchemes.useWallpaperColors && ProgramCheckerService.matugenAvailable + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "wand" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + // Matugen scheme options styled like bar position buttons + GridLayout { + Layout.fillWidth: true + columns: 2 + rowSpacing: Style.marginS + columnSpacing: Style.marginS + + Repeater { + model: [{ + "key": "scheme-content", + "name": "Content" + }, { + "key": "scheme-expressive", + "name": "Expressive" + }, { + "key": "scheme-fidelity", + "name": "Fidelity" + }, { + "key": "scheme-fruit-salad", + "name": "Fruit Salad" + }, { + "key": "scheme-monochrome", + "name": "Monochrome" + }, { + "key": "scheme-neutral", + "name": "Neutral" + }, { + "key": "scheme-rainbow", + "name": "Rainbow" + }, { + "key": "scheme-tonal-spot", + "name": "Tonal Spot" + }] + delegate: Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 48 + radius: Style.radiusM + border.width: 1 + + property bool isActive: Settings.data.colorSchemes.matugenSchemeType === modelData.key + + color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant + border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline + opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8 + + NText { + text: modelData.name + pointSize: Style.fontSizeM + font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium + color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface + anchors.centerIn: parent + } + + HoverHandler { + id: hoverHandler + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + Settings.data.colorSchemes.matugenSchemeType = modelData.key + AppThemeService.generate() + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + visible: !Settings.data.colorSchemes.useWallpaperColors + } + + // Predefined schemes section (visible when wallpaper colors disabled) + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + visible: !Settings.data.colorSchemes.useWallpaperColors + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "palette" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("settings.color-scheme.predefined.section.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("settings.color-scheme.predefined.section.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + // Predefined schemes Grid (matches ColorSchemeTab) + GridLayout { + id: schemesGrid + columns: Math.max(2, Math.floor((parent.width - Style.marginM * 2) / 180)) + rowSpacing: Style.marginM + columnSpacing: Style.marginM + Layout.fillWidth: true + + Repeater { + model: ColorSchemeService.schemes + + delegate: Rectangle { + id: schemeItem + + property string schemePath: modelData + property string schemeName: root.extractSchemeName(modelData) + + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + height: 50 + radius: Style.radiusS + color: root.cacheVersion >= 0 ? root.getSchemeColor(schemeName, "mSurface") : root.getSchemeColor(schemeName, "mSurface") + border.width: Math.max(1, Style.borderL) + border.color: itemMouseArea.containsMouse ? Color.mTertiary : (Settings.data.colorSchemes.predefinedScheme === schemeName ? Color.mSecondary : Color.mOutline) + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginXS + + NText { + text: schemeItem.schemeName + pointSize: Style.fontSizeS + font.weight: Style.fontWeightMedium + color: Color.mOnSurface + Layout.fillWidth: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + maximumLineCount: 1 + } + + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mPrimary) || root.getSchemeColor(schemeItem.schemeName, "mPrimary") + })() : Color.mPrimary + } + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mSecondary) || root.getSchemeColor(schemeItem.schemeName, "mSecondary") + })() : Color.mSecondary + } + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mTertiary) || root.getSchemeColor(schemeItem.schemeName, "mTertiary") + })() : Color.mTertiary + } + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mError) || root.getSchemeColor(schemeItem.schemeName, "mError") + })() : Color.mError + } + } + + MouseArea { + id: itemMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + Settings.data.colorSchemes.useWallpaperColors = false + Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) + } + } + } + } + } + } + + // Bottom spacer + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + } + } + + // Hidden loader to populate schemeColorsCache from files + Item { + visible: false + Repeater { + model: ColorSchemeService.schemes + delegate: Item { + FileView { + path: modelData + blockLoading: false + onLoaded: { + var schemeName = root.extractSchemeName(path) + try { + var jsonData = JSON.parse(text()) + root.schemeLoaded(schemeName, jsonData) + } catch (e) { + root.schemeLoaded(schemeName, null) + } + } + } + } + } + } +} diff --git a/Modules/SetupWizard/SetupCustomizeStep.qml b/Modules/SetupWizard/SetupCustomizeStep.qml new file mode 100644 index 00000000..e5e0dcc1 --- /dev/null +++ b/Modules/SetupWizard/SetupCustomizeStep.qml @@ -0,0 +1,508 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +ColumnLayout { + id: root + + property real selectedScaleRatio: 1.0 + property string selectedBarPosition: "top" + property bool selectedDimDesktop: true + + signal scaleRatioChanged(real ratio) + signal barPositionChanged(string position) + signal dimDesktopChanged(bool dim) + + spacing: Style.marginM + + // Beautiful header with icon + RowLayout { + Layout.fillWidth: true + Layout.bottomMargin: Style.marginL + spacing: Style.marginM + + Rectangle { + width: 40 + height: 40 + radius: Style.radiusL + color: Color.mSurfaceVariant + opacity: 0.6 + + NIcon { + icon: "palette" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: I18n.tr("setup.customize.header") + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mPrimary + } + + NText { + text: I18n.tr("setup.customize.subheader") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + } + + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + contentWidth: availableWidth + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded + + ColumnLayout { + width: parent.width + spacing: Style.marginM + + // Bar Position section + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "layout-2" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("settings.bar.appearance.position.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("settings.bar.appearance.position.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Repeater { + model: [{ + "key": "top", + "name": I18n.tr("options.bar.position.top"), + "icon": "arrow-up" + }, { + "key": "bottom", + "name": I18n.tr("options.bar.position.bottom"), + "icon": "arrow-down" + }, { + "key": "left", + "name": I18n.tr("options.bar.position.left"), + "icon": "arrow-left" + }, { + "key": "right", + "name": I18n.tr("options.bar.position.right"), + "icon": "arrow-right" + }] + delegate: Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 40 + radius: Style.radiusM + border.width: 1 + + property bool isActive: selectedBarPosition === modelData.key + + color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant + border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline + opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8 + + NText { + text: modelData.name + pointSize: Style.fontSizeM + font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium + color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface + anchors.centerIn: parent + } + + HoverHandler { + id: hoverHandler + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + selectedBarPosition = modelData.key + barPositionChanged(modelData.key) + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Dim Desktop section + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "moon" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("settings.user-interface.dim-desktop.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("settings.user-interface.dim-desktop.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + NToggle { + checked: selectedDimDesktop + onToggled: function (checked) { + selectedDimDesktop = checked + dimDesktopChanged(checked) + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Bar Density section + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "minimize" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("settings.bar.appearance.density.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("settings.bar.appearance.density.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + RowLayout { + spacing: Style.marginS + Repeater { + model: [{ + "key": "mini", + "name": I18n.tr("options.bar.density.mini") + }, { + "key": "compact", + "name": I18n.tr("options.bar.density.compact") + }, { + "key": "default", + "name": I18n.tr("options.bar.density.default") + }, { + "key": "comfortable", + "name": I18n.tr("options.bar.density.comfortable") + }] + delegate: Rectangle { + radius: 16 + border.width: 1 + Layout.preferredHeight: 32 + Layout.preferredWidth: Math.max(90, densityText.implicitWidth + Style.marginXL * 2) + + property bool isActive: Settings.data.bar.density === modelData.key + + color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant + border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline + opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8 + + NText { + id: densityText + text: modelData.name + pointSize: Style.fontSizeS + font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium + color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface + anchors.centerIn: parent + } + + HoverHandler { + id: hoverHandler + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + Settings.data.bar.density = modelData.key + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // UI Scale section + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "maximize" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("settings.user-interface.scaling.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("settings.user-interface.scaling.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + NValueSlider { + Layout.fillWidth: true + from: 0.8 + to: 1.2 + stepSize: 0.05 + value: selectedScaleRatio + onMoved: function (value) { + selectedScaleRatio = value + scaleRatioChanged(value) + } + text: Math.floor(selectedScaleRatio * 100) + "%" + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Bar Floating toggle + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "layout-2" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("settings.bar.appearance.floating.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("settings.bar.appearance.floating.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + NToggle { + checked: Settings.data.bar.floating + onToggled: function (checked) { + Settings.data.bar.floating = checked + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Bar Background Opacity + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + NLabel { + label: I18n.tr("settings.bar.appearance.background-opacity.label") + description: I18n.tr("settings.bar.appearance.background-opacity.description") + } + NValueSlider { + Layout.fillWidth: true + from: 0 + to: 1 + stepSize: 0.01 + value: Settings.data.bar.backgroundOpacity + onMoved: function (value) { + Settings.data.bar.backgroundOpacity = value + } + text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%" + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + } + } +} diff --git a/Modules/SetupWizard/SetupWallpaperStep.qml b/Modules/SetupWizard/SetupWallpaperStep.qml new file mode 100644 index 00000000..aa915f72 --- /dev/null +++ b/Modules/SetupWizard/SetupWallpaperStep.qml @@ -0,0 +1,508 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Effects +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services +import qs.Widgets +import "../../Helpers/FuzzySort.js" as FuzzySort + +ColumnLayout { + id: root + + property string selectedDirectory: "" + property string selectedWallpaper: "" + + signal directoryChanged(string directory) + signal wallpaperChanged(string wallpaper) + + spacing: Style.marginL + + // Beautiful header with icon + ColumnLayout { + Layout.fillWidth: true + Layout.bottomMargin: Style.marginL + spacing: Style.marginM + + RowLayout { + spacing: Style.marginM + + Rectangle { + width: 40 + height: 40 + radius: Style.radiusL + color: Color.mSurfaceVariant + opacity: 0.6 + + NIcon { + icon: "image" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + spacing: Style.marginXS + + NText { + text: I18n.tr("setup.wallpaper.header") + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mPrimary + } + + NText { + text: I18n.tr("setup.wallpaper.subheader") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + } + } + + // Large preview with rounded corners and shadow effect + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 180 + color: Color.mSurfaceVariant + radius: Style.radiusL + border.color: selectedWallpaper !== "" ? Color.mPrimary : Color.mOutline + border.width: selectedWallpaper !== "" ? 2 : 1 + clip: true + + // Mirror WallpaperPanel approach with rounded shader mask + NImageCached { + id: previewCached + anchors.fill: parent + anchors.margins: 4 + maxCacheDimension: 512 + cacheFolder: Settings.cacheDirImagesWallpapers + imagePath: selectedWallpaper !== "" ? "file://" + selectedWallpaper : "" + visible: false // used as texture source for the shader + } + + ShaderEffect { + anchors.fill: parent + anchors.margins: 4 + property var source: ShaderEffectSource { + sourceItem: previewCached + hideSource: true + live: true + recursive: false + format: ShaderEffectSource.RGBA + } + property real itemWidth: width + property real itemHeight: height + property real cornerRadius: Style.radiusL + property real imageOpacity: 1.0 + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/rounded_image.frag.qsb") + supportsAtlasTextures: false + blending: true + } + + // Loading placeholder + Rectangle { + anchors.fill: parent + color: Color.mSurfaceVariant + radius: Style.radiusL + visible: (previewCached.status === Image.Loading || previewCached.status === Image.Null) && selectedWallpaper !== "" + + NIcon { + icon: "image" + pointSize: Style.fontSizeXXL + color: Color.mOnSurfaceVariant + anchors.centerIn: parent + } + } + + // Error placeholder + Rectangle { + anchors.fill: parent + color: Color.mError + opacity: 0.1 + radius: Style.radiusL + visible: previewCached.status === Image.Error && selectedWallpaper !== "" + + ColumnLayout { + anchors.centerIn: parent + spacing: Style.marginS + + NIcon { + icon: "alert-circle" + pointSize: Style.fontSizeXXL + color: Color.mError + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("setup.wallpaper.preview-error") + pointSize: Style.fontSizeS + color: Color.mError + Layout.alignment: Qt.AlignHCenter + } + } + } + + NBusyIndicator { + anchors.centerIn: parent + visible: (previewCached.status === Image.Loading || previewCached.status === Image.Null) && selectedWallpaper !== "" + running: visible + size: 28 + } + + ColumnLayout { + anchors.centerIn: parent + spacing: Style.marginL + visible: selectedWallpaper === "" + opacity: 0.6 + + Rectangle { + Layout.alignment: Qt.AlignHCenter + width: 64 + height: 64 + radius: width / 2 + color: Color.mPrimary + opacity: 0.15 + + NIcon { + icon: "sparkles" + pointSize: Style.fontSizeXXL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + NText { + text: I18n.tr("setup.wallpaper.select-prompt") + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + font.weight: Style.fontWeightMedium + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + // Wallpaper gallery strip + Item { + Layout.fillWidth: true + Layout.preferredHeight: 90 + visible: filteredWallpapers.length > 0 + + ScrollView { + anchors.fill: parent + clip: true + ScrollBar.horizontal.policy: ScrollBar.AsNeeded + ScrollBar.vertical.policy: ScrollBar.AlwaysOff + + RowLayout { + spacing: Style.marginM + height: parent.height + + Repeater { + model: filteredWallpapers + delegate: Rectangle { + Layout.preferredWidth: 120 + Layout.preferredHeight: 80 + color: Color.mSurface + radius: Style.radiusM + border.color: selectedWallpaper === modelData ? Color.mPrimary : Color.mOutline + border.width: selectedWallpaper === modelData ? 2 : 1 + clip: true + + // Cached thumbnail (used as shader source) + NImageCached { + id: thumbCached + anchors.fill: parent + anchors.margins: 3 + maxCacheDimension: 256 + cacheFolder: Settings.cacheDirImagesWallpapers + imagePath: "file://" + modelData + visible: false + } + + ShaderEffect { + anchors.fill: parent + anchors.margins: 3 + property var source: ShaderEffectSource { + sourceItem: thumbCached + hideSource: true + live: true + recursive: false + format: ShaderEffectSource.RGBA + } + property real itemWidth: width + property real itemHeight: height + property real cornerRadius: Style.radiusM - 3 + property real imageOpacity: 1.0 + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/rounded_image.frag.qsb") + supportsAtlasTextures: false + blending: true + } + + // Loading state + Rectangle { + anchors.fill: parent + color: Color.mSurfaceVariant + radius: Style.radiusM + visible: thumbCached.status === Image.Loading + + NIcon { + icon: "image" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + anchors.centerIn: parent + } + } + + // Error state + Rectangle { + anchors.fill: parent + color: Color.mSurfaceVariant + radius: Style.radiusM + visible: thumbCached.status === Image.Error + + NIcon { + icon: "image" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + anchors.centerIn: parent + } + } + + NBusyIndicator { + anchors.centerIn: parent + visible: thumbCached.status === Image.Loading || thumbCached.status === Image.Null + running: visible + size: 18 + } + + Rectangle { + anchors.fill: parent + color: Color.mPrimary + opacity: hoverHandler.hovered ? 0.1 : 0 + radius: Style.radiusM + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + + Rectangle { + visible: selectedWallpaper === modelData + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 6 + width: 24 + height: 24 + radius: width / 2 + color: Color.mPrimary + + NIcon { + icon: "check" + pointSize: Style.fontSizeS + color: Color.mOnPrimary + anchors.centerIn: parent + } + } + + HoverHandler { + id: hoverHandler + } + + TapHandler { + onTapped: { + selectedWallpaper = modelData + wallpaperChanged(modelData) + } + } + } + } + } + } + } + + // Helpful info card + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 80 + color: Color.mSurfaceVariant + radius: Style.radiusM + opacity: 0.4 + visible: filteredWallpapers.length === 0 + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + NIcon { + icon: "folder-open" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + NText { + text: filteredWallpapers.length === 0 && selectedDirectory !== "" ? I18n.tr("setup.wallpaper.none-in-dir") : I18n.tr("setup.wallpaper.no-dir") + pointSize: Style.fontSizeM + font.weight: Style.fontWeightBold + color: Color.mOnSurfaceVariant + } + NText { + text: selectedDirectory !== "" ? I18n.tr("setup.wallpaper.no-valid", { + "dir": selectedDirectory + }) : I18n.tr("setup.wallpaper.choose-dir") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + opacity: 0.8 + } + } + } + } + + // Directory selection + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NTextInputButton { + id: wallpaperPathInput + label: I18n.tr("setup.wallpaper.dir.label") + description: I18n.tr("setup.wallpaper.dir.description") + text: selectedDirectory + buttonIcon: "folder-open" + buttonTooltip: I18n.tr("setup.wallpaper.dir.browse") + Layout.fillWidth: true + onInputEditingFinished: { + selectedDirectory = text + directoryChanged(text) + } + onButtonClicked: directoryPicker.open() + } + } + + // Internal properties and functions + property list wallpapersList: [] + property list filteredWallpapers: [] + + function updateFilteredWallpapers() { + filteredWallpapers = wallpapersList + } + + function refreshWallpapers() { + if (!selectedDirectory || selectedDirectory === "") { + wallpapersList = [] + filteredWallpapers = [] + return + } + if (typeof WallpaperService !== "undefined" && WallpaperService.getWallpapersList) { + var wallpapers = WallpaperService.getWallpapersList(Screen.name) + wallpapersList = wallpapers + updateFilteredWallpapers() + if (wallpapersList.length > 0 && selectedWallpaper === "") { + selectedWallpaper = wallpapersList[0] + } + } else { + readDirectoryImages(selectedDirectory) + } + } + + function readDirectoryImages(directoryPath) { + directoryScanner.command = ["find", directoryPath, "-type", "f", "\\(-iname", "*.jpg", "-o", "-iname", "*.jpeg", "-o", "-iname", "*.png", "-o", "-iname", "*.bmp", "-o", "-iname", "*.webp", "-o", "-iname", "*.svg", "\\)"] + directoryScanner.running = true + return [] + } + + onSelectedDirectoryChanged: { + if (typeof Settings !== "undefined" && Settings.data && Settings.data.wallpaper) { + Settings.data.wallpaper.directory = selectedDirectory + } + if (typeof WallpaperService !== "undefined" && WallpaperService.refreshWallpapersList) { + WallpaperService.refreshWallpapersList() + } + Qt.callLater(refreshWallpapers) + } + + Connections { + target: WallpaperService + enabled: typeof WallpaperService !== "undefined" + function onWallpaperListChanged(screenName, count) { + if (screenName === Screen.name) { + Qt.callLater(refreshWallpapers) + } + } + } + + Timer { + id: initialRefreshTimer + interval: 1000 + running: false + repeat: false + onTriggered: refreshWallpapers() + } + + Component.onCompleted: { + if (typeof Settings !== "undefined" && Settings.data && Settings.data.wallpaper && Settings.data.wallpaper.directory) { + selectedDirectory = Settings.data.wallpaper.directory + } else { + selectedDirectory = Quickshell.env("HOME") + "/Pictures/Wallpapers" + } + if (typeof WallpaperService !== "undefined" && WallpaperService.currentWallpaper) { + selectedWallpaper = WallpaperService.currentWallpaper + } + initialRefreshTimer.start() + } + + NFilePicker { + id: directoryPicker + selectionMode: "folders" + title: I18n.tr("setup.wallpaper.dir.select-title") + initialPath: selectedDirectory || Quickshell.env("HOME") + "/Pictures" + onAccepted: paths => { + if (paths.length > 0) { + selectedDirectory = paths[0] + directoryChanged(paths[0]) + } + } + } + + Process { + id: directoryScanner + command: ["find", "", "-type", "f", "\\(-iname", "*.jpg", "-o", "-iname", "*.jpeg", "-o", "-iname", "*.png", "-o", "-iname", "*.bmp", "-o", "-iname", "*.webp", "-o", "-iname", "*.svg", "\\)"] + running: false + stdout: StdioCollector {} + stderr: StdioCollector {} + onExited: function (exitCode) { + if (exitCode === 0) { + var lines = stdout.text.split('\n') + var images = [] + for (var i = 0; i < lines.length; i++) { + var line = lines[i].trim() + if (line !== '') { + images.push(line) + } + } + wallpapersList = images + updateFilteredWallpapers() + if (wallpapersList.length > 0 && selectedWallpaper === "") { + selectedWallpaper = wallpapersList[0] + } + } + } + } +} diff --git a/Modules/SetupWizard/SetupWizard.qml b/Modules/SetupWizard/SetupWizard.qml new file mode 100644 index 00000000..2a7a5c1b --- /dev/null +++ b/Modules/SetupWizard/SetupWizard.qml @@ -0,0 +1,414 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services +import qs.Widgets + +NPanel { + id: root + + preferredWidth: 520 + preferredHeight: 600 + preferredWidthRatio: 0.4 + preferredHeightRatio: 0.6 + panelAnchorHorizontalCenter: true + panelAnchorVerticalCenter: true + panelKeyboardFocus: true + + // Prevent closing during setup + backgroundClickEnabled: false + draggable: false + + property int currentStep: 0 + property int totalSteps: 4 + + // Setup wizard data + property string selectedWallpaperDirectory: Settings.defaultWallpapersDirectory + property string selectedWallpaper: "" + property real selectedScaleRatio: 1.0 + property string selectedBarPosition: "top" + property bool selectedDimDesktop: true + + panelContent: Component { + Item { + id: container + anchors.fill: parent + + ColumnLayout { + id: wizardContent + anchors.fill: parent + anchors.margins: Style.marginXL + spacing: Style.marginL + + // Override ESC key to prevent closing during setup + Shortcut { + sequences: ["Escape"] + enabled: root.active + onActivated: { + + // Do nothing - prevent ESC from closing the setup wizard + } + context: Qt.WindowShortcut + } + + // Step content - takes most of the space + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 300 + + StackLayout { + id: stepStack + anchors.fill: parent + currentIndex: currentStep + + // Step 0: Welcome - Beautiful centered design + Item { + ColumnLayout { + anchors.centerIn: parent + width: Math.min(parent.width - Style.marginXL * 2, 420) + spacing: Style.marginXL + + // Logo with subtle glow effect + Item { + Layout.fillWidth: true + Layout.preferredHeight: 120 + Layout.alignment: Qt.AlignHCenter + + Rectangle { + anchors.centerIn: parent + width: 120 + height: 120 + radius: width / 2 + color: Color.mPrimary + opacity: 0.08 + scale: 1.3 + } + + Image { + anchors.centerIn: parent + width: 110 + height: 110 + source: Qt.resolvedUrl(Quickshell.shellDir + "/Assets/noctalia.svg") + fillMode: Image.PreserveAspectFit + smooth: true + + Rectangle { + anchors.fill: parent + color: Color.mSurfaceVariant + radius: width / 2 + border.color: Color.mOutline + border.width: 2 + visible: parent.status === Image.Error + + NIcon { + icon: "sparkles" + pointSize: Style.fontSizeXXL * 1.5 + color: Color.mPrimary + anchors.centerIn: parent + } + } + + // Subtle pulse animation + SequentialAnimation on scale { + running: true + loops: Animation.Infinite + NumberAnimation { + from: 1.0 + to: 1.05 + duration: 2000 + easing.type: Easing.InOutQuad + } + NumberAnimation { + from: 1.05 + to: 1.0 + duration: 2000 + easing.type: Easing.InOutQuad + } + } + } + } + + // Welcome text with gradient feel + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + spacing: Style.marginM + + NText { + text: "Welcome to Noctalia! ✨" + pointSize: Style.fontSizeXXL * 1.4 + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + NText { + text: "Let's make your desktop uniquely yours" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } + + // Friendly subtext + Rectangle { + Layout.fillWidth: true + Layout.topMargin: Style.marginL + Layout.preferredHeight: childrenRect.height + Style.marginM * 2 + color: Color.mSurfaceVariant + radius: Style.radiusL + opacity: 0.4 + + NText { + anchors.centerIn: parent + width: parent.width - Style.marginL * 2 + text: I18n.tr("setup.welcome.note") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } + } + } + } + } + + // Step 1: Wallpaper Setup + SetupWallpaperStep { + id: step1 + selectedDirectory: root.selectedWallpaperDirectory + selectedWallpaper: root.selectedWallpaper + onDirectoryChanged: function (directory) { + root.selectedWallpaperDirectory = directory + root.applyWallpaperSettings() + } + onWallpaperChanged: function (wallpaper) { + root.selectedWallpaper = wallpaper + root.applyWallpaperSettings() + } + } + + // Step 2: UI Configuration + SetupCustomizeStep { + id: step2 + selectedScaleRatio: root.selectedScaleRatio + selectedBarPosition: root.selectedBarPosition + selectedDimDesktop: root.selectedDimDesktop + onScaleRatioChanged: function (ratio) { + root.selectedScaleRatio = ratio + root.applyUISettings() + } + onBarPositionChanged: function (position) { + root.selectedBarPosition = position + root.applyUISettings() + } + onDimDesktopChanged: function (dim) { + root.selectedDimDesktop = dim + root.applyUISettings() + } + } + + // Step 3: Appearance - Dark mode and color source + SetupAppearanceStep { + id: step3 + } + } + } + + // Elegant divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + } + + // Modern progress indicator with labels + Item { + Layout.fillWidth: true + Layout.preferredHeight: 32 + + RowLayout { + anchors.centerIn: parent + spacing: Style.marginM + + Repeater { + model: [{ + "icon": "sparkles", + "label": "Welcome" + }, { + "icon": "image", + "label": "Wallpaper" + }, { + "icon": "settings", + "label": "Customize" + }, { + "icon": "palette", + "label": "Appearance" + }] + delegate: RowLayout { + spacing: Style.marginS + + Rectangle { + width: 24 + height: 24 + radius: width / 2 + color: index <= currentStep ? Color.mPrimary : Color.mSurfaceVariant + border.color: index === currentStep ? Color.mPrimary : "transparent" + border.width: index === currentStep ? 2 : 0 + + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeS + color: index <= currentStep ? Color.mOnPrimary : Color.mOnSurfaceVariant + anchors.centerIn: parent + } + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + } + } + } + + NText { + text: modelData.label + pointSize: Style.fontSizeS + color: index <= currentStep ? Color.mPrimary : Color.mOnSurfaceVariant + font.weight: index === currentStep ? Style.fontWeightBold : Style.fontWeightRegular + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + } + } + } + + // Connector line + Rectangle { + width: 40 + height: 2 + radius: 1 + color: index < currentStep ? Color.mPrimary : Color.mSurfaceVariant + visible: index < totalSteps - 1 + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + } + } + } + } + } + } + } + + // Smooth navigation buttons + Item { + Layout.fillWidth: true + Layout.preferredHeight: 44 + Layout.topMargin: Style.marginS + + RowLayout { + anchors.fill: parent + spacing: Style.marginM + + NButton { + text: "Skip Setup" + outlined: true + visible: currentStep === 0 + Layout.preferredHeight: 44 + onClicked: { + root.completeSetup() + } + } + + Item { + Layout.fillWidth: true + } + + NButton { + text: "← Back" + outlined: true + visible: currentStep > 0 + Layout.preferredHeight: 44 + onClicked: { + if (currentStep > 0) { + currentStep-- + } + } + } + + NButton { + text: currentStep === totalSteps - 1 ? "All Done!" : "Continue →" + Layout.preferredHeight: 44 + onClicked: { + if (currentStep < totalSteps - 1) { + currentStep++ + } else { + root.completeSetup() + } + } + } + } + } + } + } + } + + function completeSetup() { + Logger.log("SetupWizard", "Completing setup with selected options") + + if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) { + Settings.data.wallpaper.directory = selectedWallpaperDirectory + WallpaperService.refreshWallpapersList() + } + + if (selectedWallpaper !== "") { + WallpaperService.changeWallpaper(selectedWallpaper, undefined) + } + + Settings.data.general.scaleRatio = selectedScaleRatio + Settings.data.bar.position = selectedBarPosition + Settings.data.general.dimDesktop = selectedDimDesktop + Settings.data.setupCompleted = true + + Settings.saveImmediate() + Logger.log("SetupWizard", "Setup completed successfully") + root.close() + } + + function applyWallpaperSettings() { + if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) { + Settings.data.wallpaper.directory = selectedWallpaperDirectory + WallpaperService.refreshWallpapersList() + } + + if (selectedWallpaper !== "") { + WallpaperService.changeWallpaper(selectedWallpaper, undefined) + } + } + + function applyUISettings() { + Settings.data.general.scaleRatio = selectedScaleRatio + Settings.data.bar.position = selectedBarPosition + Settings.data.general.dimDesktop = selectedDimDesktop + } + + Component.onCompleted: { + Logger.log("SetupWizard", "Setup wizard opened") + // Initialize selections from existing settings to avoid overwriting user config + if (Settings && Settings.data) { + selectedScaleRatio = Settings.data.general.scaleRatio + selectedBarPosition = Settings.data.bar.position + selectedDimDesktop = Settings.data.general.dimDesktop + selectedWallpaperDirectory = Settings.data.wallpaper.directory || Settings.defaultWallpapersDirectory + } + } +} diff --git a/Modules/Wallpaper/WallpaperPanel.qml b/Modules/Wallpaper/WallpaperPanel.qml index be3a526b..2dcfe18e 100644 --- a/Modules/Wallpaper/WallpaperPanel.qml +++ b/Modules/Wallpaper/WallpaperPanel.qml @@ -423,7 +423,6 @@ NPanel { NIcon { icon: "check" pointSize: Style.fontSizeM - font.weight: Style.fontWeightBold color: Color.mOnSecondary anchors.centerIn: parent } diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index 53f7fe75..d8fa9589 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -105,8 +105,8 @@ Singleton { // Wallpaper Colors Generation // -------------------------------------------------------------------------------- function generateFromWallpaper() { - Logger.log("AppThemeService", "Generating from wallpaper on screen:", Screen.name) + // Logger.log("AppThemeService", "Generating from wallpaper on screen:", Screen.name) const wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''") if (!wp) { Logger.error("AppThemeService", "No wallpaper found") diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index cf64b853..0e2bea2a 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -60,7 +60,6 @@ Singleton { if (enabled) { setChargingMode(BatteryService.ChargingMode.Full) } else { - BatteryService.chargingMode = BatteryService.ChargingMode.Disabled BatteryService.initialSetter = true ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-setup")) PanelService.getPanel("batteryPanel")?.toggle(this) @@ -194,6 +193,7 @@ Singleton { Logger.log("BatteryService", "Battery Manager uninstalled successfully") ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-success")) Settings.data.battery.chargingMode = BatteryService.chargingMode + BatteryService.chargingMode = BatteryService.ChargingMode.Disabled cleanupProcess.running = true } else { ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-failed")) diff --git a/Services/BluetoothService.qml b/Services/BluetoothService.qml index c5e17d62..2911ee34 100644 --- a/Services/BluetoothService.qml +++ b/Services/BluetoothService.qml @@ -171,22 +171,22 @@ Singleton { function getSignalIcon(device) { if (!device || device.signalStrength === undefined || device.signalStrength <= 0) { - return "signal_cellular_null" + return "antenna-bars-off" } var signal = device.signalStrength if (signal >= 80) { - return "signal_cellular_4_bar" + return "antenna-bars-5" } if (signal >= 60) { - return "signal_cellular_3_bar" + return "antenna-bars-4" } if (signal >= 40) { - return "signal_cellular_2_bar" + return "antenna-bars-3" } if (signal >= 20) { - return "signal_cellular_1_bar" + return "antenna-bars-2" } - return "signal_cellular_0_bar" + return "antenna-bars-1" } function isDeviceBusy(device) { diff --git a/Services/MediaService.qml b/Services/MediaService.qml index 006285c9..b0a4eb16 100644 --- a/Services/MediaService.qml +++ b/Services/MediaService.qml @@ -38,12 +38,19 @@ Singleton { let allPlayers = Mpris.players.values let finalPlayers = [] const genericBrowsers = ["firefox", "chromium", "chrome"] + const blacklist = (Settings.data.audio && Settings.data.audio.mprisBlacklist) ? Settings.data.audio.mprisBlacklist : [] // Separate players into specific and generic lists let specificPlayers = [] let genericPlayers = [] for (var i = 0; i < allPlayers.length; i++) { const identity = String(allPlayers[i].identity || "").toLowerCase() + const match = blacklist.find(b => { + const s = String(b || "").toLowerCase() + return s && (identity.includes(s)) + }) + if (match) + continue if (genericBrowsers.some(b => identity.includes(b))) { genericPlayers.push(allPlayers[i]) } else { diff --git a/Widgets/NCheckbox.qml b/Widgets/NCheckbox.qml index f27694e6..9fe63be9 100644 --- a/Widgets/NCheckbox.qml +++ b/Widgets/NCheckbox.qml @@ -61,7 +61,6 @@ RowLayout { icon: "check" color: root.activeOnColor pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5) - font.weight: Style.fontWeightBold } MouseArea { diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index 4fd7875e..cc318f02 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -624,7 +624,6 @@ Popup { NIcon { icon: "filepicker-check" pointSize: Style.fontSizeS - font.weight: Style.fontWeightBold color: Color.mOnSecondary anchors.centerIn: parent } diff --git a/Widgets/NImageCached.qml b/Widgets/NImageCached.qml index 1343828b..edeb4baf 100644 --- a/Widgets/NImageCached.qml +++ b/Widgets/NImageCached.qml @@ -31,8 +31,9 @@ Image { } onCachePathChanged: { if (imageHash && cachePath) { - // Try to load the cached version, failure will be detected below in onStatusChanged - source = cachePath + // Check if cache file exists before trying to load it + cacheChecker.command = ["test", "-f", cachePath] + cacheChecker.running = true } } onStatusChanged: { @@ -48,4 +49,19 @@ Image { }) } } + + // Check if cache file exists to avoid warnings + Process { + id: cacheChecker + running: false + onExited: function (exitCode) { + if (exitCode === 0 && root.cachePath) { + // Cache file exists, load it + root.source = root.cachePath + } else if (root.imagePath) { + // Cache doesn't exist, load original directly + root.source = root.imagePath + } + } + } } diff --git a/Widgets/NReorderCheckboxes.qml b/Widgets/NReorderCheckboxes.qml index 9a8c1abc..a7d95927 100644 --- a/Widgets/NReorderCheckboxes.qml +++ b/Widgets/NReorderCheckboxes.qml @@ -217,7 +217,6 @@ Item { icon: "check" color: root.activeOnColor pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5) - font.weight: Style.fontWeightBold } MouseArea { diff --git a/flake.nix b/flake.nix index 54381f04..d2046bb1 100644 --- a/flake.nix +++ b/flake.nix @@ -49,9 +49,13 @@ nixpkgs.legacyPackages.${pkgs.system}.app2unit; }; - nixosModules.default = {pkgs, ...}: { + nixosModules.default = { + pkgs, + lib, + ... + }: { imports = [./nix/nixos-module.nix]; - services.noctalia-shell.package = self.packages.${pkgs.system}.default; + services.noctalia-shell.package = lib.mkDefault self.packages.${pkgs.system}.default; }; }; } diff --git a/shell.qml b/shell.qml index 39b76224..66d15d51 100644 --- a/shell.qml +++ b/shell.qml @@ -40,6 +40,7 @@ import qs.Modules.OSD import qs.Modules.Settings import qs.Modules.Toast import qs.Modules.Wallpaper +import qs.Modules.SetupWizard ShellRoot { id: shellRoot @@ -168,4 +169,32 @@ ShellRoot { } } } + + // ------------------------------ + // Setup Wizard + Loader { + id: setupWizardLoader + active: false + asynchronous: true + sourceComponent: SetupWizard {} + onLoaded: { + if (setupWizardLoader.item && setupWizardLoader.item.open) { + setupWizardLoader.item.open() + } + } + } + + Connections { + target: Settings + function onSettingsLoaded() { + // Only open the setup wizard for new users + if (!Settings.data.setupCompleted) { + if (Settings.data.settingsVersion >= Settings.settingsVersion) { + setupWizardLoader.active = true + } else { + Settings.data.setupCompleted = true + } + } + } + } }