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:
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
148
Common/Theme.qml
148
Common/Theme.qml
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,11 +320,14 @@ Item {
|
||||
height: Theme.spacingM
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Rectangle {
|
||||
width: 120
|
||||
height: 40
|
||||
radius: 20
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: {
|
||||
if (ToastService.wallpaperErrorStatus === "error"
|
||||
|| ToastService.wallpaperErrorStatus === "matugen_missing")
|
||||
@@ -342,13 +345,13 @@ Item {
|
||||
return Qt.rgba(Theme.error.r,
|
||||
Theme.error.g,
|
||||
Theme.error.b, 0.5)
|
||||
else if (Theme.isDynamicTheme)
|
||||
else if (Theme.currentThemeName === "dynamic")
|
||||
return Theme.primary
|
||||
else
|
||||
return Theme.outline
|
||||
}
|
||||
border.width: Theme.isDynamicTheme ? 2 : 1
|
||||
scale: Theme.isDynamicTheme ? 1.1 : (autoMouseArea.containsMouse ? 1.02 : 1)
|
||||
border.width: (Theme.currentThemeName === "dynamic") ? 2 : 1
|
||||
scale: (Theme.currentThemeName === "dynamic") ? 1.1 : (autoMouseArea.containsMouse ? 1.02 : 1)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
127
docs/CUSTOM_THEMES.md
Normal 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.
|
||||
42
docs/theme_cyberpunk_electric.json
Normal file
42
docs/theme_cyberpunk_electric.json
Normal 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"
|
||||
}
|
||||
}
|
||||
42
docs/theme_hotline_miami.json
Normal file
42
docs/theme_hotline_miami.json
Normal 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"
|
||||
}
|
||||
}
|
||||
42
docs/theme_miami_vice.json
Normal file
42
docs/theme_miami_vice.json
Normal 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"
|
||||
}
|
||||
}
|
||||
42
docs/theme_synthwave_electric.json
Normal file
42
docs/theme_synthwave_electric.json
Normal 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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user