import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell.Io import qs.Commons import qs.Services import qs.Widgets ColumnLayout { id: root // Cache for scheme JSON (can be flat or {dark, light}) property var schemeColorsCache: ({}) // Scale properties for card animations property real cardScaleLow: 0.95 property real cardScaleHigh: 1.0 // Helper function to get color from scheme file (supports dark/light variants) function getSchemeColor(schemePath, colorKey) { // Extract scheme name from path var schemeName = schemePath.split("/").pop().replace(".json", "") // Try to get from cached data first if (schemeColorsCache[schemeName]) { var entry = schemeColorsCache[schemeName] var variant = entry if (entry.dark || entry.light) { variant = Settings.data.colorSchemes.darkMode ? (entry.dark || entry.light) : (entry.light || entry.dark) } if (variant && variant[colorKey]) return variant[colorKey] } // Return a default color if not cached yet return "#000000" } // This function is called by the FileView Repeater when a scheme file is loaded function schemeLoaded(schemeName, jsonData) { var value = jsonData || {} var newCache = schemeColorsCache newCache[schemeName] = value schemeColorsCache = newCache } // When the list of available schemes changes, clear the cache. // The Repeater below will automatically re-create the FileViews. Connections { target: ColorSchemeService function onSchemesChanged() { schemeColorsCache = {} } } // Simple process to check if matugen exists Process { id: matugenCheck command: ["which", "matugen"] running: false onExited: function (exitCode) { if (exitCode === 0) { // Matugen exists, enable it Settings.data.colorSchemes.useWallpaperColors = true ColorSchemeService.changedWallpaper() ToastService.showNotice("Matugen", "Enabled") } else { // Matugen not found ToastService.showWarning("Matugen", "Not installed") } } stdout: StdioCollector {} stderr: StdioCollector {} } // A non-visual Item to host the Repeater that loads the color scheme files. Item { visible: false id: fileLoaders Repeater { model: ColorSchemeService.schemes // The delegate is a Component, which correctly wraps the non-visual FileView delegate: Item { FileView { path: modelData blockLoading: true onLoaded: { var schemeName = path.split("/").pop().replace(".json", "") try { var jsonData = JSON.parse(text()) root.schemeLoaded(schemeName, jsonData) } catch (e) { Logger.warn("ColorSchemeTab", "Failed to parse JSON for scheme:", schemeName, e) root.schemeLoaded(schemeName, null) // Load defaults on parse error } } } } } } ColumnLayout { spacing: 0 Item { Layout.fillWidth: true Layout.preferredHeight: 0 } ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true // Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants) NToggle { label: "Dark Mode" description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available." checked: Settings.data.colorSchemes.darkMode enabled: true onToggled: checked => { Settings.data.colorSchemes.darkMode = checked if (Settings.data.colorSchemes.useWallpaperColors) { MatugenService.generateFromWallpaper() } else if (Settings.data.colorSchemes.predefinedScheme) { // Re-apply current scheme to pick the right variant ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) // Force refresh of previews var tmp = schemeColorsCache schemeColorsCache = {} schemeColorsCache = tmp } } } // Use Matugen NToggle { label: "Enable Matugen" description: "Automatically generate colors based on your active wallpaper." checked: Settings.data.colorSchemes.useWallpaperColors onToggled: checked => { if (checked) { // Check if matugen is installed matugenCheck.running = true } else { Settings.data.colorSchemes.useWallpaperColors = false ToastService.showNotice("Matugen", "Disabled") } } } NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginXL * scaling Layout.bottomMargin: Style.marginXL * scaling } ColumnLayout { spacing: Style.marginS * scaling Layout.fillWidth: true NText { text: "Predefined Color Schemes" font.pointSize: Style.fontSizeXXL * scaling font.weight: Style.fontWeightBold color: Color.mSecondary } NText { text: "These color schemes are only active when 'Use Matugen' is turned off. With Matugen enabled, colors will be automatically generated from your wallpaper. You can still switch between light and dark themes while using Matugen." font.pointSize: Style.fontSizeM * scaling color: Color.mOnSurfaceVariant Layout.fillWidth: true wrapMode: Text.WordWrap } } // Color Schemes Grid GridLayout { columns: 4 rowSpacing: Style.marginL * scaling columnSpacing: Style.marginL * scaling Layout.fillWidth: true Repeater { model: ColorSchemeService.schemes Rectangle { id: schemeCard property string schemePath: modelData Layout.fillWidth: true Layout.preferredHeight: 120 * scaling radius: Style.radiusM * scaling color: getSchemeColor(modelData, "mSurface") border.width: Math.max(1, Style.borderL * scaling) border.color: Settings.data.colorSchemes.predefinedScheme === modelData ? Color.mPrimary : Color.mOutline scale: root.cardScaleLow // Mouse area for selection MouseArea { anchors.fill: parent onClicked: { // Disable useWallpaperColors when picking a predefined color scheme Settings.data.colorSchemes.useWallpaperColors = false Logger.log("ColorSchemeTab", "Disabled matugen setting") Settings.data.colorSchemes.predefinedScheme = schemePath ColorSchemeService.applyScheme(schemePath) } hoverEnabled: true cursorShape: Qt.PointingHandCursor onEntered: { schemeCard.scale = root.cardScaleHigh } onExited: { schemeCard.scale = root.cardScaleLow } } // Card content ColumnLayout { anchors.fill: parent anchors.margins: Style.marginXL * scaling spacing: Style.marginS * scaling // Scheme name NText { text: { // Remove json and the full path var chunks = schemePath.replace(".json", "").split("/") return chunks[chunks.length - 1] } font.pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold color: getSchemeColor(modelData, "mOnSurface") Layout.fillWidth: true elide: Text.ElideRight horizontalAlignment: Text.AlignHCenter } // Color swatches RowLayout { spacing: Style.marginS * scaling Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter // Primary color swatch Rectangle { width: 28 * scaling height: 28 * scaling radius: width * 0.5 color: getSchemeColor(modelData, "mPrimary") } // Secondary color swatch Rectangle { width: 28 * scaling height: 28 * scaling radius: width * 0.5 color: getSchemeColor(modelData, "mSecondary") } // Tertiary color swatch Rectangle { width: 28 * scaling height: 28 * scaling radius: width * 0.5 color: getSchemeColor(modelData, "mTertiary") } // Error color swatch Rectangle { width: 28 * scaling height: 28 * scaling radius: width * 0.5 color: getSchemeColor(modelData, "mError") } } } // Selection indicator Rectangle { visible: Settings.data.colorSchemes.predefinedScheme === schemePath anchors.right: parent.right anchors.top: parent.top anchors.margins: Style.marginS * scaling width: 24 * scaling height: 24 * scaling radius: width * 0.5 color: Color.mPrimary NText { anchors.centerIn: parent text: "✓" font.pointSize: Style.fontSizeXS * scaling font.weight: Style.fontWeightBold color: Color.mOnPrimary } } // Smooth animations Behavior on scale { NumberAnimation { duration: Style.animationNormal easing.type: Easing.OutCubic } } Behavior on border.color { ColorAnimation { duration: Style.animationNormal } } Behavior on border.width { NumberAnimation { duration: Style.animationFast } } } } } } } NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginXL * scaling Layout.bottomMargin: Style.marginXL * scaling visible: Settings.data.colorSchemes.useWallpaperColors } // Matugen template toggles (moved from MatugenTab) ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true visible: Settings.data.colorSchemes.useWallpaperColors ColumnLayout { spacing: Style.marginS * scaling Layout.fillWidth: true NText { text: "Matugen Templates" font.pointSize: Style.fontSizeXXL * scaling font.weight: Style.fontWeightBold color: Color.mSecondary } NText { text: "Select which external components Matugen should apply theming to." font.pointSize: Style.fontSizeM * scaling color: Color.mOnSurfaceVariant Layout.fillWidth: true wrapMode: Text.WordWrap } } NCheckbox { label: "GTK 4 (libadwaita)" description: "Write ~/.config/gtk-4.0/gtk.css" checked: Settings.data.matugen.gtk4 onToggled: checked => { Settings.data.matugen.gtk4 = checked if (Settings.data.colorSchemes.useWallpaperColors) MatugenService.generateFromWallpaper() } } NCheckbox { label: "GTK 3" description: "Write ~/.config/gtk-3.0/gtk.css" checked: Settings.data.matugen.gtk3 onToggled: checked => { Settings.data.matugen.gtk3 = checked if (Settings.data.colorSchemes.useWallpaperColors) MatugenService.generateFromWallpaper() } } NCheckbox { label: "Qt6ct" description: "Write ~/.config/qt6ct/colors/noctalia.conf" checked: Settings.data.matugen.qt6 onToggled: checked => { Settings.data.matugen.qt6 = checked if (Settings.data.colorSchemes.useWallpaperColors) MatugenService.generateFromWallpaper() } } NCheckbox { label: "Qt5ct" description: "Write ~/.config/qt5ct/colors/noctalia.conf" checked: Settings.data.matugen.qt5 onToggled: checked => { Settings.data.matugen.qt5 = checked if (Settings.data.colorSchemes.useWallpaperColors) MatugenService.generateFromWallpaper() } } NCheckbox { label: "Kitty" description: "Write ~/.config/kitty/themes/noctalia.conf and reload" checked: Settings.data.matugen.kitty onToggled: checked => { Settings.data.matugen.kitty = checked if (Settings.data.colorSchemes.useWallpaperColors) MatugenService.generateFromWallpaper() } } NCheckbox { label: "Ghostty" description: "Write ~/.config/ghostty/themes/noctalia and reload" checked: Settings.data.matugen.ghostty onToggled: checked => { Settings.data.matugen.ghostty = checked if (Settings.data.colorSchemes.useWallpaperColors) MatugenService.generateFromWallpaper() } } } }