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

Implement user icon theme selection in settings

This commit is contained in:
purian23
2025-07-21 00:34:45 -04:00
parent 79a600cf0d
commit 6f54e592ba
6 changed files with 480 additions and 66 deletions

View File

@@ -7,8 +7,6 @@ import Quickshell
import Quickshell.Io
Singleton {
// "auto", "wifi", "ethernet"
// Alphabetical tiebreaker
id: root
@@ -18,26 +16,24 @@ Singleton {
property real topBarTransparency: 0.75
property real popupTransparency: 0.92
property var recentlyUsedApps: []
// New global preferences
property bool use24HourClock: true
property bool useFahrenheit: false
property bool nightModeEnabled: false
property string profileImage: ""
property string weatherLocationOverride: "New York, NY"
// Widget visibility preferences for TopBar
property bool showFocusedWindow: true
property bool showWeather: true
property bool showMusic: true
property bool showClipboard: true
property bool showSystemResources: true
property bool showSystemTray: true
// WorkspaceSwitcher index toggle
property bool showWorkspaceIndex: true
// View mode preferences for launchers
property string appLauncherViewMode: "list"
property string spotlightLauncherViewMode: "list"
// Network preference
property string networkPreference: "auto"
property string iconTheme: "System Default"
property var availableIconThemes: ["System Default"]
property string systemDefaultIconTheme: "Adwaita"
function loadSettings() {
parseSettings(settingsFile.text());
@@ -68,15 +64,16 @@ Singleton {
appLauncherViewMode = settings.appLauncherViewMode !== undefined ? settings.appLauncherViewMode : "list";
spotlightLauncherViewMode = settings.spotlightLauncherViewMode !== undefined ? settings.spotlightLauncherViewMode : "list";
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto";
console.log("Loaded settings - themeIndex:", themeIndex, "isDynamic:", themeIsDynamic, "lightMode:", isLightMode, "transparency:", topBarTransparency, "recentApps:", recentlyUsedApps.length);
applyStoredTheme();
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default";
applyStoredTheme();
detectAvailableIconThemes();
updateGtkIconTheme(iconTheme);
applyStoredIconTheme();
} else {
console.log("Settings file is empty - applying default theme");
applyStoredTheme();
applyStoredTheme();
}
} catch (e) {
console.log("Could not parse settings, using defaults:", e);
applyStoredTheme();
applyStoredTheme();
}
}
@@ -102,19 +99,17 @@ Singleton {
"showWorkspaceIndex": showWorkspaceIndex,
"appLauncherViewMode": appLauncherViewMode,
"spotlightLauncherViewMode": spotlightLauncherViewMode,
"networkPreference": networkPreference
"networkPreference": networkPreference,
"iconTheme": iconTheme
}, null, 2));
console.log("Saving settings - themeIndex:", themeIndex, "isDynamic:", themeIsDynamic, "lightMode:", isLightMode, "transparency:", topBarTransparency, "recentApps:", recentlyUsedApps.length);
}
function setShowWorkspaceIndex(enabled) {
console.log("Prefs setShowWorkspaceIndex called - showWorkspaceIndex:", enabled);
showWorkspaceIndex = enabled;
saveSettings();
}
function applyStoredTheme() {
console.log("Applying stored theme:", themeIndex, themeIsDynamic, "lightMode:", isLightMode);
if (typeof Theme !== "undefined") {
Theme.isLightMode = isLightMode;
Theme.switchTheme(themeIndex, themeIsDynamic, false);
@@ -129,26 +124,22 @@ Singleton {
}
function setTheme(index, isDynamic) {
console.log("Prefs setTheme called - themeIndex:", index, "isDynamic:", isDynamic);
themeIndex = index;
themeIsDynamic = isDynamic;
saveSettings();
}
function setLightMode(lightMode) {
console.log("Prefs setLightMode called - isLightMode:", lightMode);
isLightMode = lightMode;
saveSettings();
}
function setTopBarTransparency(transparency) {
console.log("Prefs setTopBarTransparency called - topBarTransparency:", transparency);
topBarTransparency = transparency;
saveSettings();
}
function setPopupTransparency(transparency) {
console.log("Prefs setPopupTransparency called - popupTransparency:", transparency);
popupTransparency = transparency;
saveSettings();
}
@@ -207,98 +198,180 @@ Singleton {
// New preference setters
function setClockFormat(use24Hour) {
console.log("Prefs setClockFormat called - use24HourClock:", use24Hour);
use24HourClock = use24Hour;
saveSettings();
}
function setTemperatureUnit(fahrenheit) {
console.log("Prefs setTemperatureUnit called - useFahrenheit:", fahrenheit);
useFahrenheit = fahrenheit;
saveSettings();
}
function setNightModeEnabled(enabled) {
console.log("Prefs setNightModeEnabled called - nightModeEnabled:", enabled);
nightModeEnabled = enabled;
saveSettings();
}
function setProfileImage(imageUrl) {
console.log("Prefs setProfileImage called - profileImage:", imageUrl);
profileImage = imageUrl;
saveSettings();
}
// Widget visibility setters
function setShowFocusedWindow(enabled) {
console.log("Prefs setShowFocusedWindow called - showFocusedWindow:", enabled);
showFocusedWindow = enabled;
saveSettings();
}
function setShowWeather(enabled) {
console.log("Prefs setShowWeather called - showWeather:", enabled);
showWeather = enabled;
saveSettings();
}
function setShowMusic(enabled) {
console.log("Prefs setShowMusic called - showMusic:", enabled);
showMusic = enabled;
saveSettings();
}
function setShowClipboard(enabled) {
console.log("Prefs setShowClipboard called - showClipboard:", enabled);
showClipboard = enabled;
saveSettings();
}
function setShowSystemResources(enabled) {
console.log("Prefs setShowSystemResources called - showSystemResources:", enabled);
showSystemResources = enabled;
saveSettings();
}
function setShowSystemTray(enabled) {
console.log("Prefs setShowSystemTray called - showSystemTray:", enabled);
showSystemTray = enabled;
saveSettings();
}
// View mode setters
function setAppLauncherViewMode(mode) {
console.log("Prefs setAppLauncherViewMode called - appLauncherViewMode:", mode);
appLauncherViewMode = mode;
saveSettings();
}
function setSpotlightLauncherViewMode(mode) {
console.log("Prefs setSpotlightLauncherViewMode called - spotlightLauncherViewMode:", mode);
spotlightLauncherViewMode = mode;
saveSettings();
}
// Weather location override setter
function setWeatherLocationOverride(location) {
console.log("Prefs setWeatherLocationOverride called - weatherLocationOverride:", location);
weatherLocationOverride = location;
saveSettings();
}
// Network preference setter
function setNetworkPreference(preference) {
console.log("Prefs setNetworkPreference called - networkPreference:", preference);
networkPreference = preference;
saveSettings();
}
function detectAvailableIconThemes() {
// First detect system default, then available themes
systemDefaultDetectionProcess.running = true;
}
function setIconTheme(themeName) {
iconTheme = themeName;
updateGtkIconTheme(themeName);
updateQuickshellIconTheme(themeName);
saveSettings();
}
function updateGtkIconTheme(themeName) {
var gtkThemeName;
switch(themeName) {
case "System Default":
gtkThemeName = systemDefaultIconTheme;
break;
case "Papirus":
gtkThemeName = "Papirus";
break;
case "Papirus-Dark":
gtkThemeName = "Papirus-Dark";
break;
case "Papirus-Light":
gtkThemeName = "Papirus-Light";
break;
case "Adwaita":
gtkThemeName = "Adwaita";
break;
default:
gtkThemeName = themeName;
}
envCheckProcess.command = ["sh", "-c", "echo 'QT_QPA_PLATFORMTHEME=' $QT_QPA_PLATFORMTHEME"];
envCheckProcess.running = true;
var gtk3Settings = `[Settings]
gtk-icon-theme-name=${gtkThemeName}
gtk-theme-name=Adwaita-dark
gtk-application-prefer-dark-theme=true`;
gtk3Process.command = ["sh", "-c", `mkdir -p ~/.config/gtk-3.0 && echo '${gtk3Settings}' > ~/.config/gtk-3.0/settings.ini`];
gtk3Process.running = true;
gtk4Process.command = ["sh", "-c", `mkdir -p ~/.config/gtk-4.0 && echo '${gtk3Settings}' > ~/.config/gtk-4.0/settings.ini`];
gtk4Process.running = true;
reloadThemeProcess.command = ["sh", "-c", "gsettings set org.gnome.desktop.interface icon-theme '" + gtkThemeName + "' 2>/dev/null || true"];
reloadThemeProcess.running = true;
}
function updateQuickshellIconTheme(themeName) {
var quickshellThemeName;
switch(themeName) {
case "System Default":
quickshellThemeName = "";
break;
case "Papirus":
quickshellThemeName = "Papirus";
break;
case "Papirus-Dark":
quickshellThemeName = "Papirus-Dark";
break;
case "Papirus-Light":
quickshellThemeName = "Papirus-Light";
break;
case "Adwaita":
quickshellThemeName = "Adwaita";
break;
default:
quickshellThemeName = themeName;
}
if (quickshellThemeName) {
envSetProcess.command = ["sh", "-c", `export QS_ICON_THEME="${quickshellThemeName}" && rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true`];
} else {
envSetProcess.command = ["sh", "-c", `unset QS_ICON_THEME && rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true`];
}
envSetProcess.running = true;
}
function applyStoredIconTheme() {
if (iconTheme && iconTheme !== "System Default") {
updateGtkIconTheme(iconTheme);
}
}
Component.onCompleted: loadSettings()
// Monitor system resources preference changes to control service monitoring
onShowSystemResourcesChanged: {
console.log("Prefs: System resources monitoring", showSystemResources ? "enabled" : "disabled");
// Control SystemMonitorService based on whether system monitor widgets are visible
if (typeof SystemMonitorService !== 'undefined')
SystemMonitorService.enableTopBarMonitoring(showSystemResources);
@@ -312,13 +385,106 @@ Singleton {
blockWrites: true
watchChanges: true
onLoaded: {
console.log("Settings file loaded successfully");
parseSettings(settingsFile.text());
}
onLoadFailed: (error) => {
console.log("Settings file not found, using defaults. Error:", error);
applyStoredTheme();
}
}
Process {
id: gtk3Process
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
} else {
console.warn("Failed to update GTK 3 settings, exit code:", exitCode);
}
}
}
Process {
id: gtk4Process
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
} else {
console.warn("Failed to update GTK 4 settings, exit code:", exitCode);
}
}
}
Process {
id: reloadThemeProcess
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
} else {
console.log("GTK theme reload failed (this is normal if gsettings is not available), exit code:", exitCode);
}
}
}
Process {
id: qtThemeProcess
running: false
onExited: (exitCode) => {
console.log("Qt theme reload signal sent, exit code:", exitCode);
}
}
Process {
id: envCheckProcess
running: false
onExited: (exitCode) => {
if (exitCode !== 0) {
console.warn("Environment check failed, exit code:", exitCode);
}
}
}
Process {
id: envSetProcess
running: false
onExited: (exitCode) => {
}
}
Process {
id: systemDefaultDetectionProcess
command: ["sh", "-c", "gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed \"s/'//g\" || echo 'Adwaita'"]
running: false
onExited: (exitCode) => {
if (exitCode === 0 && stdout && stdout.length > 0) {
systemDefaultIconTheme = stdout.trim();
} else {
systemDefaultIconTheme = "Adwaita";
}
iconThemeDetectionProcess.running = true;
}
}
Process {
id: iconThemeDetectionProcess
command: ["sh", "-c", "find /usr/share/icons ~/.local/share/icons ~/.icons -maxdepth 1 -type d 2>/dev/null | sed 's|.*/||' | grep -v '^icons$' | sort -u"]
running: false
stdout: StdioCollector {
onStreamFinished: {
var detectedThemes = ["System Default"];
if (text && text.trim()) {
var themes = text.trim().split('\n');
for (var i = 0; i < themes.length; i++) {
var theme = themes[i].trim();
if (theme && theme !== "" && theme !== "default" && theme !== "hicolor" && theme !== "locolor") {
detectedThemes.push(theme);
}
}
}
availableIconThemes = detectedThemes;
}
}
}
}

View File

@@ -593,10 +593,10 @@ Singleton {
// Connect to transparency changes
if (Prefs.popupTransparencyChanged)
Prefs.popupTransparencyChanged.connect(function() {
if (typeof Prefs !== "undefined" && Prefs.popupTransparency !== undefined)
root.popupTransparency = Prefs.popupTransparency;
if (typeof Prefs !== "undefined" && Prefs.popupTransparency !== undefined)
root.popupTransparency = Prefs.popupTransparency;
});
});
}
console.log("Theme initialized, waiting for Prefs to load settings and apply theme");

104
Modules/GlobalDropdown.qml Normal file
View File

@@ -0,0 +1,104 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Widgets
PanelWindow {
id: globalDropdownWindow
property var sourceComponent: null
property var options: []
property string currentValue: ""
property int targetX: 0
property int targetY: 0
signal valueSelected(string value)
visible: sourceComponent !== null
implicitWidth: 180
implicitHeight: Math.min(200, options.length * 36 + 16)
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.margins {
top: targetY
left: targetX
}
anchors {
top: true
left: true
}
color: "transparent"
function showAt(component, globalX, globalY, opts, current) {
sourceComponent = component;
options = opts;
currentValue = current;
// Set the target position using margins
targetX = globalX;
targetY = globalY;
visible = true;
}
function hide() {
sourceComponent = null;
visible = false;
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadiusSmall
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1.0)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
border.width: 1
ScrollView {
anchors.fill: parent
anchors.margins: Theme.spacingS
clip: true
ListView {
model: globalDropdownWindow.options
spacing: 2
delegate: Rectangle {
width: ListView.view.width
height: 32
radius: Theme.cornerRadiusSmall
color: optionArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
Text {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: modelData
font.pixelSize: Theme.fontSizeMedium
color: globalDropdownWindow.currentValue === modelData ? Theme.primary : Theme.surfaceText
font.weight: globalDropdownWindow.currentValue === modelData ? Font.Medium : Font.Normal
}
MouseArea {
id: optionArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
globalDropdownWindow.valueSelected(modelData);
globalDropdownWindow.hide();
}
}
}
}
}
}
// Close on click outside
MouseArea {
anchors.fill: parent
z: -1
onClicked: globalDropdownWindow.hide()
}
}

View File

@@ -16,9 +16,13 @@ PanelWindow {
signal closingPopup()
onSettingsVisibleChanged: {
if (!settingsVisible)
if (!settingsVisible) {
closingPopup();
// Hide any open dropdown when settings close
if (typeof globalDropdownWindow !== 'undefined') {
globalDropdownWindow.hide();
}
}
}
visible: settingsVisible
implicitWidth: 600
@@ -28,6 +32,110 @@ PanelWindow {
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent"
// SettingsDropdown component - only used within this popup
Component {
id: settingsDropdownComponent
Rectangle {
id: dropdownRoot
property string text: ""
property string description: ""
property string currentValue: ""
property var options: []
signal valueChanged(string value)
width: parent.width
height: 60
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
Column {
anchors.left: parent.left
anchors.right: dropdown.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingXS
Text {
text: dropdownRoot.text
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Text {
text: dropdownRoot.description
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
visible: description.length > 0
wrapMode: Text.WordWrap
width: parent.width
}
}
Rectangle {
id: dropdown
width: 180
height: 36
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
radius: Theme.cornerRadiusSmall
color: dropdownArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.contentBackground()
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
Row {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingS
Text {
text: dropdownRoot.currentValue
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 24
elide: Text.ElideRight
}
DankIcon {
name: "expand_more"
size: 20
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: dropdownArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (typeof globalDropdownWindow !== 'undefined') {
// Get global position of the dropdown button
var globalPos = dropdown.mapToGlobal(0, 0);
globalDropdownWindow.showAt(dropdownRoot, globalPos.x, globalPos.y + dropdown.height + 4, dropdownRoot.options, dropdownRoot.currentValue);
// Connect to value selection (with cleanup)
globalDropdownWindow.valueSelected.connect(function(value) {
dropdownRoot.currentValue = value;
dropdownRoot.valueChanged(value);
});
}
}
}
}
}
}
anchors {
top: true
left: true
@@ -123,12 +231,38 @@ PanelWindow {
}
// Settings sections
ScrollView {
Flickable {
id: settingsScrollView
width: parent.width
height: parent.height - 80
clip: true
contentHeight: settingsColumn.height
boundsBehavior: Flickable.DragAndOvershootBounds
flickDeceleration: 8000
maximumFlickVelocity: 15000
property real wheelStepSize: 60
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
z: -1
onWheel: (wheel) => {
var delta = wheel.angleDelta.y
var steps = delta / 120
settingsScrollView.contentY -= steps * settingsScrollView.wheelStepSize
// Keep within bounds
if (settingsScrollView.contentY < 0)
settingsScrollView.contentY = 0
else if (settingsScrollView.contentY > settingsScrollView.contentHeight - settingsScrollView.height)
settingsScrollView.contentY = Math.max(0, settingsScrollView.contentHeight - settingsScrollView.height)
}
}
Column {
id: settingsColumn
width: parent.width
spacing: Theme.spacingL
@@ -340,6 +474,7 @@ PanelWindow {
}
// Weather Settings
SettingsSection {
title: "Weather"
@@ -534,6 +669,31 @@ PanelWindow {
}
}
Loader {
width: parent.width
sourceComponent: settingsDropdownComponent
onLoaded: {
item.text = "Icon Theme"
item.description = "Select icon theme (requires restart)"
item.currentValue = Prefs.iconTheme
// Set initial options, will be updated when detection completes
item.options = Qt.binding(function() { return Prefs.availableIconThemes; })
item.valueChanged.connect(function(value) {
Prefs.setIconTheme(value);
})
}
// Update options when available themes change
Connections {
target: Prefs
function onAvailableIconThemesChanged() {
if (parent.item && parent.item.hasOwnProperty('options')) {
parent.item.options = Prefs.availableIconThemes;
}
}
}
}
// Top Bar Transparency
Column {
width: parent.width

View File

@@ -5,7 +5,6 @@ import qs.Modules
import qs.Modules.CenterCommandCenter
import qs.Modules.ControlCenter
import qs.Modules.TopBar
import qs.Services
ShellRoot {
id: root
@@ -17,7 +16,6 @@ ShellRoot {
delegate: TopBar {
modelData: item
}
}
// Global popup windows
@@ -69,6 +67,10 @@ ShellRoot {
id: settingsPopup
}
GlobalDropdown {
id: globalDropdownWindow
}
// Application and clipboard components
AppLauncher {
id: appLauncher

View File

@@ -1,18 +0,0 @@
#!/bin/bash
# Test script for the new 2nd tier notification system
echo "Testing 2nd tier notification system..."
# Send a few test notifications to create a group
notify-send "Test App" "Short message 1"
sleep 1
notify-send "Test App" "This is a much longer message that should trigger the expand/collapse functionality for individual messages within the notification group system"
sleep 1
notify-send "Test App" "Message 3 with some content"
echo "Test notifications sent. Check the notification popup and center for:"
echo "1. 1st tier controls moved above group header"
echo "2. Message count badge next to app name when expanded"
echo "3. 'title • timestamp' format for individual messages"
echo "4. Expand/collapse buttons for long individual messages"