1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -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)
}
}
}
}

View File

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

View File

@@ -1,6 +1,8 @@
import QtQuick
import QtQuick.Controls
import Quickshell.Io
import qs.Common
import qs.Modals
import qs.Services
import qs.Widgets
@@ -179,7 +181,6 @@ Item {
Row {
spacing: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
Repeater {
model: Theme.availableThemeNames.slice(0, 5)
@@ -248,7 +249,6 @@ Item {
Row {
spacing: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
Repeater {
model: Theme.availableThemeNames.slice(5, 10)
@@ -320,41 +320,44 @@ Item {
height: Theme.spacingM
}
Rectangle {
width: 120
height: 40
radius: 20
Row {
anchors.horizontalCenter: parent.horizontalCenter
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.isDynamicTheme)
return Theme.primary
else
return Theme.outline
}
border.width: Theme.isDynamicTheme ? 2 : 1
scale: Theme.isDynamicTheme ? 1.1 : (autoMouseArea.containsMouse ? 1.02 : 1)
spacing: Theme.spacingL
Row {
anchors.centerIn: parent
spacing: Theme.spacingS
Rectangle {
width: 120
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: {
if (ToastService.wallpaperErrorStatus === "error"
|| 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 {
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
color: Theme.warning
wrapMode: Text.WordWrap
@@ -732,9 +815,9 @@ Item {
DankToggle {
width: parent.width
text: "Theme GTK Applications"
description: Theme.gtkThemingEnabled ? "File managers, text editors, and system dialogs will match your theme" : "GTK theming not available (install gsettings)"
enabled: Theme.gtkThemingEnabled
checked: Theme.gtkThemingEnabled
description: SettingsData.gtkAvailable ? "File managers, text editors, and system dialogs will match your theme" : "GTK theming not available (install gsettings)"
enabled: SettingsData.gtkAvailable
checked: SettingsData.gtkAvailable
&& SettingsData.gtkThemingEnabled
onToggled: function (checked) {
SettingsData.setGtkThemingEnabled(checked)
@@ -744,9 +827,9 @@ Item {
DankToggle {
width: parent.width
text: "Theme Qt Applications"
description: Theme.qtThemingEnabled ? "Qt applications will match your theme colors" : "Qt theming not available (install qt5ct or qt6ct)"
enabled: Theme.qtThemingEnabled
checked: Theme.qtThemingEnabled
description: (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) ? "Qt applications will match your theme colors" : "Qt theming not available (install qt5ct or qt6ct)"
enabled: (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable)
checked: (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable)
&& SettingsData.qtThemingEnabled
onToggled: function (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 ?
(ToastService.hasDetails ? 380 : messageText.implicitWidth + Theme.iconSize + Theme.spacingM * 3 + Theme.spacingL * 2) :
frozenWidth
width: shouldBeVisible ? (ToastService.hasDetails ? 380 : 350) : frozenWidth
height: toastContent.height + Theme.spacingL * 2
anchors.horizontalCenter: parent.horizontalCenter
y: Theme.barHeight - 4 + SettingsData.topBarSpacing + 2
@@ -132,10 +130,14 @@ PanelWindow {
anchors.left: statusIcon.right
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
anchors.right: ToastService.hasDetails ? expandButton.left : closeButton.left
anchors.rightMargin: Theme.spacingM
wrapMode: Text.NoWrap
elide: Text.ElideRight
}
DankActionButton {
id: expandButton
iconName: toast.expanded ? "expand_less" : "expand_more"
iconSize: Theme.iconSize
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"
}
}