mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-07 05:55:37 -05:00
Persist theme preference, dynamic auto theme with matugen
This commit is contained in:
140
Common/Colors.qml
Normal file
140
Common/Colors.qml
Normal file
@@ -0,0 +1,140 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Qt.labs.platform // ← gives us StandardPaths
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
/* ──────────────── basic state ──────────────── */
|
||||
signal colorsUpdated()
|
||||
|
||||
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
|
||||
readonly property string homeDir: _homeUrl.startsWith("file://")
|
||||
? _homeUrl.substring(7)
|
||||
: _homeUrl
|
||||
readonly property string wallpaperPath: homeDir + "/quickshell/current_wallpaper"
|
||||
|
||||
property bool matugenAvailable: false
|
||||
property string matugenJson: ""
|
||||
property var matugenColors: ({})
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("Colors.qml → home =", homeDir)
|
||||
matugenCheck.running = true // kick off the chain
|
||||
}
|
||||
|
||||
/* ──────────────── availability checks ──────────────── */
|
||||
Process {
|
||||
id: matugenCheck
|
||||
command: ["which", "matugen"]
|
||||
onExited: (code) => {
|
||||
matugenAvailable = (code === 0)
|
||||
console.log("Matugen in PATH:", matugenAvailable)
|
||||
|
||||
if (!matugenAvailable) {
|
||||
console.warn("Matugen missing → dynamic theme disabled")
|
||||
Theme.rootObj.wallpaperErrorStatus = "matugen_missing"
|
||||
return
|
||||
}
|
||||
fileChecker.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fileChecker // exists & readable?
|
||||
command: ["test", "-r", wallpaperPath]
|
||||
onExited: (code) => {
|
||||
if (code === 0) {
|
||||
matugenProcess.running = true
|
||||
} else {
|
||||
console.error("code", code)
|
||||
console.error("Wallpaper not found:", wallpaperPath)
|
||||
Theme.rootObj.showWallpaperError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ──────────────── matugen invocation ──────────────── */
|
||||
Process {
|
||||
id: matugenProcess
|
||||
command: ["matugen", "-v", "image", wallpaperPath, "--json", "hex"]
|
||||
|
||||
/* ── grab stdout as a stream ── */
|
||||
stdout: StdioCollector {
|
||||
id: matugenCollector
|
||||
onStreamFinished: {
|
||||
const out = matugenCollector.text
|
||||
if (!out.length) {
|
||||
console.error("matugen produced zero bytes\nstderr:", matugenProcess.stderr)
|
||||
Theme.rootObj.showWallpaperError()
|
||||
return
|
||||
}
|
||||
try {
|
||||
root.matugenJson = out
|
||||
root.matugenColors = JSON.parse(out)
|
||||
root.colorsUpdated()
|
||||
Theme.rootObj.wallpaperErrorStatus = ""
|
||||
} catch (e) {
|
||||
console.error("JSON parse failed:", e)
|
||||
Theme.rootObj.showWallpaperError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* grab stderr too, so we can print it above */
|
||||
stderr: StdioCollector { id: matugenErr }
|
||||
}
|
||||
|
||||
/* ──────────────── public helper ──────────────── */
|
||||
function extractColors() {
|
||||
if (matugenAvailable)
|
||||
fileChecker.running = true
|
||||
else
|
||||
matugenCheck.running = true
|
||||
}
|
||||
|
||||
function getMatugenColor(path, fallback) {
|
||||
let cur = matugenColors?.colors?.dark
|
||||
for (const part of path.split(".")) {
|
||||
if (!cur || typeof cur !== "object" || !(part in cur))
|
||||
return fallback
|
||||
cur = cur[part]
|
||||
}
|
||||
return cur || fallback
|
||||
}
|
||||
|
||||
/* ──────────────── color properties (MD3) ──────────────── */
|
||||
property color primary: getMatugenColor("primary", "#42a5f5")
|
||||
property color secondary: getMatugenColor("secondary", "#8ab4f8")
|
||||
property color tertiary: getMatugenColor("tertiary", "#bb86fc")
|
||||
property color tertiaryContainer: getMatugenColor("tertiary_container", "#3700b3")
|
||||
property color error: getMatugenColor("error", "#cf6679")
|
||||
property color inversePrimary: getMatugenColor("inverse_primary", "#6200ea")
|
||||
|
||||
/* backgrounds */
|
||||
property color bg: getMatugenColor("background", "#1a1c1e")
|
||||
property color surface: getMatugenColor("surface", "#1a1c1e")
|
||||
property color surfaceContainer: getMatugenColor("surface_container", "#1e2023")
|
||||
property color surfaceContainerHigh: getMatugenColor("surface_container_high", "#292b2f")
|
||||
property color surfaceVariant: getMatugenColor("surface_variant", "#44464f")
|
||||
|
||||
/* text */
|
||||
property color surfaceText: getMatugenColor("on_background", "#e3e8ef")
|
||||
property color primaryText: getMatugenColor("on_primary", "#ffffff")
|
||||
property color surfaceVariantText: getMatugenColor("on_surface_variant", "#c4c7c5")
|
||||
|
||||
/* containers & misc */
|
||||
property color primaryContainer: getMatugenColor("primary_container", "#1976d2")
|
||||
property color surfaceTint: getMatugenColor("surface_tint", "#8ab4f8")
|
||||
property color outline: getMatugenColor("outline", "#8e918f")
|
||||
|
||||
/* legacy aliases */
|
||||
property color accentHi: primary
|
||||
property color accentLo: secondary
|
||||
|
||||
function isColorDark(c) {
|
||||
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5
|
||||
}
|
||||
}
|
||||
49
Common/Prefs.qml
Normal file
49
Common/Prefs.qml
Normal file
@@ -0,0 +1,49 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Qt.labs.settings
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property alias themeIndex: settings.themeIndex
|
||||
property alias themeIsDynamic: settings.themeIsDynamic
|
||||
|
||||
Settings {
|
||||
id: settings
|
||||
category: "theme"
|
||||
|
||||
// 0-9 = built-in static themes, 10 = Auto (dynamic)
|
||||
property int themeIndex: 0
|
||||
property bool themeIsDynamic: false
|
||||
}
|
||||
|
||||
// Apply theme when component is ready
|
||||
Component.onCompleted: {
|
||||
console.log("Prefs Component.onCompleted - themeIndex:", settings.themeIndex, "isDynamic:", settings.themeIsDynamic)
|
||||
Qt.callLater(applyStoredTheme)
|
||||
}
|
||||
|
||||
function applyStoredTheme() {
|
||||
console.log("Applying stored theme:", settings.themeIndex, settings.themeIsDynamic)
|
||||
|
||||
// Make sure Theme is available
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.switchTheme(settings.themeIndex, settings.themeIsDynamic, false) // Don't save during startup
|
||||
} else {
|
||||
// Try again in a moment
|
||||
Qt.callLater(() => {
|
||||
if (typeof Theme !== "undefined") {
|
||||
Theme.switchTheme(settings.themeIndex, settings.themeIsDynamic, false) // Don't save during startup
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function setTheme(index, isDynamic) {
|
||||
console.log("Prefs setTheme called - themeIndex:", index, "isDynamic:", isDynamic)
|
||||
settings.themeIndex = index
|
||||
settings.themeIsDynamic = isDynamic
|
||||
console.log("Prefs saved - themeIndex:", settings.themeIndex, "isDynamic:", settings.themeIsDynamic)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,45 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
// Reference to the main shell root for calling functions
|
||||
property var rootObj: null
|
||||
|
||||
// Apply saved theme on startup
|
||||
Component.onCompleted: {
|
||||
console.log("Theme Component.onCompleted")
|
||||
|
||||
// Connect to Colors signal
|
||||
if (typeof Colors !== "undefined") {
|
||||
Colors.colorsUpdated.connect(root.onColorsUpdated)
|
||||
}
|
||||
|
||||
Qt.callLater(() => {
|
||||
if (typeof Prefs !== "undefined") {
|
||||
console.log("Theme applying saved preferences:", Prefs.themeIndex, Prefs.themeIsDynamic)
|
||||
switchTheme(Prefs.themeIndex, Prefs.themeIsDynamic, false) // Don't save during startup
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Handle successful color extraction
|
||||
function onColorsUpdated() {
|
||||
console.log("Colors updated successfully - switching to dynamic theme")
|
||||
currentThemeIndex = 10
|
||||
isDynamicTheme = true
|
||||
console.log("Dynamic theme activated. Theme.primary should now be:", primary)
|
||||
|
||||
// Save preference after successful switch
|
||||
if (typeof Prefs !== "undefined") {
|
||||
Prefs.setTheme(currentThemeIndex, isDynamicTheme)
|
||||
}
|
||||
}
|
||||
|
||||
// Theme definitions with complete Material 3 expressive color palettes
|
||||
property var themes: [
|
||||
{
|
||||
@@ -179,33 +214,50 @@ QtObject {
|
||||
}
|
||||
]
|
||||
|
||||
// Current theme index
|
||||
// Current theme index (10 = Auto/Dynamic)
|
||||
property int currentThemeIndex: 0
|
||||
property bool isDynamicTheme: false
|
||||
|
||||
// Function to switch themes
|
||||
function switchTheme(themeIndex) {
|
||||
if (themeIndex >= 0 && themeIndex < themes.length) {
|
||||
function switchTheme(themeIndex, isDynamic = false, savePrefs = true) {
|
||||
console.log("Theme.switchTheme called:", themeIndex, isDynamic, "savePrefs:", savePrefs)
|
||||
|
||||
if (isDynamic && themeIndex === 10) {
|
||||
console.log("Attempting to switch to dynamic theme - checking colors first")
|
||||
|
||||
// Don't change theme yet - wait for color extraction to succeed
|
||||
if (typeof Colors !== "undefined") {
|
||||
console.log("Calling Colors.extractColors()")
|
||||
Colors.extractColors()
|
||||
} else {
|
||||
console.error("Colors singleton not available")
|
||||
}
|
||||
} else if (themeIndex >= 0 && themeIndex < themes.length) {
|
||||
currentThemeIndex = themeIndex
|
||||
// Simple persistence - store in a property
|
||||
// In a real application, you might use Qt.labs.settings or another persistence mechanism
|
||||
isDynamicTheme = false
|
||||
}
|
||||
|
||||
// Save preference (unless this is a startup restoration)
|
||||
if (savePrefs && typeof Prefs !== "undefined") {
|
||||
Prefs.setTheme(currentThemeIndex, isDynamicTheme)
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic color properties that change based on current theme
|
||||
property color primary: themes[currentThemeIndex].primary
|
||||
property color primaryText: themes[currentThemeIndex].primaryText
|
||||
property color primaryContainer: themes[currentThemeIndex].primaryContainer
|
||||
property color secondary: themes[currentThemeIndex].secondary
|
||||
property color surface: themes[currentThemeIndex].surface
|
||||
property color surfaceText: themes[currentThemeIndex].surfaceText
|
||||
property color surfaceVariant: themes[currentThemeIndex].surfaceVariant
|
||||
property color surfaceVariantText: themes[currentThemeIndex].surfaceVariantText
|
||||
property color surfaceTint: themes[currentThemeIndex].surfaceTint
|
||||
property color background: themes[currentThemeIndex].background
|
||||
property color backgroundText: themes[currentThemeIndex].backgroundText
|
||||
property color outline: themes[currentThemeIndex].outline
|
||||
property color surfaceContainer: themes[currentThemeIndex].surfaceContainer
|
||||
property color surfaceContainerHigh: themes[currentThemeIndex].surfaceContainerHigh
|
||||
property color primary: isDynamicTheme ? Colors.accentHi : (currentThemeIndex < themes.length ? themes[currentThemeIndex].primary : themes[0].primary)
|
||||
property color primaryText: isDynamicTheme ? Colors.primaryText : (currentThemeIndex < themes.length ? themes[currentThemeIndex].primaryText : themes[0].primaryText)
|
||||
property color primaryContainer: isDynamicTheme ? Colors.primaryContainer : (currentThemeIndex < themes.length ? themes[currentThemeIndex].primaryContainer : themes[0].primaryContainer)
|
||||
property color secondary: isDynamicTheme ? Colors.accentLo : (currentThemeIndex < themes.length ? themes[currentThemeIndex].secondary : themes[0].secondary)
|
||||
property color surface: isDynamicTheme ? Colors.surface : (currentThemeIndex < themes.length ? themes[currentThemeIndex].surface : themes[0].surface)
|
||||
property color surfaceText: isDynamicTheme ? Colors.surfaceText : (currentThemeIndex < themes.length ? themes[currentThemeIndex].surfaceText : themes[0].surfaceText)
|
||||
property color surfaceVariant: isDynamicTheme ? Colors.surfaceVariant : (currentThemeIndex < themes.length ? themes[currentThemeIndex].surfaceVariant : themes[0].surfaceVariant)
|
||||
property color surfaceVariantText: isDynamicTheme ? Colors.surfaceVariantText : (currentThemeIndex < themes.length ? themes[currentThemeIndex].surfaceVariantText : themes[0].surfaceVariantText)
|
||||
property color surfaceTint: isDynamicTheme ? Colors.surfaceTint : (currentThemeIndex < themes.length ? themes[currentThemeIndex].surfaceTint : themes[0].surfaceTint)
|
||||
property color background: isDynamicTheme ? Colors.background : (currentThemeIndex < themes.length ? themes[currentThemeIndex].background : themes[0].background)
|
||||
property color backgroundText: isDynamicTheme ? Colors.backgroundText : (currentThemeIndex < themes.length ? themes[currentThemeIndex].backgroundText : themes[0].backgroundText)
|
||||
property color outline: isDynamicTheme ? Colors.outline : (currentThemeIndex < themes.length ? themes[currentThemeIndex].outline : themes[0].outline)
|
||||
property color surfaceContainer: isDynamicTheme ? Colors.surfaceContainer : (currentThemeIndex < themes.length ? themes[currentThemeIndex].surfaceContainer : themes[0].surfaceContainer)
|
||||
property color surfaceContainerHigh: isDynamicTheme ? Colors.surfaceContainerHigh : (currentThemeIndex < themes.length ? themes[currentThemeIndex].surfaceContainerHigh : themes[0].surfaceContainerHigh)
|
||||
|
||||
// Static colors that don't change with themes
|
||||
property color archBlue: "#1793D1"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
module Common
|
||||
|
||||
singleton Theme 1.0 Theme.qml
|
||||
singleton Colors 1.0 Colors.qml
|
||||
singleton Prefs 1.0 Prefs.qml
|
||||
Utilities 1.0 Utilities.js
|
||||
Reference in New Issue
Block a user