diff --git a/Assets/Matugen/Matugen.qml b/Assets/Matugen/Matugen.qml new file mode 100644 index 0000000..0385b32 --- /dev/null +++ b/Assets/Matugen/Matugen.qml @@ -0,0 +1,52 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons + +// Central place to define which templates we generate and where they write. +// Users can extend it by dropping additional templates into: +// - Assets/Matugen/templates/ +Singleton { + id: root + + // Build the base TOML using current settings + function buildConfigToml() { + var lines = [] + lines.push("[config]") + + // Always include noctalia colors output for the shell + lines.push("[templates.noctalia]") + lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/noctalia.json"') + lines.push('output_path = "' + Settings.configDir + 'colors.json"') + + if (Settings.data.matugen.gtk4) { + lines.push("\n[templates.gtk4]") + lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/gtk4.css"') + lines.push('output_path = "~/.config/gtk-4.0/gtk.css"') + } + if (Settings.data.matugen.gtk3) { + lines.push("\n[templates.gtk3]") + lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/gtk3.css"') + lines.push('output_path = "~/.config/gtk-3.0/gtk.css"') + } + if (Settings.data.matugen.qt6) { + lines.push("\n[templates.qt6]") + lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/qtct.conf"') + lines.push('output_path = "~/.config/qt6ct/colors/noctalia.conf"') + } + if (Settings.data.matugen.qt5) { + lines.push("\n[templates.qt5]") + lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/qtct.conf"') + lines.push('output_path = "~/.config/qt5ct/colors/noctalia.conf"') + } + if (Settings.data.matugen.kitty) { + lines.push("\n[templates.kitty]") + lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/kitty.conf"') + lines.push('output_path = "~/.config/kitty/themes/noctalia.conf"') + lines.push("post_hook = 'kitty +kitten themes --reload-in=all noctalia'") + } + + return lines.join("\n") + "\n" + } +} diff --git a/Assets/Matugen/matugen.base.toml b/Assets/Matugen/matugen.base.toml deleted file mode 100644 index 4cb465a..0000000 --- a/Assets/Matugen/matugen.base.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Base: only write Noctalia colors.json for the shell -[config] - -[templates.noctalia] -input_path = "templates/noctalia.json" -output_path = "~/.config/noctalia/colors.json" diff --git a/Assets/Matugen/matugen.toml b/Assets/Matugen/matugen.toml deleted file mode 100644 index 1633bfd..0000000 --- a/Assets/Matugen/matugen.toml +++ /dev/null @@ -1,31 +0,0 @@ -# This file configures how matugen generates colors from wallpapers for Noctalia -[config] - - -[templates.noctalia] -input_path = "templates/noctalia.json" -output_path = "~/.config/noctalia/colors.json" - -# GTK 4 (libadwaita) variables override -[templates.gtk4] -input_path = "templates/gtk4.css" -output_path = "~/.config/gtk-4.0/gtk.css" - -# GTK 3 named-colors fallback for legacy apps -[templates.gtk3] -input_path = "templates/gtk3.css" -output_path = "~/.config/gtk-3.0/gtk.css" - -# Qt6ct color scheme (can also be used by qt5ct in many distros) -[templates.qt6] -input_path = "templates/qtct.conf" -output_path = "~/.config/qt6ct/colors/noctalia.conf" - -[templates.qt5] -input_path = "templates/qtct.conf" -output_path = "~/.config/qt5ct/colors/noctalia.conf" - -[templates.kitty] -input_path = "templates/kitty.conf" -output_path = "~/.config/kitty/themes/noctalia.conf" -post_hook = 'kitty +kitten themes --reload-in=all noctalia' diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 47561c7..8f8c65f 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -256,8 +256,16 @@ Singleton { property bool useWallpaperColors: false property string predefinedScheme: "" property bool darkMode: true - // External app theming (GTK & Qt) - property bool themeApps: false + } + + // matugen templates toggles + property JsonObject matugen: JsonObject { + // Per-template flags to control dynamic config generation + property bool gtk4: false + property bool gtk3: false + property bool qt6: false + property bool qt5: false + property bool kitty: false } // night light diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index 4a2b47f..9421b15 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -25,8 +25,10 @@ Variants { property var removingNotifications: ({}) // If no notification display activated in settings, then show them all - active: Settings.isLoaded && modelData && (NotificationService.notificationModel.count > 0) ? (Settings.data.notifications.monitors.includes(modelData.name) - || (Settings.data.notifications.monitors.length === 0)) : false + active: Settings.isLoaded && modelData + && (NotificationService.notificationModel.count > 0) ? (Settings.data.notifications.monitors.includes( + modelData.name) + || (Settings.data.notifications.monitors.length === 0)) : false visible: (NotificationService.notificationModel.count > 0) @@ -51,7 +53,7 @@ Variants { NotificationService.animateAndRemove.connect(function (notification, index) { // Prefer lookup by identity to avoid index mismatches var delegate = null - if (notificationStack.children && notificationStack.children.length > 0) { + if (notificationStack && notificationStack.children && notificationStack.children.length > 0) { for (var i = 0; i < notificationStack.children.length; i++) { var child = notificationStack.children[i] if (child && child.model && child.model.rawNotification === notification) { @@ -62,7 +64,7 @@ Variants { } // Fallback to index if identity lookup failed - if (!delegate && notificationStack.children && notificationStack.children[index]) { + if (!delegate && notificationStack && notificationStack.children && notificationStack.children[index]) { delegate = notificationStack.children[index] } diff --git a/Modules/SettingsPanel/Tabs/AboutTab.qml b/Modules/SettingsPanel/Tabs/AboutTab.qml index bc02641..876c1c0 100644 --- a/Modules/SettingsPanel/Tabs/AboutTab.qml +++ b/Modules/SettingsPanel/Tabs/AboutTab.qml @@ -9,7 +9,7 @@ import qs.Services import qs.Widgets ColumnLayout { - id: root + id: root property string latestVersion: GitHubService.latestVersion property string currentVersion: "Unknown" // Fallback version @@ -37,199 +37,196 @@ ColumnLayout { } } + NText { + text: "Noctalia Shell" + font.pointSize: Style.fontSizeXXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.alignment: Qt.AlignCenter + Layout.bottomMargin: Style.marginS * scaling + } + + GridLayout { + Layout.alignment: Qt.AlignCenter + columns: 2 + rowSpacing: Style.marginXS * scaling + columnSpacing: Style.marginS * scaling + + NText { + text: "Latest Version:" + color: Color.mOnSurface + Layout.alignment: Qt.AlignRight + } + + NText { + text: root.latestVersion + color: Color.mOnSurface + font.weight: Style.fontWeightBold + } + + NText { + text: "Installed Version:" + color: Color.mOnSurface + Layout.alignment: Qt.AlignRight + } + + NText { + text: root.currentVersion + color: Color.mOnSurface + font.weight: Style.fontWeightBold + } + } + + Rectangle { + Layout.alignment: Qt.AlignCenter + Layout.topMargin: Style.marginS * scaling + Layout.preferredWidth: updateText.implicitWidth + 46 * scaling + Layout.preferredHeight: Style.barHeight * scaling + radius: Style.radiusL * scaling + color: updateArea.containsMouse ? Color.mPrimary : Color.transparent + border.color: Color.mPrimary + border.width: Math.max(1, Style.borderS * scaling) + visible: { + if (root.currentVersion === "Unknown" || root.latestVersion === "Unknown") + return false + + const latest = root.latestVersion.replace("v", "").split(".") + const current = root.currentVersion.replace("v", "").split(".") + for (var i = 0; i < Math.max(latest.length, current.length); i++) { + const l = parseInt(latest[i] || "0") + const c = parseInt(current[i] || "0") + if (l > c) + return true + + if (l < c) + return false + } + return false + } + + RowLayout { + anchors.centerIn: parent + spacing: Style.marginS * scaling + + NIcon { + text: "system_update" + font.pointSize: Style.fontSizeXXL * scaling + color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary + } NText { - text: "Noctalia Shell" - font.pointSize: Style.fontSizeXXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.alignment: Qt.AlignCenter - Layout.bottomMargin: Style.marginS * scaling + id: updateText + text: "Download latest release" + font.pointSize: Style.fontSizeL * scaling + color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary } + } - GridLayout { - Layout.alignment: Qt.AlignCenter - columns: 2 - rowSpacing: Style.marginXS * scaling - columnSpacing: Style.marginS * scaling + MouseArea { + id: updateArea - NText { - text: "Latest Version:" - color: Color.mOnSurface - Layout.alignment: Qt.AlignRight - } - - NText { - text: root.latestVersion - color: Color.mOnSurface - font.weight: Style.fontWeightBold - } - - NText { - text: "Installed Version:" - color: Color.mOnSurface - Layout.alignment: Qt.AlignRight - } - - NText { - text: root.currentVersion - color: Color.mOnSurface - font.weight: Style.fontWeightBold - } + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + Quickshell.execDetached(["xdg-open", "https://github.com/Ly-sec/Noctalia/releases/latest"]) } + } + } - Rectangle { - Layout.alignment: Qt.AlignCenter - Layout.topMargin: Style.marginS * scaling - Layout.preferredWidth: updateText.implicitWidth + 46 * scaling - Layout.preferredHeight: Style.barHeight * scaling + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginxL * scaling + } + + NText { + text: `Shout-out to our ${root.contributors.length} awesome contributors!` + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.alignment: Qt.AlignCenter + Layout.topMargin: Style.marginL * 2 + } + + ScrollView { + Layout.alignment: Qt.AlignCenter + Layout.preferredWidth: 200 * Style.marginXS * scaling + Layout.fillHeight: true + Layout.topMargin: Style.marginL * scaling + clip: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded + + GridView { + id: contributorsGrid + + anchors.fill: parent + width: 200 * 4 * scaling + height: Math.ceil(root.contributors.length / 4) * 100 + cellWidth: Style.baseWidgetSize * 6.25 * scaling + cellHeight: Style.baseWidgetSize * 3.125 * scaling + model: root.contributors + + delegate: Rectangle { + width: contributorsGrid.cellWidth - Style.marginL * scaling + height: contributorsGrid.cellHeight - Style.marginXS * scaling radius: Style.radiusL * scaling - color: updateArea.containsMouse ? Color.mPrimary : Color.transparent - border.color: Color.mPrimary - border.width: Math.max(1, Style.borderS * scaling) - visible: { - if (root.currentVersion === "Unknown" || root.latestVersion === "Unknown") - return false - - const latest = root.latestVersion.replace("v", "").split(".") - const current = root.currentVersion.replace("v", "").split(".") - for (var i = 0; i < Math.max(latest.length, current.length); i++) { - const l = parseInt(latest[i] || "0") - const c = parseInt(current[i] || "0") - if (l > c) - return true - - if (l < c) - return false - } - return false - } + color: contributorArea.containsMouse ? Color.mSecondary : Color.transparent RowLayout { - anchors.centerIn: parent - spacing: Style.marginS * scaling + anchors.fill: parent + anchors.margins: Style.marginS * scaling + spacing: Style.marginM * scaling - NIcon { - text: "system_update" - font.pointSize: Style.fontSizeXXL * scaling - color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary + Item { + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: Style.baseWidgetSize * 2 * scaling + Layout.preferredHeight: Style.baseWidgetSize * 2 * scaling + + NImageCircled { + imagePath: modelData.avatar_url || "" + anchors.fill: parent + anchors.margins: Style.marginXS * scaling + fallbackIcon: "person" + borderColor: Color.mPrimary + borderWidth: Math.max(1, Style.borderM * scaling) + } } - NText { - id: updateText - text: "Download latest release" - font.pointSize: Style.fontSizeL * scaling - color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary + ColumnLayout { + spacing: Style.marginXS * scaling + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + + NText { + text: modelData.login || "Unknown" + font.weight: Style.fontWeightBold + color: contributorArea.containsMouse ? Color.mSurface : Color.mOnSurface + elide: Text.ElideRight + Layout.fillWidth: true + } + + NText { + text: (modelData.contributions || 0) + " " + ((modelData.contributions || 0) === 1 ? "commit" : "commits") + font.pointSize: Style.fontSizeXS * scaling + color: contributorArea.containsMouse ? Color.mSurface : Color.mOnSurface + } } } MouseArea { - id: updateArea + id: contributorArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - Quickshell.execDetached(["xdg-open", "https://github.com/Ly-sec/Noctalia/releases/latest"]) - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginxL * scaling - } - - NText { - text: `Shout-out to our ${root.contributors.length} awesome contributors!` - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.alignment: Qt.AlignCenter - Layout.topMargin: Style.marginL * 2 - } - - ScrollView { - Layout.alignment: Qt.AlignCenter - Layout.preferredWidth: 200 * Style.marginXS * scaling - Layout.fillHeight: true - Layout.topMargin: Style.marginL * scaling - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded - - GridView { - id: contributorsGrid - - anchors.fill: parent - width: 200 * 4 * scaling - height: Math.ceil(root.contributors.length / 4) * 100 - cellWidth: Style.baseWidgetSize * 6.25 * scaling - cellHeight: Style.baseWidgetSize * 3.125 * scaling - model: root.contributors - - delegate: Rectangle { - width: contributorsGrid.cellWidth - Style.marginL * scaling - height: contributorsGrid.cellHeight - Style.marginXS * scaling - radius: Style.radiusL * scaling - color: contributorArea.containsMouse ? Color.mSecondary : Color.transparent - - RowLayout { - anchors.fill: parent - anchors.margins: Style.marginS * scaling - spacing: Style.marginM * scaling - - Item { - Layout.alignment: Qt.AlignVCenter - Layout.preferredWidth: Style.baseWidgetSize * 2 * scaling - Layout.preferredHeight: Style.baseWidgetSize * 2 * scaling - - NImageCircled { - imagePath: modelData.avatar_url || "" - anchors.fill: parent - anchors.margins: Style.marginXS * scaling - fallbackIcon: "person" - borderColor: Color.mPrimary - borderWidth: Math.max(1, Style.borderM * scaling) - } - } - - ColumnLayout { - spacing: Style.marginXS * scaling - Layout.alignment: Qt.AlignVCenter - Layout.fillWidth: true - - NText { - text: modelData.login || "Unknown" - font.weight: Style.fontWeightBold - color: contributorArea.containsMouse ? Color.mSurface : Color.mOnSurface - elide: Text.ElideRight - Layout.fillWidth: true - } - - NText { - text: (modelData.contributions || 0) + " " + ((modelData.contributions - || 0) === 1 ? "commit" : "commits") - font.pointSize: Style.fontSizeXS * scaling - color: contributorArea.containsMouse ? Color.mSurface : Color.mOnSurface - } - } - } - - MouseArea { - id: contributorArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - if (modelData.html_url) - Quickshell.execDetached(["xdg-open", modelData.html_url]) - } - } + if (modelData.html_url) + Quickshell.execDetached(["xdg-open", modelData.html_url]) } } } } - + } +} diff --git a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml index 795d03c..699e68e 100644 --- a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml +++ b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml @@ -104,7 +104,6 @@ ColumnLayout { } ColumnLayout { - width: scrollView.availableWidth spacing: 0 Item { @@ -125,7 +124,7 @@ ColumnLayout { onToggled: checked => { Settings.data.colorSchemes.darkMode = checked if (Settings.data.colorSchemes.useWallpaperColors) { - ColorSchemeService.changedWallpaper() + MatugenService.generateFromWallpaper() } else if (Settings.data.colorSchemes.predefinedScheme) { // Re-apply current scheme to pick the right variant ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) @@ -137,19 +136,6 @@ ColumnLayout { } } - // App theming - NToggle { - label: "Theme external apps (GTK, Qt & kitty)" - description: "Writes GTK (gtk.css), Qt5/6 (noctalia.conf) and Kitty (noctalia.conf) themes based on your colors." - checked: Settings.data.colorSchemes.themeApps - onToggled: checked => { - Settings.data.colorSchemes.themeApps = checked - if (Settings.data.colorSchemes.useWallpaperColors) { - ColorSchemeService.changedWallpaper() - } - } - } - // Use Matugen NToggle { label: "Enable Matugen" @@ -348,5 +334,84 @@ ColumnLayout { 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 + + NText { + text: "Matugen Templates" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + Layout.bottomMargin: Style.marginS * scaling + } + + NText { + text: "Choose which external components should be themed by Matugen." + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + + NToggle { + 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() + } + } + + NToggle { + 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() + } + } + + NToggle { + 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() + } + } + + NToggle { + 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() + } + } + + NToggle { + 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() + } + } } } diff --git a/Modules/SettingsPanel/Tabs/DisplayTab.qml b/Modules/SettingsPanel/Tabs/DisplayTab.qml index 4eecb3b..71aaa96 100644 --- a/Modules/SettingsPanel/Tabs/DisplayTab.qml +++ b/Modules/SettingsPanel/Tabs/DisplayTab.qml @@ -43,168 +43,166 @@ ColumnLayout { }) } + NText { + text: "Monitor-specific configuration" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + } - NText { - text: "Monitor-specific configuration" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - } + NText { + text: "Bars and notifications appear on all displays by default. Choose specific displays below to limit where they're shown." + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + Layout.preferredWidth: parent.width - (Style.marginL * 2 * scaling) + } - NText { - text: "Bars and notifications appear on all displays by default. Choose specific displays below to limit where they're shown." - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - Layout.preferredWidth: parent.width - (Style.marginL * 2 * scaling) - } + ColumnLayout { + spacing: Style.marginL * scaling + Layout.topMargin: Style.marginL * scaling - ColumnLayout { - spacing: Style.marginL * scaling - Layout.topMargin: Style.marginL * scaling + Repeater { + model: Quickshell.screens || [] + delegate: Rectangle { + Layout.fillWidth: true + Layout.minimumWidth: 550 * scaling + radius: Style.radiusM * scaling + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling - Repeater { - model: Quickshell.screens || [] - delegate: Rectangle { - Layout.fillWidth: true - Layout.minimumWidth: 550 * scaling - radius: Style.radiusM * scaling - color: Color.mSurface - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling + ColumnLayout { + id: contentCol + anchors.fill: parent + anchors.margins: Style.marginL * scaling + spacing: Style.marginXXS * scaling + + NText { + text: (modelData.name || "Unknown") + font.pointSize: Style.fontSizeXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + NText { + text: `Resolution: ${modelData.width}x${modelData.height} - Position: (${modelData.x}, ${modelData.y})` + font.pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } ColumnLayout { - id: contentCol - anchors.fill: parent - anchors.margins: Style.marginL * scaling - spacing: Style.marginXXS * scaling + spacing: Style.marginL * scaling + Layout.fillWidth: true - NText { - text: (modelData.name || "Unknown") - font.pointSize: Style.fontSizeXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary + NToggle { + Layout.fillWidth: true + label: "Bar" + description: "Enable the bar on this monitor." + checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name) + } else { + Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name) + } + } } - NText { - text: `Resolution: ${modelData.width}x${modelData.height} - Position: (${modelData.x}, ${modelData.y})` - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap + NToggle { Layout.fillWidth: true + label: "Notifications" + description: "Enable notifications on this monitor." + checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors, + modelData.name) + } else { + Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors, + modelData.name) + } + } + } + + NToggle { + Layout.fillWidth: true + label: "Dock" + description: "Enable the dock on this monitor." + checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + Settings.data.dock.monitors = addMonitor(Settings.data.dock.monitors, modelData.name) + } else { + Settings.data.dock.monitors = removeMonitor(Settings.data.dock.monitors, modelData.name) + } + } } ColumnLayout { - spacing: Style.marginL * scaling + spacing: Style.marginS * scaling Layout.fillWidth: true - NToggle { + RowLayout { Layout.fillWidth: true - label: "Bar" - description: "Enable the bar on this monitor." - checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1 - onToggled: checked => { - if (checked) { - Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name) - } else { - Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name) - } - } - } + spacing: Style.marginL * scaling - NToggle { - Layout.fillWidth: true - label: "Notifications" - description: "Enable notifications on this monitor." - checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1 - onToggled: checked => { - if (checked) { - Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors, - modelData.name) - } else { - Settings.data.notifications.monitors = removeMonitor( - Settings.data.notifications.monitors, modelData.name) - } - } - } - - NToggle { - Layout.fillWidth: true - label: "Dock" - description: "Enable the dock on this monitor." - checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1 - onToggled: checked => { - if (checked) { - Settings.data.dock.monitors = addMonitor(Settings.data.dock.monitors, modelData.name) - } else { - Settings.data.dock.monitors = removeMonitor(Settings.data.dock.monitors, modelData.name) - } - } - } - - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - - RowLayout { + ColumnLayout { + spacing: Style.marginXXS * scaling Layout.fillWidth: true - spacing: Style.marginL * scaling - - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true - - NText { - text: "Scale" - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - NText { - text: "Scale the user interface on this monitor." - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - } NText { - text: `${Math.round(ScalingService.scaleByName(modelData.name) * 100)}%` - Layout.alignment: Qt.AlignVCenter - Layout.minimumWidth: 50 * scaling - horizontalAlignment: Text.AlignRight + text: "Scale" + font.pointSize: Style.fontSizeM * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: "Scale the user interface on this monitor." + font.pointSize: Style.fontSizeS * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true } } - RowLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true + NText { + text: `${Math.round(ScalingService.scaleByName(modelData.name) * 100)}%` + Layout.alignment: Qt.AlignVCenter + Layout.minimumWidth: 50 * scaling + horizontalAlignment: Text.AlignRight + } + } - NSlider { - id: scaleSlider - from: 0.7 - to: 1.8 - stepSize: 0.01 - value: ScalingService.scaleByName(modelData.name) - onPressedChanged: { - var data = Settings.data.monitorsScaling || {} - data[modelData.name] = value - Settings.data.monitorsScaling = data - } - Layout.fillWidth: true - Layout.minimumWidth: 150 * scaling + RowLayout { + spacing: Style.marginS * scaling + Layout.fillWidth: true + + NSlider { + id: scaleSlider + from: 0.7 + to: 1.8 + stepSize: 0.01 + value: ScalingService.scaleByName(modelData.name) + onPressedChanged: { + var data = Settings.data.monitorsScaling || {} + data[modelData.name] = value + Settings.data.monitorsScaling = data } + Layout.fillWidth: true + Layout.minimumWidth: 150 * scaling + } - NIconButton { - icon: "refresh" - tooltipText: "Reset Scaling" - onClicked: { - var data = Settings.data.monitorsScaling || {} - data[modelData.name] = 1.0 - Settings.data.monitorsScaling = data - } + NIconButton { + icon: "refresh" + tooltipText: "Reset Scaling" + onClicked: { + var data = Settings.data.monitorsScaling || {} + data[modelData.name] = 1.0 + Settings.data.monitorsScaling = data } } } @@ -212,121 +210,121 @@ ColumnLayout { } } } + } - NDivider { + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Night Light Section + ColumnLayout { + spacing: Style.marginXS * scaling + NText { + text: "Night Light" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + NText { + text: "Reduce blue light emission to help you sleep better and reduce eye strain." + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginXL * scaling + Layout.preferredWidth: parent.width - (Style.marginL * 2 * scaling) } + } - // Night Light Section - ColumnLayout { - spacing: Style.marginXS * scaling - NText { - text: "Night Light" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - } + NToggle { + label: "Enable Night Light" + description: "Apply a warm color filter to reduce blue light emission." + checked: Settings.data.nightLight.enabled + onToggled: checked => Settings.data.nightLight.enabled = checked + } - NText { - text: "Reduce blue light emission to help you sleep better and reduce eye strain." - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap + NToggle { + label: "Auto Schedule" + description: "Automatically enable night light based on time schedule." + checked: Settings.data.nightLight.autoSchedule + onToggled: checked => Settings.data.nightLight.autoSchedule = checked + } + + // Intensity settings + ColumnLayout { + NLabel { + label: "Intensity" + description: "Higher values create warmer light." + } + RowLayout { + spacing: Style.marginS * scaling + + NSlider { + from: 0 + to: 1 + stepSize: 0.01 + value: Settings.data.nightLight.intensity + onMoved: Settings.data.nightLight.intensity = value Layout.fillWidth: true - Layout.preferredWidth: parent.width - (Style.marginL * 2 * scaling) - } - } - - NToggle { - label: "Enable Night Light" - description: "Apply a warm color filter to reduce blue light emission." - checked: Settings.data.nightLight.enabled - onToggled: checked => Settings.data.nightLight.enabled = checked - } - - NToggle { - label: "Auto Schedule" - description: "Automatically enable night light based on time schedule." - checked: Settings.data.nightLight.autoSchedule - onToggled: checked => Settings.data.nightLight.autoSchedule = checked - } - - // Intensity settings - ColumnLayout { - NLabel { - label: "Intensity" - description: "Higher values create warmer light." - } - RowLayout { - spacing: Style.marginS * scaling - - NSlider { - from: 0 - to: 1 - stepSize: 0.01 - value: Settings.data.nightLight.intensity - onMoved: Settings.data.nightLight.intensity = value - Layout.fillWidth: true - Layout.minimumWidth: 150 * scaling - } - - NText { - text: `${Math.round(Settings.data.nightLight.intensity * 100)}%` - Layout.alignment: Qt.AlignVCenter - Layout.minimumWidth: 60 * scaling - horizontalAlignment: Text.AlignRight - } - } - } - - // Schedule settings - ColumnLayout { - spacing: Style.marginXS * scaling - - NLabel { - label: "Schedule" - description: "Set a start and end time for automatic schedule." + Layout.minimumWidth: 150 * scaling } - RowLayout { - Layout.fillWidth: false - spacing: Style.marginM * scaling - - NText { - text: "Start Time" - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant - } - - NComboBox { - model: timeOptions - currentKey: Settings.data.nightLight.startTime - placeholder: "Select start time" - onSelected: key => Settings.data.nightLight.startTime = key - preferredWidth: 120 * scaling - } - - Item {// add a little more spacing - } - - NText { - text: "Stop Time" - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant - } - NComboBox { - model: timeOptions - currentKey: Settings.data.nightLight.stopTime - placeholder: "Select stop time" - onSelected: key => Settings.data.nightLight.stopTime = key - preferredWidth: 120 * scaling - } + NText { + text: `${Math.round(Settings.data.nightLight.intensity * 100)}%` + Layout.alignment: Qt.AlignVCenter + Layout.minimumWidth: 60 * scaling + horizontalAlignment: Text.AlignRight } } } - + + // Schedule settings + ColumnLayout { + spacing: Style.marginXS * scaling + + NLabel { + label: "Schedule" + description: "Set a start and end time for automatic schedule." + } + + RowLayout { + Layout.fillWidth: false + spacing: Style.marginM * scaling + + NText { + text: "Start Time" + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant + } + + NComboBox { + model: timeOptions + currentKey: Settings.data.nightLight.startTime + placeholder: "Select start time" + onSelected: key => Settings.data.nightLight.startTime = key + preferredWidth: 120 * scaling + } + + Item {// add a little more spacing + } + + NText { + text: "Stop Time" + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant + } + NComboBox { + model: timeOptions + currentKey: Settings.data.nightLight.stopTime + placeholder: "Select stop time" + onSelected: key => Settings.data.nightLight.stopTime = key + preferredWidth: 120 * scaling + } + } + } + } NDivider { Layout.fillWidth: true diff --git a/Services/ColorSchemeService.qml b/Services/ColorSchemeService.qml index 0847f07..db8868f 100644 --- a/Services/ColorSchemeService.qml +++ b/Services/ColorSchemeService.qml @@ -5,6 +5,7 @@ import Qt.labs.folderlistmodel import Quickshell import Quickshell.Io import qs.Commons +import qs.Services Singleton { id: root @@ -37,7 +38,7 @@ Singleton { function changedWallpaper() { if (Settings.data.colorSchemes.useWallpaperColors) { Logger.log("ColorScheme", "Starting color generation from wallpaper") - generateColorsProcess.running = true + MatugenService.generateFromWallpaper() // Invalidate potential predefined scheme Settings.data.colorSchemes.predefinedScheme = "" } @@ -137,35 +138,5 @@ Singleton { colorsWriter.writeAdapter() } - Process { - id: generateColorsProcess - command: { - // Choose config based on external theming toggles - var cfg = Quickshell.shellDir + "/Assets/Matugen/matugen.toml" - if (!Settings.data.colorSchemes.themeApps) { - cfg = Quickshell.shellDir + "/Assets/Matugen/matugen.base.toml" - } - var cmd = ["matugen", "image", WallpaperService.currentWallpaper, "--config", cfg] - if (!Settings.data.colorSchemes.darkMode) { - cmd.push("--mode", "light") - } else { - cmd.push("--mode", "dark") - } - return cmd - } - workingDirectory: Quickshell.shellDir - running: false - stdout: StdioCollector { - onStreamFinished: { - Logger.log("ColorScheme", "Completed colors generation") - } - } - stderr: StdioCollector { - onStreamFinished: { - if (this.text !== "") { - Logger.error(this.text) - } - } - } - } + // Matugen generation moved to MatugenService } diff --git a/Services/MatugenService.qml b/Services/MatugenService.qml new file mode 100644 index 0000000..1749410 --- /dev/null +++ b/Services/MatugenService.qml @@ -0,0 +1,53 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Assets.Matugen +import qs.Services + +Singleton { + id: root + + property string dynamicConfigPath: Settings.cacheDir + "matugen.dynamic.toml" + + // Build TOML content based on settings + function buildConfigToml() { + return Matugen.buildConfigToml() + } + + // Generate colors using current wallpaper and settings + function generateFromWallpaper() { + // Ensure cache dir exists + Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]) + + var content = buildConfigToml() + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var wp = WallpaperService.currentWallpaper.replace(/'/g, "'\\''") + var pathEsc = dynamicConfigPath.replace(/'/g, "'\\''") + var extraRepo = (Quickshell.shellDir + "/Assets/Matugen/extra").replace(/'/g, "'\\''") + var extraUser = (Settings.configDir + "matugen.d").replace(/'/g, "'\\''") + var script = "cat > '" + pathEsc + "' << 'EOF'\n" + content + "EOF\n" + "for d in '" + extraRepo + "' '" + extraUser + + "'; do\n" + " if [ -d \"$d\" ]; then\n" + + " for f in \"$d\"/*.toml; do\n" + " [ -f \"$f\" ] && { echo; echo \"# extra: $f\"; cat \"$f\"; } >> '" + + pathEsc + "'\n" + " done\n" + " fi\n" + "done\n" + "matugen image '" + wp + "' --config '" + pathEsc + "' --mode " + mode + generateProcess.command = ["bash", "-lc", script] + generateProcess.running = true + } + + Process { + id: generateProcess + workingDirectory: Quickshell.shellDir + running: false + stdout: StdioCollector { + onStreamFinished: Logger.log("Matugen", "Completed colors generation") + } + stderr: StdioCollector { + onStreamFinished: if (this.text !== "") + Logger.error(this.text) + } + } + + // No separate writer; the write happens inline via bash heredoc +}