1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -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 // Theme settings
property string currentThemeName: "blue" property string currentThemeName: "blue"
property string customThemeFile: ""
property real topBarTransparency: 0.75 property real topBarTransparency: 0.75
property real topBarWidgetTransparency: 0.85 property real topBarWidgetTransparency: 0.85
property real popupTransparency: 0.92 property real popupTransparency: 0.92
@@ -65,6 +66,7 @@ Singleton {
property string systemDefaultIconTheme: "" property string systemDefaultIconTheme: ""
property bool qt5ctAvailable: false property bool qt5ctAvailable: false
property bool qt6ctAvailable: false property bool qt6ctAvailable: false
property bool gtkAvailable: false
property bool useOSLogo: false property bool useOSLogo: false
property string osLogoColorOverride: "" property string osLogoColorOverride: ""
property real osLogoBrightness: 0.5 property real osLogoBrightness: 0.5
@@ -126,6 +128,7 @@ Singleton {
} else { } else {
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "blue" currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "blue"
} }
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : ""
topBarTransparency = settings.topBarTransparency topBarTransparency = settings.topBarTransparency
!== undefined ? (settings.topBarTransparency !== undefined ? (settings.topBarTransparency
> 1 ? settings.topBarTransparency > 1 ? settings.topBarTransparency
@@ -288,6 +291,7 @@ Singleton {
function saveSettings() { function saveSettings() {
settingsFile.setText(JSON.stringify({ settingsFile.setText(JSON.stringify({
"currentThemeName": currentThemeName, "currentThemeName": currentThemeName,
"customThemeFile": customThemeFile,
"topBarTransparency": topBarTransparency, "topBarTransparency": topBarTransparency,
"topBarWidgetTransparency": topBarWidgetTransparency, "topBarWidgetTransparency": topBarWidgetTransparency,
"popupTransparency": popupTransparency, "popupTransparency": popupTransparency,
@@ -459,6 +463,11 @@ Singleton {
saveSettings() saveSettings()
} }
function setCustomThemeFile(filePath) {
customThemeFile = filePath
saveSettings()
}
function setTopBarTransparency(transparency) { function setTopBarTransparency(transparency) {
topBarTransparency = transparency topBarTransparency = transparency
saveSettings() saveSettings()
@@ -808,11 +817,17 @@ Singleton {
function setGtkThemingEnabled(enabled) { function setGtkThemingEnabled(enabled) {
gtkThemingEnabled = enabled gtkThemingEnabled = enabled
saveSettings() saveSettings()
if (enabled && typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme()
}
} }
function setQtThemingEnabled(enabled) { function setQtThemingEnabled(enabled) {
qtThemingEnabled = enabled qtThemingEnabled = enabled
saveSettings() saveSettings()
if (enabled && typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme()
}
} }
function setShowDock(enabled) { function setShowDock(enabled) {
@@ -967,7 +982,7 @@ Singleton {
Process { Process {
id: qtToolsDetectionProcess 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 running: false
stdout: StdioCollector { stdout: StdioCollector {
@@ -980,6 +995,8 @@ Singleton {
qt5ctAvailable = line.split(':')[1] === 'true' qt5ctAvailable = line.split(':')[1] === 'true'
else if (line.startsWith('qt6ct:')) else if (line.startsWith('qt6ct:'))
qt6ctAvailable = line.split(':')[1] === 'true' qt6ctAvailable = line.split(':')[1] === 'true'
else if (line.startsWith('gtk:'))
gtkAvailable = line.split(':')[1] === 'true'
} }
} }
} }

View File

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

View File

@@ -19,15 +19,17 @@ DankModal {
StandardPaths.HomeLocation) StandardPaths.HomeLocation)
property string currentPath: "" property string currentPath: ""
property var fileExtensions: ["*.*"] property var fileExtensions: ["*.*"]
property alias filterExtensions: fileBrowserModal.fileExtensions
property string browserTitle: "Select File" property string browserTitle: "Select File"
property string browserIcon: "folder_open" property string browserIcon: "folder_open"
property string browserType: "generic" // "wallpaper" or "profile" for last path memory property string browserType: "generic" // "wallpaper" or "profile" for last path memory
property bool showHiddenFiles: false
FolderListModel { FolderListModel {
id: folderModel id: folderModel
showDirsFirst: true showDirsFirst: true
showDotAndDotDot: false showDotAndDotDot: false
showHidden: false showHidden: fileBrowserModal.showHiddenFiles
nameFilters: fileExtensions nameFilters: fileExtensions
showFiles: true showFiles: true
showDirs: true showDirs: true

View File

@@ -1,6 +1,8 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell.Io
import qs.Common import qs.Common
import qs.Modals
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -179,7 +181,6 @@ Item {
Row { Row {
spacing: Theme.spacingM spacing: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
Repeater { Repeater {
model: Theme.availableThemeNames.slice(0, 5) model: Theme.availableThemeNames.slice(0, 5)
@@ -248,7 +249,6 @@ Item {
Row { Row {
spacing: Theme.spacingM spacing: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
Repeater { Repeater {
model: Theme.availableThemeNames.slice(5, 10) model: Theme.availableThemeNames.slice(5, 10)
@@ -320,41 +320,44 @@ Item {
height: Theme.spacingM height: Theme.spacingM
} }
Rectangle { Row {
width: 120
height: 40
radius: 20
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
color: { spacing: Theme.spacingL
if (ToastService.wallpaperErrorStatus === "error"
|| ToastService.wallpaperErrorStatus === "matugen_missing")
return Qt.rgba(Theme.error.r,
Theme.error.g,
Theme.error.b, 0.12)
else
return Qt.rgba(Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.3)
}
border.color: {
if (ToastService.wallpaperErrorStatus === "error"
|| ToastService.wallpaperErrorStatus === "matugen_missing")
return Qt.rgba(Theme.error.r,
Theme.error.g,
Theme.error.b, 0.5)
else if (Theme.isDynamicTheme)
return Theme.primary
else
return Theme.outline
}
border.width: Theme.isDynamicTheme ? 2 : 1
scale: Theme.isDynamicTheme ? 1.1 : (autoMouseArea.containsMouse ? 1.02 : 1)
Row { Rectangle {
anchors.centerIn: parent width: 120
spacing: Theme.spacingS height: 40
radius: 20
color: {
if (ToastService.wallpaperErrorStatus === "error"
|| ToastService.wallpaperErrorStatus === "matugen_missing")
return Qt.rgba(Theme.error.r,
Theme.error.g,
Theme.error.b, 0.12)
else
return Qt.rgba(Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.3)
}
border.color: {
if (ToastService.wallpaperErrorStatus === "error"
|| ToastService.wallpaperErrorStatus === "matugen_missing")
return Qt.rgba(Theme.error.r,
Theme.error.g,
Theme.error.b, 0.5)
else if (Theme.currentThemeName === "dynamic")
return Theme.primary
else
return Theme.outline
}
border.width: (Theme.currentThemeName === "dynamic") ? 2 : 1
scale: (Theme.currentThemeName === "dynamic") ? 1.1 : (autoMouseArea.containsMouse ? 1.02 : 1)
DankIcon { Row {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: { name: {
if (ToastService.wallpaperErrorStatus === "error" if (ToastService.wallpaperErrorStatus === "error"
|| ToastService.wallpaperErrorStatus || ToastService.wallpaperErrorStatus
@@ -474,6 +477,86 @@ Item {
} }
} }
} }
Rectangle {
width: 120
height: 40
radius: 20
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: (Theme.currentThemeName === "custom") ? Theme.primary : Theme.outline
border.width: (Theme.currentThemeName === "custom") ? 2 : 1
scale: (Theme.currentThemeName === "custom") ? 1.1 : (customMouseArea.containsMouse ? 1.02 : 1)
Row {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "folder_open"
size: 16
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Custom"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: customMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
fileBrowserModal.open()
}
}
Rectangle {
width: customTooltipText.contentWidth + Theme.spacingM * 2
height: customTooltipText.contentHeight + Theme.spacingS * 2
color: Theme.surfaceContainer
border.color: Theme.outline
border.width: 1
radius: Theme.cornerRadius
anchors.bottom: parent.top
anchors.bottomMargin: Theme.spacingS
anchors.horizontalCenter: parent.horizontalCenter
visible: customMouseArea.containsMouse && (Theme.currentThemeName !== "custom")
StyledText {
id: customTooltipText
text: "Load custom theme from JSON file"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.centerIn: parent
wrapMode: Text.WordWrap
width: Math.min(implicitWidth, 250)
horizontalAlignment: Text.AlignHCenter
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on border.width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
}
} // Close Row
} }
} }
} }
@@ -625,7 +708,7 @@ Item {
StyledText { StyledText {
id: warningText id: warningText
text: "Changing these settings will manipulate GTK and Qt configurations on the system" text: "Changing these settings will manipulate GTK and Qt configurations on the system, requires \"Auto\" theme"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.warning color: Theme.warning
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
@@ -732,9 +815,9 @@ Item {
DankToggle { DankToggle {
width: parent.width width: parent.width
text: "Theme GTK Applications" text: "Theme GTK Applications"
description: Theme.gtkThemingEnabled ? "File managers, text editors, and system dialogs will match your theme" : "GTK theming not available (install gsettings)" description: SettingsData.gtkAvailable ? "File managers, text editors, and system dialogs will match your theme" : "GTK theming not available (install gsettings)"
enabled: Theme.gtkThemingEnabled enabled: SettingsData.gtkAvailable
checked: Theme.gtkThemingEnabled checked: SettingsData.gtkAvailable
&& SettingsData.gtkThemingEnabled && SettingsData.gtkThemingEnabled
onToggled: function (checked) { onToggled: function (checked) {
SettingsData.setGtkThemingEnabled(checked) SettingsData.setGtkThemingEnabled(checked)
@@ -744,9 +827,9 @@ Item {
DankToggle { DankToggle {
width: parent.width width: parent.width
text: "Theme Qt Applications" text: "Theme Qt Applications"
description: Theme.qtThemingEnabled ? "Qt applications will match your theme colors" : "Qt theming not available (install qt5ct or qt6ct)" description: (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) ? "Qt applications will match your theme colors" : "Qt theming not available (install qt5ct or qt6ct)"
enabled: Theme.qtThemingEnabled enabled: (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable)
checked: Theme.qtThemingEnabled checked: (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable)
&& SettingsData.qtThemingEnabled && SettingsData.qtThemingEnabled
onToggled: function (checked) { onToggled: function (checked) {
SettingsData.setQtThemingEnabled(checked) SettingsData.setQtThemingEnabled(checked)
@@ -756,4 +839,24 @@ Item {
} }
} }
} }
FileBrowserModal {
id: fileBrowserModal
browserTitle: "Select Custom Theme"
filterExtensions: ["*.json"]
showHiddenFiles: true
function selectCustomTheme() {
shouldBeVisible = true
}
onFileSelected: function(filePath) {
// Save the custom theme file path and switch to custom theme
if (filePath.endsWith(".json")) {
SettingsData.setCustomThemeFile(filePath)
Theme.switchTheme("custom")
close()
}
}
}
} }

View File

@@ -66,9 +66,7 @@ PanelWindow {
} }
} }
width: shouldBeVisible ? width: shouldBeVisible ? (ToastService.hasDetails ? 380 : 350) : frozenWidth
(ToastService.hasDetails ? 380 : messageText.implicitWidth + Theme.iconSize + Theme.spacingM * 3 + Theme.spacingL * 2) :
frozenWidth
height: toastContent.height + Theme.spacingL * 2 height: toastContent.height + Theme.spacingL * 2
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
y: Theme.barHeight - 4 + SettingsData.topBarSpacing + 2 y: Theme.barHeight - 4 + SettingsData.topBarSpacing + 2
@@ -132,10 +130,14 @@ PanelWindow {
anchors.left: statusIcon.right anchors.left: statusIcon.right
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: ToastService.hasDetails ? expandButton.left : closeButton.left
anchors.rightMargin: Theme.spacingM
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
elide: Text.ElideRight
} }
DankActionButton { DankActionButton {
id: expandButton
iconName: toast.expanded ? "expand_less" : "expand_more" iconName: toast.expanded ? "expand_less" : "expand_more"
iconSize: Theme.iconSize iconSize: Theme.iconSize
iconColor: Theme.background iconColor: Theme.background

127
docs/CUSTOM_THEMES.md Normal file
View File

@@ -0,0 +1,127 @@
# Custom Themes
This guide covers creating custom themes for DankMaterialShell. You can define your own color schemes by creating theme files that the shell can load.
## Theme Structure
Themes are defined using the same structure as the built-in themes. Each theme must specify a complete set of Material Design 3 colors that work together harmoniously.
### Required Core Colors
These are the essential colors that define your theme's appearance:
```json
{
"dark": {
"name": "Cyberpunk Electric Dark",
"primary": "#00FFCC",
"primaryText": "#000000",
"primaryContainer": "#00CC99",
"secondary": "#FF4DFF",
"surface": "#0F0F0F",
"surfaceText": "#E0FFE0",
"surfaceVariant": "#1F2F1F",
"surfaceVariantText": "#CCFFCC",
"surfaceTint": "#00FFCC",
"background": "#000000",
"backgroundText": "#F0FFF0",
"outline": "#80FF80",
"surfaceContainer": "#1A2B1A",
"surfaceContainerHigh": "#264026",
"error": "#FF0066",
"warning": "#CCFF00",
"info": "#00FFCC"
},
"light": {
"name": "Cyberpunk Electric Light",
"primary": "#00B899",
"primaryText": "#FFFFFF",
"primaryContainer": "#66FFDD",
"secondary": "#CC00CC",
"surface": "#F0FFF0",
"surfaceText": "#1F2F1F",
"surfaceVariant": "#E6FFE6",
"surfaceVariantText": "#2D4D2D",
"surfaceTint": "#00B899",
"background": "#FFFFFF",
"backgroundText": "#000000",
"outline": "#4DCC4D",
"surfaceContainer": "#F5FFF5",
"surfaceContainerHigh": "#EBFFEB",
"error": "#B3004D",
"warning": "#99CC00",
"info": "#00B899"
}
}
```
You can the colors at the top level if you do not want "dark" and "light" variants.
## Example Themes
There are example themes you can start from:
- [Cyberpunk Electric](theme_cyberpunk_electric.json) - Neon green and magenta cyberpunk aesthetic
- [Hotline Miami](theme_hotline_miami.json) - Retro 80s inspired hot pink and blue
- [Miami Vice](theme_miami_vice.json) - Classic teal and pink vice aesthetic
- [Synthwave Electric](theme_synthwave_electric.json) - Electric purple and cyan synthwave vibes
### Color Definitions
**Primary Colors**
- `primary` - Main accent color used for buttons, highlights, and active states
- `primaryText` - Text color that contrasts well with primary background
- `primaryContainer` - Darker/lighter variant of primary for containers
**Secondary Colors**
- `secondary` - Supporting accent color for variety and hierarchy
- `surfaceTint` - Tint color applied to surfaces, usually derived from primary
**Surface Colors**
- `surface` - Default surface color for cards, panels, etc.
- `surfaceText` - Primary text color on surface backgrounds
- `surfaceVariant` - Alternate surface color for subtle differentiation
- `surfaceVariantText` - Text color for surfaceVariant backgrounds
- `surfaceContainer` - Container surface color, slightly different from surface
- `surfaceContainerHigh` - Elevated container color for layered interfaces
**Background Colors**
- `background` - Main background color for the entire interface
- `backgroundText` - Text color for background areas
**Outline Colors**
- `outline` - Border and divider color for subtle boundaries
## Optional Properties
While the core colors above are required, you can also customize these optional properties:
### Semantic Colors
```json
{
"error": "#f44336",
"warning": "#ff9800",
"info": "#2196f3"
}
```
- `error` - Used for error states, delete buttons, and critical warnings
- `warning` - Used for warning states and caution indicators
- `info` - Used for informational states and neutral indicators
## Setting Custom Theme
In settings -> Theme & Colors you can choose "Custom" to choose a path to your theme.
You can also edit `~/.config/DankMaterialShell/settings.json` manually
```json
{
"currentThemeName": "custom",
"customThemeFile": "/path/to/mytheme.json"
}
```
### Reactivity
Editing the custom theme file will auto-update the shell if it's the current theme.

View File

@@ -0,0 +1,42 @@
{
"dark": {
"name": "Cyberpunk Electric Dark",
"primary": "#00FFCC",
"primaryText": "#000000",
"primaryContainer": "#00CC99",
"secondary": "#FF4DFF",
"surface": "#0F0F0F",
"surfaceText": "#E0FFE0",
"surfaceVariant": "#1F2F1F",
"surfaceVariantText": "#CCFFCC",
"surfaceTint": "#00FFCC",
"background": "#000000",
"backgroundText": "#F0FFF0",
"outline": "#80FF80",
"surfaceContainer": "#1A2B1A",
"surfaceContainerHigh": "#264026",
"error": "#FF0066",
"warning": "#CCFF00",
"info": "#00FFCC"
},
"light": {
"name": "Cyberpunk Electric Light",
"primary": "#00B899",
"primaryText": "#FFFFFF",
"primaryContainer": "#66FFDD",
"secondary": "#CC00CC",
"surface": "#F0FFF0",
"surfaceText": "#1F2F1F",
"surfaceVariant": "#E6FFE6",
"surfaceVariantText": "#2D4D2D",
"surfaceTint": "#00B899",
"background": "#FFFFFF",
"backgroundText": "#000000",
"outline": "#4DCC4D",
"surfaceContainer": "#F5FFF5",
"surfaceContainerHigh": "#EBFFEB",
"error": "#B3004D",
"warning": "#99CC00",
"info": "#00B899"
}
}

View File

@@ -0,0 +1,42 @@
{
"dark": {
"name": "Hotline Miami Dark",
"primary": "#FF0080",
"primaryText": "#FFFFFF",
"primaryContainer": "#CC0066",
"secondary": "#00FF80",
"surface": "#0D0D0D",
"surfaceText": "#F0F0F0",
"surfaceVariant": "#1A0F1A",
"surfaceVariantText": "#E0E0E0",
"surfaceTint": "#FF0080",
"background": "#000000",
"backgroundText": "#FFFFFF",
"outline": "#8000FF",
"surfaceContainer": "#1A0D1A",
"surfaceContainerHigh": "#260F26",
"error": "#FF4080",
"warning": "#FFFF00",
"info": "#00FF80"
},
"light": {
"name": "Hotline Miami Light",
"primary": "#CC0066",
"primaryText": "#FFFFFF",
"primaryContainer": "#FF80B3",
"secondary": "#00CC66",
"surface": "#FFF0FF",
"surfaceText": "#1A0F1A",
"surfaceVariant": "#F0E6F0",
"surfaceVariantText": "#2D1A2D",
"surfaceTint": "#CC0066",
"background": "#FFFFFF",
"backgroundText": "#0D0D0D",
"outline": "#6600CC",
"surfaceContainer": "#F5F0F5",
"surfaceContainerHigh": "#EBE0EB",
"error": "#B30040",
"warning": "#B3B300",
"info": "#00B359"
}
}

View File

@@ -0,0 +1,42 @@
{
"dark": {
"name": "Miami Vice Dark",
"primary": "#00FFFF",
"primaryText": "#000000",
"primaryContainer": "#00CCCC",
"secondary": "#FF1493",
"surface": "#0A0A0F",
"surfaceText": "#E0E0FF",
"surfaceVariant": "#1A1A2E",
"surfaceVariantText": "#C0C0FF",
"surfaceTint": "#00FFFF",
"background": "#000008",
"backgroundText": "#F0F0FF",
"outline": "#4040FF",
"surfaceContainer": "#131325",
"surfaceContainerHigh": "#1F1F40",
"error": "#FF0080",
"warning": "#FFFF00",
"info": "#00FFFF"
},
"light": {
"name": "Miami Vice Light",
"primary": "#0099CC",
"primaryText": "#FFFFFF",
"primaryContainer": "#00CCFF",
"secondary": "#CC0066",
"surface": "#F8F8FF",
"surfaceText": "#1A1A2E",
"surfaceVariant": "#E8E8FF",
"surfaceVariantText": "#2A2A4E",
"surfaceTint": "#0099CC",
"background": "#FFFFFF",
"backgroundText": "#0A0A2E",
"outline": "#6666CC",
"surfaceContainer": "#F0F0FF",
"surfaceContainerHigh": "#E0E0FF",
"error": "#CC0055",
"warning": "#CC9900",
"info": "#0099CC"
}
}

View File

@@ -0,0 +1,42 @@
{
"dark": {
"name": "Synthwave Electric Dark",
"primary": "#FF6600",
"primaryText": "#000000",
"primaryContainer": "#CC5200",
"secondary": "#0080FF",
"surface": "#0A0A15",
"surfaceText": "#E6F0FF",
"surfaceVariant": "#1A1A33",
"surfaceVariantText": "#CCE0FF",
"surfaceTint": "#FF6600",
"background": "#000008",
"backgroundText": "#F0F8FF",
"outline": "#4D80FF",
"surfaceContainer": "#151529",
"surfaceContainerHigh": "#212147",
"error": "#FF3366",
"warning": "#FFCC00",
"info": "#0080FF"
},
"light": {
"name": "Synthwave Electric Light",
"primary": "#CC5200",
"primaryText": "#FFFFFF",
"primaryContainer": "#FF9966",
"secondary": "#0066CC",
"surface": "#FFF8F0",
"surfaceText": "#1A1A33",
"surfaceVariant": "#F0F0FF",
"surfaceVariantText": "#333366",
"surfaceTint": "#CC5200",
"background": "#FFFFFF",
"backgroundText": "#000008",
"outline": "#3366CC",
"surfaceContainer": "#F5F5FF",
"surfaceContainerHigh": "#EBEBFF",
"error": "#CC1A40",
"warning": "#CC9900",
"info": "#0066CC"
}
}