1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-28 15:32:50 -05:00

support for custom themes

This commit is contained in:
bbedward
2025-08-20 21:20:33 -04:00
parent 0a846fd1ee
commit 0e513185e0
10 changed files with 554 additions and 105 deletions

View File

@@ -12,6 +12,7 @@ Singleton {
// Theme settings
property string currentThemeName: "blue"
property string customThemeFile: ""
property real topBarTransparency: 0.75
property real topBarWidgetTransparency: 0.85
property real popupTransparency: 0.92
@@ -65,6 +66,7 @@ Singleton {
property string systemDefaultIconTheme: ""
property bool qt5ctAvailable: false
property bool qt6ctAvailable: false
property bool gtkAvailable: false
property bool useOSLogo: false
property string osLogoColorOverride: ""
property real osLogoBrightness: 0.5
@@ -126,6 +128,7 @@ Singleton {
} else {
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "blue"
}
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : ""
topBarTransparency = settings.topBarTransparency
!== undefined ? (settings.topBarTransparency
> 1 ? settings.topBarTransparency
@@ -288,6 +291,7 @@ Singleton {
function saveSettings() {
settingsFile.setText(JSON.stringify({
"currentThemeName": currentThemeName,
"customThemeFile": customThemeFile,
"topBarTransparency": topBarTransparency,
"topBarWidgetTransparency": topBarWidgetTransparency,
"popupTransparency": popupTransparency,
@@ -459,6 +463,11 @@ Singleton {
saveSettings()
}
function setCustomThemeFile(filePath) {
customThemeFile = filePath
saveSettings()
}
function setTopBarTransparency(transparency) {
topBarTransparency = transparency
saveSettings()
@@ -808,11 +817,17 @@ Singleton {
function setGtkThemingEnabled(enabled) {
gtkThemingEnabled = enabled
saveSettings()
if (enabled && typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme()
}
}
function setQtThemingEnabled(enabled) {
qtThemingEnabled = enabled
saveSettings()
if (enabled && typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme()
}
}
function setShowDock(enabled) {
@@ -967,7 +982,7 @@ Singleton {
Process {
id: qtToolsDetectionProcess
command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'"]
command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'; echo -n 'gtk:'; (command -v gsettings >/dev/null || command -v dconf >/dev/null) && echo 'true' || echo 'false'"]
running: false
stdout: StdioCollector {
@@ -980,6 +995,8 @@ Singleton {
qt5ctAvailable = line.split(':')[1] === 'true'
else if (line.startsWith('qt6ct:'))
qt6ctAvailable = line.split(':')[1] === 'true'
else if (line.startsWith('gtk:'))
gtkAvailable = line.split(':')[1] === 'true'
}
}
}

View File

@@ -1,24 +1,23 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.UPower
import Qt.labs.platform
import qs.Services
import "StockThemes.js" as StockThemes
Singleton {
id: root
// Theme selection
property string currentTheme: "blue"
property bool isLightMode: false
readonly property string dynamic: "dynamic"
readonly property bool isDynamicTheme: !StockThemes.isStockTheme(currentTheme)
// Dynamic color extraction properties
readonly property string homeDir: {
const url = StandardPaths.writableLocation(StandardPaths.HomeLocation).toString()
return url.startsWith("file://") ? url.substring(7) : url
@@ -31,14 +30,14 @@ Singleton {
readonly property string wallpaperPath: typeof SessionData !== "undefined" ? SessionData.wallpaperPath : ""
property bool matugenAvailable: false
property bool gtkThemingEnabled: false
property bool qtThemingEnabled: false
property bool gtkThemingEnabled: typeof SettingsData !== "undefined" ? SettingsData.gtkAvailable : false
property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false
property bool systemThemeGenerationInProgress: false
property var matugenColors: ({})
property bool extractionRequested: false
property int colorUpdateTrigger: 0
property var customThemeData: null
// Helper function to get matugen colors (unified from Colors.qml)
function getMatugenColor(path, fallback) {
colorUpdateTrigger
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
@@ -51,9 +50,10 @@ Singleton {
return cur || fallback
}
// Current theme data
readonly property var currentThemeData: {
if (isDynamicTheme) {
if (currentTheme === "custom") {
return customThemeData || StockThemes.getThemeByName("blue", isLightMode)
} else if (currentTheme === dynamic) {
return {
primary: getMatugenColor("primary", "#42a5f5"),
primaryText: getMatugenColor("on_primary", "#ffffff"),
@@ -68,14 +68,16 @@ Singleton {
backgroundText: getMatugenColor("on_background", "#e3e8ef"),
outline: getMatugenColor("outline", "#8e918f"),
surfaceContainer: getMatugenColor("surface_container", "#1e2023"),
surfaceContainerHigh: getMatugenColor("surface_container_high", "#292b2f")
surfaceContainerHigh: getMatugenColor("surface_container_high", "#292b2f"),
error: "#F2B8B5",
warning: "#FF9800",
info: "#2196F3"
}
} else {
return StockThemes.getThemeByName(currentTheme, isLightMode)
}
}
// Core color properties (unified from both Theme.qml and Colors.qml)
property color primary: currentThemeData.primary
property color primaryText: currentThemeData.primaryText
property color primaryContainer: currentThemeData.primaryContainer
@@ -91,12 +93,10 @@ Singleton {
property color surfaceContainer: currentThemeData.surfaceContainer
property color surfaceContainerHigh: currentThemeData.surfaceContainerHigh
// Additional semantic colors
property color error: "#F2B8B5"
property color warning: "#FF9800"
property color info: "#2196F3"
property color error: currentThemeData.error || "#F2B8B5"
property color warning: currentThemeData.warning || "#FF9800"
property color info: currentThemeData.info || "#2196F3"
// Interaction states
property color primaryHover: Qt.rgba(primary.r, primary.g, primary.b, 0.12)
property color primaryHoverLight: Qt.rgba(primary.r, primary.g, primary.b, 0.08)
property color primaryPressed: Qt.rgba(primary.r, primary.g, primary.b, 0.16)
@@ -125,7 +125,6 @@ Singleton {
property color shadowMedium: Qt.rgba(0, 0, 0, 0.08)
property color shadowStrong: Qt.rgba(0, 0, 0, 0.3)
// Animation and timing
property int shortDuration: 150
property int mediumDuration: 300
property int longDuration: 500
@@ -133,7 +132,6 @@ Singleton {
property int standardEasing: Easing.OutCubic
property int emphasizedEasing: Easing.OutQuart
// Layout and sizing
property real cornerRadius: typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
property real spacingXS: 4
property real spacingS: 8
@@ -149,29 +147,26 @@ Singleton {
property real iconSizeSmall: 16
property real iconSizeLarge: 32
// Transparency settings
property real panelTransparency: 0.85
property real widgetTransparency: typeof SettingsData !== "undefined" && SettingsData.topBarWidgetTransparency !== undefined ? SettingsData.topBarWidgetTransparency : 0.85
property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 0.92
// Theme switching API
function switchTheme(themeName, savePrefs = true) {
if (themeName === dynamic) {
if (StockThemes.isStockTheme(currentTheme)) {
// Switching from stock to dynamic, restore old theme
restoreSystemThemes()
}
currentTheme = dynamic
extractColors()
} else {
if (!StockThemes.isStockTheme(currentTheme)) {
// Switching from dynamic to stock
restoreSystemThemes()
} else if (themeName === "custom") {
currentTheme = "custom"
if (typeof SettingsData !== "undefined" && SettingsData.customThemeFile) {
loadCustomThemeFromFile(SettingsData.customThemeFile)
}
} else {
currentTheme = themeName
}
if (savePrefs && typeof SettingsData !== "undefined")
SettingsData.setTheme(currentTheme)
generateSystemThemesFromCurrentTheme()
}
function toggleLightMode(savePrefs = true) {
@@ -190,15 +185,33 @@ Singleton {
}
function getThemeColors(themeName) {
if (themeName === "custom" && customThemeData) {
return customThemeData
}
return StockThemes.getThemeByName(themeName, isLightMode)
}
function loadCustomTheme(themeData) {
if (themeData.dark || themeData.light) {
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
const selectedTheme = themeData[colorMode] || themeData.dark || themeData.light
customThemeData = selectedTheme
} else {
customThemeData = themeData
}
generateSystemThemesFromCurrentTheme()
}
function loadCustomThemeFromFile(filePath) {
customThemeFileView.path = filePath
}
property alias availableThemeNames: root._availableThemeNames
readonly property var _availableThemeNames: StockThemes.getAllThemeNames()
property string currentThemeName: currentTheme
// Background helper functions
function popupBackground() {
return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, popupTransparency)
}
@@ -223,7 +236,6 @@ Singleton {
return popupTransparency
}
// Utility functions
function isColorDark(c) {
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5
}
@@ -305,7 +317,6 @@ Singleton {
}
}
// Dynamic color extraction (merged from Colors.qml)
function extractColors() {
extractionRequested = true
if (matugenAvailable)
@@ -321,6 +332,10 @@ Singleton {
generateSystemThemes()
}
}
if (currentTheme === "custom" && customThemeFileView.path) {
customThemeFileView.reload()
}
}
function generateSystemThemes() {
@@ -337,19 +352,30 @@ Singleton {
systemThemeGenerator.running = true
}
function restoreSystemThemes() {
if (!shellDir) return
function generateSystemThemesFromCurrentTheme() {
if (!isDynamicTheme)
return
if (systemThemeGenerationInProgress)
return
if (!matugenAvailable || !wallpaperPath)
return
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false"
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
const gtkTheming = (typeof SettingsData !== "undefined" && SettingsData.gtkThemingEnabled) ? "true" : "false"
const qtTheming = (typeof SettingsData !== "undefined" && SettingsData.qtThemingEnabled) ? "true" : "false"
systemThemeRestoreProcess.command = [shellDir + "/generate-themes.sh", "", shellDir, configDir, "restore", isLight, iconTheme, gtkTheming, qtTheming]
systemThemeRestoreProcess.running = true
if (gtkTheming === "false" && qtTheming === "false")
return
systemThemeGenerationInProgress = true
systemThemeGenerator.command = [shellDir + "/generate-themes.sh", wallpaperPath, shellDir, configDir, "generate", isLight, iconTheme, gtkTheming, qtTheming]
systemThemeGenerator.running = true
}
// JSON extraction helper
function extractJsonFromText(text) {
if (!text) return null
@@ -400,7 +426,6 @@ Singleton {
return null
}
// Process definitions for dynamic theming
Process {
id: matugenCheck
command: ["which", "matugen"]
@@ -505,30 +530,7 @@ Singleton {
}
}
Process {
id: systemThemeRestoreProcess
running: false
stdout: StdioCollector {
id: restoreThemeStdout
}
stderr: StdioCollector {
id: restoreThemeStderr
}
onExited: exitCode => {
if (typeof ToastService !== "undefined") {
if (exitCode === 0) {
ToastService.showInfo("System themes restored to default")
} else {
ToastService.showWarning("Failed to restore system themes: " + restoreThemeStderr.text)
}
}
}
}
// Generate app configs
function generateAppConfigs() {
if (!matugenColors || !matugenColors.colors) {
return
@@ -623,4 +625,32 @@ Singleton {
if (typeof SessionData !== "undefined")
SessionData.isLightModeChanged.connect(root.onLightModeChanged)
}
FileView {
id: customThemeFileView
watchChanges: true
function parseAndLoadTheme() {
try {
var themeData = JSON.parse(customThemeFileView.text())
loadCustomTheme(themeData)
} catch (e) {
ToastService.showError("Invalid JSON format: " + e.message)
}
}
onLoaded: {
parseAndLoadTheme()
}
onFileChanged: {
customThemeFileView.reload()
}
onLoadFailed: function(error) {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to read theme file: " + error)
}
}
}
}