1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-15 18:22:08 -04:00

Fix reactivity, different settings structure, etc, etc.

This commit is contained in:
bbedward
2025-10-02 12:13:49 -04:00
parent ae461b1caf
commit 9b41eecbf1
27 changed files with 631 additions and 588 deletions

View File

@@ -79,7 +79,6 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width
text: "Automatically lock after"
options: timeoutOptions
@@ -116,7 +115,6 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width
text: "Turn off monitors after"
options: timeoutOptions
@@ -153,7 +151,6 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width
text: "Suspend system after"
options: timeoutOptions
@@ -190,7 +187,6 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width
text: "Hibernate system after"
options: timeoutOptions
visible: SessionService.hibernateSupported

View File

@@ -271,18 +271,17 @@ DankPopout {
spacing: Theme.spacingM
visible: searchField.text.length === 0
leftPadding: Theme.spacingS
topPadding: Theme.spacingXS
Item {
width: 200
height: 36
Rectangle {
width: 180
height: 40
radius: Theme.cornerRadius
color: "transparent"
DankDropdown {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
text: ""
dropdownWidth: 180
currentValue: appLauncher.selectedCategory
options: appLauncher.categories
optionIcons: appLauncher.categoryIcons
@@ -293,7 +292,7 @@ DankPopout {
}
Item {
width: parent.width - 310
width: parent.width - 290
height: 1
}

View File

@@ -99,17 +99,11 @@ Item {
target: PluginService
function onPluginLoaded(pluginId) {
console.log("DankBar: Plugin loaded:", pluginId)
// Force componentMap to update by triggering property change
if (topBarContent) {
topBarContent.updateComponentMap()
}
SettingsData.widgetDataChanged()
}
function onPluginUnloaded(pluginId) {
console.log("DankBar: Plugin unloaded:", pluginId)
// Force componentMap to update by triggering property change
if (topBarContent) {
topBarContent.updateComponentMap()
}
SettingsData.widgetDataChanged()
}
}

View File

@@ -273,7 +273,7 @@ DankPopout {
height: 32
radius: 16
color: closeBatteryArea.containsMouse ? Theme.errorHover : "transparent"
anchors.verticalCenter: parent.verticalCenter
anchors.top: parent.top
DankIcon {
anchors.centerIn: parent

View File

@@ -79,18 +79,15 @@ Loader {
}
if (item.pluginService !== undefined) {
console.log("WidgetHost: Injecting PluginService into plugin widget:", widgetId)
item.pluginService = PluginService
if (item.loadTimezones) {
console.log("WidgetHost: Calling loadTimezones for widget:", widgetId)
item.loadTimezones()
if (item.pluginId !== undefined) {
item.pluginId = widgetId
}
item.pluginService = PluginService
}
}
}
function getWidgetComponent(widgetId, components) {
// Build component map for built-in widgets
const componentMap = {
"launcherButton": components.launcherButtonComponent,
"workspaceSwitcher": components.workspaceSwitcherComponent,
@@ -121,12 +118,10 @@ Loader {
"systemUpdate": components.systemUpdateComponent
}
// Check for built-in component first
if (componentMap[widgetId]) {
return componentMap[widgetId]
}
// Check for plugin component
let pluginMap = PluginService.getWidgetComponents()
return pluginMap[widgetId] || null
}

View File

@@ -157,7 +157,6 @@ Rectangle {
}
DankDropdown {
width: parent.width
text: "Low Priority"
description: "Timeout for low priority notifications"
currentValue: getTimeoutText(SettingsData.notificationTimeoutLow)
@@ -173,7 +172,6 @@ Rectangle {
}
DankDropdown {
width: parent.width
text: "Normal Priority"
description: "Timeout for normal priority notifications"
currentValue: getTimeoutText(SettingsData.notificationTimeoutNormal)
@@ -189,7 +187,6 @@ Rectangle {
}
DankDropdown {
width: parent.width
text: "Critical Priority"
description: "Timeout for critical priority notifications"
currentValue: getTimeoutText(SettingsData.notificationTimeoutCritical)

View File

@@ -11,6 +11,8 @@ Item {
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property string pluginId: ""
property var pluginService: null
property Component horizontalBarPill: null
property Component verticalBarPill: null
@@ -18,11 +20,42 @@ Item {
property real popoutWidth: 400
property real popoutHeight: 400
property var pluginData: ({})
readonly property bool isVertical: axis?.isVertical ?? false
readonly property bool hasHorizontalPill: horizontalBarPill !== null
readonly property bool hasVerticalPill: verticalBarPill !== null
readonly property bool hasPopout: popoutContent !== null
Component.onCompleted: {
loadPluginData()
}
onPluginServiceChanged: {
loadPluginData()
}
onPluginIdChanged: {
loadPluginData()
}
Connections {
target: pluginService
function onPluginDataChanged(changedPluginId) {
if (changedPluginId === pluginId) {
loadPluginData()
}
}
}
function loadPluginData() {
if (!pluginService || !pluginId) {
pluginData = {}
return
}
pluginData = SettingsData.getPluginSettingsForPlugin(pluginId)
}
width: isVertical ? (hasVerticalPill ? verticalPill.width : 0) : (hasHorizontalPill ? horizontalPill.width : 0)
height: isVertical ? (hasVerticalPill ? verticalPill.height : 0) : (hasHorizontalPill ? horizontalPill.height : 0)
@@ -60,6 +93,12 @@ Item {
}
}
function closePopout() {
if (pluginPopout) {
pluginPopout.close()
}
}
PluginPopout {
id: pluginPopout
contentWidth: root.popoutWidth

View File

@@ -62,53 +62,24 @@ DankPopout {
Column {
id: popoutColumn
width: parent.width - Theme.spacingL * 2
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: Theme.spacingL
spacing: Theme.spacingL
Row {
width: parent.width
height: 32
visible: closeButton.visible
Item {
width: parent.width - 32
height: 32
}
Rectangle {
id: closeButton
width: 32
height: 32
radius: 16
color: closeArea.containsMouse ? Theme.errorHover : "transparent"
visible: true
DankIcon {
anchors.centerIn: parent
name: "close"
size: Theme.iconSize - 4
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.close()
}
}
}
}
width: parent.width - Theme.spacingS * 2
height: parent.height - Theme.spacingS * 2
x: Theme.spacingS
y: Theme.spacingS
spacing: Theme.spacingS
Loader {
id: popoutContent
width: parent.width
sourceComponent: root.pluginContent
onLoaded: {
if (item && "closePopout" in item) {
item.closePopout = function() {
root.close()
}
}
}
}
}
}

View File

@@ -10,11 +10,24 @@ Item {
property var pluginService: null
default property alias content: settingsColumn.children
signal settingChanged()
implicitHeight: hasPermission ? settingsColumn.implicitHeight : errorText.implicitHeight
height: implicitHeight
readonly property bool hasPermission: pluginService && pluginService.hasPermission ? pluginService.hasPermission(pluginId, "settings_write") : true
onPluginServiceChanged: {
if (pluginService) {
for (let i = 0; i < settingsColumn.children.length; i++) {
const child = settingsColumn.children[i]
if (child.loadValue) {
child.loadValue()
}
}
}
}
function saveValue(key, value) {
if (!pluginService) {
return
@@ -25,6 +38,7 @@ Item {
}
if (pluginService.savePluginData) {
pluginService.savePluginData(pluginId, key, value)
settingChanged()
}
}

View File

@@ -0,0 +1,76 @@
import QtQuick
import qs.Common
import qs.Widgets
Column {
id: root
property string headerText: ""
property string detailsText: ""
property bool showCloseButton: false
property var closePopout: null
readonly property int headerHeight: popoutHeader.visible ? popoutHeader.height : 0
readonly property int detailsHeight: popoutDetails.visible ? popoutDetails.implicitHeight : 0
spacing: 0
Item {
id: popoutHeader
width: parent.width
height: 40
visible: headerText.length > 0
StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: root.headerText
font.pixelSize: Theme.fontSizeLarge + 4
font.weight: Font.Bold
color: Theme.surfaceText
}
Rectangle {
id: closeButton
width: 32
height: 32
radius: 16
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: closeArea.containsMouse ? Theme.errorHover : "transparent"
visible: root.showCloseButton
DankIcon {
anchors.centerIn: parent
name: "close"
size: Theme.iconSize - 4
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
if (root.closePopout) {
root.closePopout()
}
}
}
}
}
StyledText {
id: popoutDetails
width: parent.width
leftPadding: Theme.spacingS
bottomPadding: Theme.spacingS
text: root.detailsText
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
visible: detailsText.length > 0
wrapMode: Text.WordWrap
}
}

View File

@@ -15,6 +15,17 @@ Column {
width: parent.width
spacing: Theme.spacingS
function loadValue() {
const settings = findSettings()
if (settings && settings.pluginService) {
value = settings.loadValue(settingKey, defaultValue)
}
}
Component.onCompleted: {
loadValue()
}
readonly property var optionLabels: {
const labels = []
for (let i = 0; i < options.length; i++) {
@@ -49,13 +60,6 @@ Column {
return map
}
Component.onCompleted: {
const settings = findSettings()
if (settings) {
value = settings.loadValue(settingKey, defaultValue)
}
}
onValueChanged: {
const settings = findSettings()
if (settings) {
@@ -74,40 +78,14 @@ Column {
return null
}
Row {
width: parent.width
spacing: Theme.spacingM
Column {
width: parent.width * 0.4
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: root.label
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: root.description
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.WordWrap
visible: root.description !== ""
}
}
DankDropdown {
width: parent.width * 0.6 - Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
width: parent.width
text: root.label
description: root.description
currentValue: root.valueToLabel[root.value] || root.value
options: root.optionLabels
onValueChanged: newValue => {
root.value = root.labelToValue[newValue] || newValue
}
}
}
}

View File

@@ -19,13 +19,17 @@ Column {
width: parent.width
spacing: Theme.spacingS
Component.onCompleted: {
function loadValue() {
const settings = findSettings()
if (settings) {
if (settings && settings.pluginService) {
value = settings.loadValue(settingKey, defaultValue)
}
}
Component.onCompleted: {
loadValue()
}
onValueChanged: {
const settings = findSettings()
if (settings) {
@@ -69,6 +73,7 @@ Column {
rightIcon: root.rightIcon
unit: root.unit
wheelEnabled: false
thumbOutlineColor: Theme.surfaceContainerHighest
onSliderValueChanged: newValue => {
root.value = newValue
}

View File

@@ -8,6 +8,15 @@ import qs.Widgets
Item {
id: dankBarTab
function getWidgetsForPopup() {
return baseWidgetDefinitions.filter(widget => {
if (widget.warning && widget.warning.includes("Plugin is disabled")) {
return false
}
return true
})
}
property var baseWidgetDefinitions: {
var coreWidgets = [{
"id": "launcherButton",
@@ -179,16 +188,18 @@ Item {
"enabled": SystemUpdateService.distributionSupported
}]
// Add plugin widgets dynamically
var loadedPlugins = PluginService.getLoadedPlugins()
for (var i = 0; i < loadedPlugins.length; i++) {
var plugin = loadedPlugins[i]
// Add all available plugins (loaded and unloaded)
var allPlugins = PluginService.getAvailablePlugins()
for (var i = 0; i < allPlugins.length; i++) {
var plugin = allPlugins[i]
var isLoaded = PluginService.isPluginLoaded(plugin.id)
coreWidgets.push({
"id": plugin.id,
"text": plugin.name,
"description": plugin.description || "Plugin widget",
"icon": plugin.icon || "extension",
"enabled": true
"enabled": isLoaded,
"warning": !isLoaded ? "Plugin is disabled - enable in Plugins settings to use" : undefined
})
}
@@ -1146,7 +1157,7 @@ Item {
}
onAddWidget: sectionId => {
widgetSelectionPopup.allWidgets
= dankBarTab.baseWidgetDefinitions
= dankBarTab.getWidgetsForPopup()
widgetSelectionPopup.targetSection = sectionId
widgetSelectionPopup.safeOpen()
}
@@ -1218,7 +1229,7 @@ Item {
}
onAddWidget: sectionId => {
widgetSelectionPopup.allWidgets
= dankBarTab.baseWidgetDefinitions
= dankBarTab.getWidgetsForPopup()
widgetSelectionPopup.targetSection = sectionId
widgetSelectionPopup.safeOpen()
}
@@ -1290,7 +1301,7 @@ Item {
}
onAddWidget: sectionId => {
widgetSelectionPopup.allWidgets
= dankBarTab.baseWidgetDefinitions
= dankBarTab.getWidgetsForPopup()
widgetSelectionPopup.targetSection = sectionId
widgetSelectionPopup.safeOpen()
}

View File

@@ -520,7 +520,6 @@ Item {
DankDropdown {
id: monitorDropdown
width: parent.width - parent.leftPadding
text: "Monitor"
description: "Select monitor to configure wallpaper"
currentValue: selectedMonitorName || "No monitors"
@@ -678,7 +677,6 @@ Item {
property var intervalOptions: ["1 minute", "5 minutes", "15 minutes", "30 minutes", "1 hour", "1.5 hours", "2 hours", "3 hours", "4 hours", "6 hours", "8 hours", "12 hours"]
property var intervalValues: [60, 300, 900, 1800, 3600, 5400, 7200, 10800, 14400, 21600, 28800, 43200]
width: parent.width - parent.leftPadding
visible: {
if (SessionData.perMonitorWallpaper) {
return SessionData.getMonitorCyclingSettings(selectedMonitorName).mode === "interval"
@@ -833,7 +831,6 @@ Item {
}
DankDropdown {
width: parent.width
text: "Transition Effect"
description: "Visual effect used when wallpaper changes"
currentValue: {
@@ -851,8 +848,6 @@ Item {
width: parent.width
spacing: Theme.spacingS
visible: SessionData.wallpaperTransition === "random"
leftPadding: Theme.spacingM
rightPadding: Theme.spacingM
StyledText {
text: "Include Transitions"
@@ -866,12 +861,12 @@ Item {
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width - parent.leftPadding - parent.rightPadding
width: parent.width
}
DankButtonGroup {
id: transitionGroup
width: parent.width - parent.leftPadding - parent.rightPadding
width: parent.width
selectionMode: "multi"
model: SessionData.availableWallpaperTransitions.filter(t => t !== "none")
initialSelection: SessionData.includedTransitions
@@ -959,7 +954,6 @@ Item {
DankDropdown {
id: personalizationMatugenPaletteDropdown
width: parent.width
text: "Matugen Palette"
description: "Select the palette algorithm used for wallpaper-based colors"
options: Theme.availableMatugenSchemes.map(function (option) { return option.label })
@@ -1075,7 +1069,6 @@ Item {
}
DankDropdown {
width: parent.width
text: "Temperature"
description: "Color temperature for night mode"
currentValue: SessionData.nightModeTemperature + "K"
@@ -1424,7 +1417,6 @@ Item {
}
DankDropdown {
width: parent.width
text: "Popup Position"
description: "Choose where notification popups appear on screen"
currentValue: {
@@ -1506,7 +1498,6 @@ Item {
}
DankDropdown {
width: parent.width
text: "Font Family"
description: "Select system font family"
currentValue: {
@@ -1528,7 +1519,6 @@ Item {
}
DankDropdown {
width: parent.width
text: "Font Weight"
description: "Select font weight"
currentValue: {
@@ -1595,7 +1585,6 @@ Item {
}
DankDropdown {
width: parent.width
text: "Monospace Font"
description: "Select monospace font for process list and technical displays"
currentValue: {

View File

@@ -10,14 +10,6 @@ Item {
property string expandedPluginId: ""
Component.onCompleted: {
console.log("PluginsTab: Component completed")
console.log("PluginsTab: PluginService available:", typeof PluginService !== "undefined")
if (typeof PluginService !== "undefined") {
console.log("PluginsTab: Available plugins:", Object.keys(PluginService.availablePlugins).length)
console.log("PluginsTab: Plugin directory:", PluginService.pluginDirectory)
}
}
DankFlickable {
anchors.fill: parent
@@ -186,9 +178,6 @@ Item {
property bool hasSettings: pluginData && pluginData.settings !== undefined && pluginData.settings !== ""
property bool isExpanded: pluginsTab.expandedPluginId === pluginId
onIsExpandedChanged: {
console.log("Plugin", pluginId, "isExpanded changed to:", isExpanded)
}
color: pluginMouseArea.containsMouse ? Theme.surfacePressed : (isExpanded ? Theme.surfaceContainerHighest : Theme.surfaceContainerHigh)
border.width: 0
@@ -200,15 +189,8 @@ Item {
hoverEnabled: true
cursorShape: pluginDelegate.hasSettings ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
console.log("Plugin clicked:", pluginDelegate.pluginId, "hasSettings:", pluginDelegate.hasSettings, "isLoaded:", PluginService.isPluginLoaded(pluginDelegate.pluginId))
if (pluginDelegate.hasSettings) {
if (pluginsTab.expandedPluginId === pluginDelegate.pluginId) {
console.log("Collapsing plugin:", pluginDelegate.pluginId)
pluginsTab.expandedPluginId = ""
} else {
console.log("Expanding plugin:", pluginDelegate.pluginId)
pluginsTab.expandedPluginId = pluginDelegate.pluginId
}
pluginsTab.expandedPluginId = pluginsTab.expandedPluginId === pluginDelegate.pluginId ? "" : pluginDelegate.pluginId
}
}
}
@@ -234,7 +216,7 @@ Item {
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - pluginToggle.width - Theme.spacingM
width: parent.width - Theme.iconSize - Theme.spacingM - toggleRow.width - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
@@ -267,6 +249,44 @@ Item {
}
}
Row {
id: toggleRow
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
Rectangle {
width: 28
height: 28
radius: 14
color: reloadArea.containsMouse ? Theme.surfaceContainerHighest : "transparent"
visible: PluginService.isPluginLoaded(pluginDelegate.pluginId)
DankIcon {
anchors.centerIn: parent
name: "refresh"
size: 16
color: reloadArea.containsMouse ? Theme.primary : Theme.surfaceVariantText
}
MouseArea {
id: reloadArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const currentPluginId = pluginDelegate.pluginId
const currentPluginName = pluginDelegate.pluginName
pluginsTab.isReloading = true
if (PluginService.reloadPlugin(currentPluginId)) {
ToastService.showInfo("Plugin reloaded: " + currentPluginName)
} else {
ToastService.showError("Failed to reload plugin: " + currentPluginName)
pluginsTab.isReloading = false
}
}
}
}
DankToggle {
id: pluginToggle
anchors.verticalCenter: parent.verticalCenter
@@ -296,6 +316,7 @@ Item {
}
}
}
}
StyledText {
width: parent.width
@@ -358,15 +379,8 @@ Item {
active: pluginDelegate.isExpanded && pluginDelegate.hasSettings && PluginService.isPluginLoaded(pluginDelegate.pluginId)
asynchronous: false
onActiveChanged: {
console.log("Settings loader active changed to:", active, "for plugin:", pluginDelegate.pluginId,
"isExpanded:", pluginDelegate.isExpanded, "hasSettings:", pluginDelegate.hasSettings,
"isLoaded:", PluginService.isPluginLoaded(pluginDelegate.pluginId))
}
source: {
if (active && pluginDelegate.pluginSettingsPath) {
console.log("Loading plugin settings from:", pluginDelegate.pluginSettingsPath)
var path = pluginDelegate.pluginSettingsPath
if (!path.startsWith("file://")) {
path = "file://" + path
@@ -376,37 +390,9 @@ Item {
return ""
}
onStatusChanged: {
console.log("Settings loader status changed:", status, "for plugin:", pluginDelegate.pluginId)
if (status === Loader.Error) {
console.error("Failed to load plugin settings:", pluginDelegate.pluginSettingsPath)
} else if (status === Loader.Ready) {
console.log("Settings successfully loaded for plugin:", pluginDelegate.pluginId)
}
}
onLoaded: {
if (item) {
console.log("Plugin settings loaded for:", pluginDelegate.pluginId)
if (typeof PluginService !== "undefined") {
console.log("Making PluginService available to plugin settings")
console.log("PluginService functions available:",
"savePluginData" in PluginService,
"loadPluginData" in PluginService)
if (item && typeof PluginService !== "undefined") {
item.pluginService = PluginService
console.log("PluginService assignment completed, item.pluginService:", item.pluginService !== null)
} else {
console.error("PluginService not available in PluginsTab context")
}
if (item.loadTimezones) {
console.log("Calling loadTimezones for WorldClock plugin")
item.loadTimezones()
}
if (item.initializeSettings) {
item.initializeSettings()
}
}
}
}
@@ -440,14 +426,19 @@ Item {
}
}
property bool isReloading: false
Connections {
target: PluginService
function onPluginLoaded() {
pluginRepeater.model = PluginService.getAvailablePlugins()
if (isReloading) {
isReloading = false
}
}
function onPluginUnloaded() {
pluginRepeater.model = PluginService.getAvailablePlugins()
if (pluginsTab.expandedPluginId !== "" && !PluginService.isPluginLoaded(pluginsTab.expandedPluginId)) {
if (!isReloading && pluginsTab.expandedPluginId !== "" && !PluginService.isPluginLoaded(pluginsTab.expandedPluginId)) {
pluginsTab.expandedPluginId = ""
}
}

View File

@@ -651,7 +651,6 @@ Item {
DankDropdown {
id: matugenPaletteDropdown
width: parent.width
text: "Matugen Palette"
description: "Select the palette algorithm used for wallpaper-based colors"
options: Theme.availableMatugenSchemes.map(function (option) { return option.label })
@@ -993,7 +992,6 @@ Item {
}
DankDropdown {
width: parent.width - Theme.iconSize - Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
text: "Icon Theme"
description: "DankShell & System Icons\n(requires restart)"

View File

@@ -121,7 +121,6 @@ Item {
}
DankDropdown {
width: parent.width
height: 50
text: "Top Bar Format"
description: "Preview: " + (SettingsData.clockDateFormat ? new Date().toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) : new Date().toLocaleDateString(Qt.locale(), "ddd d"))
@@ -185,7 +184,6 @@ Item {
}
DankDropdown {
width: parent.width
height: 50
text: "Lock Screen Format"
description: "Preview: " + (SettingsData.lockDateFormat ? new Date().toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat) : new Date().toLocaleDateString(Qt.locale(), Locale.LongFormat))

View File

@@ -142,13 +142,14 @@ Column {
spacing: Theme.spacingXS
Item {
width: 120
width: 60
height: 32
visible: modelData.id === "gpuTemp"
DankDropdown {
id: gpuDropdown
anchors.fill: parent
popupWidth: -1
currentValue: {
var selectedIndex = modelData.selectedGpuIndex
!== undefined ? modelData.selectedGpuIndex : 0
@@ -223,12 +224,7 @@ Column {
Item {
width: 32
height: 32
visible: (modelData.warning !== undefined
&& modelData.warning !== "")
&& (modelData.id === "cpuUsage"
|| modelData.id === "memUsage"
|| modelData.id === "cpuTemp"
|| modelData.id === "gpuTemp")
visible: modelData.warning !== undefined && modelData.warning !== ""
DankIcon {
name: "warning"

View File

@@ -8,18 +8,13 @@ import qs.Modules.Plugins
PluginComponent {
id: root
property var pluginService: null
property var enabledEmojis: pluginData.emojis || ["😊", "😢", "❤️"]
property int cycleInterval: pluginData.cycleInterval || 3000
property int maxBarEmojis: pluginData.maxBarEmojis || 3
// Load settings from PluginService
property var enabledEmojis: pluginService ? pluginService.loadPluginData("exampleEmojiPlugin", "emojis", ["😊", "😢", "❤️"]) : ["😊", "😢", "❤️"]
property int cycleInterval: pluginService ? pluginService.loadPluginData("exampleEmojiPlugin", "cycleInterval", 3000) : 3000
property int maxBarEmojis: pluginService ? pluginService.loadPluginData("exampleEmojiPlugin", "maxBarEmojis", 3) : 3
// Current state for cycling through emojis
property int currentIndex: 0
property var displayedEmojis: []
// Timer to cycle through emojis at the configured interval
Timer {
interval: root.cycleInterval
running: true
@@ -32,7 +27,6 @@ PluginComponent {
}
}
// Update the emojis shown in the bar when settings or index changes
function updateDisplayedEmojis() {
const maxToShow = Math.min(root.maxBarEmojis, root.enabledEmojis.length)
let emojis = []
@@ -51,15 +45,8 @@ PluginComponent {
onMaxBarEmojisChanged: updateDisplayedEmojis()
horizontalBarPill: Component {
StyledRect {
width: emojiRow.implicitWidth + Theme.spacingM * 2
height: parent.widgetThickness
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Row {
id: emojiRow
anchors.centerIn: parent
spacing: Theme.spacingXS
Repeater {
@@ -71,18 +58,10 @@ PluginComponent {
}
}
}
}
verticalBarPill: Component {
StyledRect {
width: parent.widgetThickness
height: emojiColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: emojiColumn
anchors.centerIn: parent
spacing: Theme.spacingXS
Repeater {
@@ -95,14 +74,15 @@ PluginComponent {
}
}
}
}
popoutContent: Component {
Item {
width: parent.width
height: parent.height
PopoutComponent {
id: popoutColumn
headerText: "Emoji Picker"
detailsText: "Click an emoji to copy it to clipboard"
showCloseButton: true
// A grid of 120+ emojis for the user to pick from
property var allEmojis: [
"😀", "😃", "😄", "😁", "😆", "😅", "🤣", "😂", "🙂", "🙃",
"😉", "😊", "😇", "🥰", "😍", "🤩", "😘", "😗", "😚", "😙",
@@ -119,35 +99,21 @@ PluginComponent {
"👌", "🤌", "🤏", "👈", "👉", "👆", "👇", "☝️", "✋", "🤚"
]
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
StyledText {
text: "Click an emoji to copy it!"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
DankFlickable {
Item {
width: parent.width
height: parent.height - parent.spacing - 30
contentWidth: emojiGrid.width
contentHeight: emojiGrid.height
clip: true
implicitHeight: root.popoutHeight - popoutColumn.headerHeight - popoutColumn.detailsHeight - Theme.spacingXL
Grid {
DankGridView {
id: emojiGrid
width: parent.width - Theme.spacingM
columns: 8
spacing: Theme.spacingS
anchors.horizontalCenter: parent.horizontalCenter
width: Math.floor(parent.width / 50) * 50
height: parent.height
clip: true
cellWidth: 50
cellHeight: 50
model: popoutColumn.allEmojis
Repeater {
model: allEmojis
StyledRect {
delegate: StyledRect {
width: 45
height: 45
radius: Theme.cornerRadius
@@ -168,10 +134,8 @@ PluginComponent {
onClicked: {
Quickshell.execDetached(["sh", "-c", "echo -n '" + modelData + "' | wl-copy"])
ToastService.show("Copied " + modelData + " to clipboard", 2000)
root.closePopout()
}
}
ToastService.showInfo("Copied " + modelData + " to clipboard")
popoutColumn.closePopout()
}
}
}

View File

@@ -1,6 +1,6 @@
# Emoji Cycler Plugin
An example DankMaterialShell plugin that displays cycling emojis in your bar with an emoji picker popout.
An example dms plugin that displays cycling emojis in your bar with an emoji picker popout.
## Features

View File

@@ -3,7 +3,7 @@
"name": "Emoji Cycler",
"description": "Display cycling emojis in your bar with a handy emoji picker popout",
"version": "1.0.0",
"author": "DMS Plugin System",
"author": "AvengeMedia",
"icon": "mood",
"component": "./EmojiWidget.qml",
"settings": "./EmojiSettings.qml",

View File

@@ -121,10 +121,15 @@ PluginComponent {
// Define popout content (optional)
popoutContent: Component {
PopoutComponent {
headerText: "My Plugin"
detailsText: "Optional description text goes here"
showCloseButton: true
// Your popout content goes here
Column {
width: parent.width
spacing: Theme.spacingM
padding: Theme.spacingM
StyledText {
text: "Popout Content"
@@ -133,6 +138,7 @@ PluginComponent {
}
}
}
}
// Popout dimensions (required if popoutContent is set)
popoutWidth: 400
@@ -160,6 +166,42 @@ The PluginComponent automatically handles:
- Proper positioning and anchoring
- Theme integration
### PopoutComponent
PopoutComponent provides a consistent header/content layout for plugin popouts:
```qml
import qs.Modules.Plugins
PopoutComponent {
headerText: "Header Title" // Main header text (bold, large)
detailsText: "Description text" // Optional description (smaller, gray)
showCloseButton: true // Show X button in top-right
// Access to exposed properties for dynamic sizing
readonly property int headerHeight // Height of header area
readonly property int detailsHeight // Height of description area
// Your content here - use parent.width for full width
// Calculate available height: root.popoutHeight - headerHeight - detailsHeight - spacing
DankGridView {
width: parent.width
height: parent.height
// ...
}
}
```
**PopoutComponent Properties:**
- `headerText`: Main header text (optional, hidden if empty)
- `detailsText`: Description text below header (optional, hidden if empty)
- `showCloseButton`: Show close button in header (default: false)
- `closePopout`: Function to close popout (auto-injected by PluginPopout)
- `headerHeight`: Readonly height of header (0 if not visible)
- `detailsHeight`: Readonly height of description (0 if not visible)
The component automatically handles spacing and layout. Content children are rendered below the description with proper padding.
### Settings Component
Optional settings UI loaded inline in the PluginsTab accordion interface. Use the simplified settings API with auto-storage components:

View File

@@ -11,7 +11,7 @@
</div>
A modern Wayland desktop shell built with [Quickshell](https://quickshell.org/) and designed for the [niri](https://github.com/YaLTeR/niri) and [Hyprland](https://hyprland.org/) compositors. Features Material 3 design principles with a heavy focus on functionality and customizability.
A modern Wayland desktop shell built with [Quickshell](https://quickshell.org/) and optimized for the [niri](https://github.com/YaLTeR/niri) and [Hyprland](https://hyprland.org/) compositors.
## Screenshots
@@ -122,6 +122,8 @@ curl -fsSL https://install.danklinux.com | sh
- Configure bluetooth, wifi, and audio input+output devices.
- A lock screen
- Idle monitoring - configure auto lock, screen off, suspend, and hibernate with different knobs for battery + AC power.
- A greeter
- A comprehensive plugin system for endless customization possibilities.
**TL;DR** *dms replaces your waybar, swaylock, swayidle, hypridle, hyprlock, fuzzels, walker, mako, and basically everything you use to stitch a desktop together*
@@ -629,6 +631,14 @@ echo "app-notifications = no-clipboard-copy,no-config-reload" >> ~/.config/ghost
echo "include dank-theme.conf" >> ~/.config/kitty/kitty.conf
```
## Plugins
dms features a plugin system - meaning you can create your own widgets and load other user widgets.
More comprehensive details available in the [PLUGINS](PLUGINS/README.md) - and example [Emoji Plugin](PLUGINS/ExampleEmojiPlugin) is available for reference.
The example plugin can be installed by `cp -R ./PLUGINS/ExampleEmojiPlugin ~/.config/DankMaterialShell/plugins` - then it will appear in dms settings.
### Calendar Setup
Sync your caldev compatible calendar (Google, Office365, etc.) for dashboard integration:

View File

@@ -28,6 +28,7 @@ Singleton {
signal pluginLoaded(string pluginId)
signal pluginUnloaded(string pluginId)
signal pluginLoadFailed(string pluginId, string error)
signal pluginDataChanged(string pluginId)
Component.onCompleted: {
Qt.callLater(initializePlugins)
@@ -225,7 +226,10 @@ Singleton {
return false
}
pluginWidgetComponents[pluginId] = component
var newComponents = Object.assign({}, pluginWidgetComponents)
newComponents[pluginId] = component
pluginWidgetComponents = newComponents
plugin.loaded = true
loadedPlugins[pluginId] = plugin
@@ -254,7 +258,9 @@ Singleton {
component.destroy()
}
}
delete pluginWidgetComponents[pluginId]
var newComponents = Object.assign({}, pluginWidgetComponents)
delete newComponents[pluginId]
pluginWidgetComponents = newComponents
plugin.loaded = false
delete loadedPlugins[pluginId]
@@ -314,6 +320,7 @@ Singleton {
function savePluginData(pluginId, key, value) {
SettingsData.setPluginSetting(pluginId, key, value)
pluginDataChanged(pluginId)
return true
}

View File

@@ -1,10 +1,11 @@
import "../Common/fzf.js" as Fzf
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import qs.Common
import qs.Widgets
Rectangle {
Item {
id: root
property string text: ""
@@ -12,50 +13,33 @@ Rectangle {
property string currentValue: ""
property var options: []
property var optionIcons: []
property bool forceRecreate: false
property bool enableFuzzySearch: false
property int popupWidthOffset: 0
property int maxPopupHeight: 400
property bool openUpwards: false
property int popupWidth: 0
property bool alignPopupRight: false
property int dropdownWidth: 200
signal valueChanged(string value)
width: parent.width
height: 60
radius: Theme.cornerRadius
color: "transparent"
Component.onCompleted: forceRecreateTimer.start()
implicitHeight: Math.max(60, labelColumn.implicitHeight + Theme.spacingM)
Component.onDestruction: {
const popup = popupLoader.item
const popup = dropdownMenu
if (popup && popup.visible) {
popup.close()
}
}
onVisibleChanged: {
const popup = popupLoader.item
if (!visible && popup && popup.visible) {
popup.close()
} else if (visible) {
forceRecreateTimer.start()
}
}
Timer {
id: forceRecreateTimer
interval: 50
repeat: false
onTriggered: root.forceRecreate = !root.forceRecreate
}
Column {
id: labelColumn
anchors.left: parent.left
anchors.right: dropdown.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingL
spacing: Theme.spacingXS
StyledText {
@@ -78,15 +62,14 @@ Rectangle {
Rectangle {
id: dropdown
width: root.width <= 60 ? root.width : 180
height: 36
width: root.popupWidth === -1 ? undefined : (root.popupWidth > 0 ? root.popupWidth : root.dropdownWidth)
height: 40
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
radius: Theme.cornerRadius
color: dropdownArea.containsMouse ? Theme.primaryHover : Theme.contentBackground()
border.color: Theme.surfaceVariantAlpha
border.width: 1
color: dropdownArea.containsMouse || dropdownMenu.visible ? Theme.surfaceContainerHigh : Theme.surfaceContainer
border.color: dropdownMenu.visible ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: dropdownMenu.visible ? 2 : 1
MouseArea {
id: dropdownArea
@@ -95,42 +78,39 @@ Rectangle {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const popup = popupLoader.item
if (!popup) {
if (dropdownMenu.visible) {
dropdownMenu.close()
return
}
if (popup.visible) {
popup.close()
return
}
dropdownMenu.searchQuery = ""
dropdownMenu.updateFilteredOptions()
dropdownMenu.open()
if (root.openUpwards || root.alignPopupRight) {
popup.open()
Qt.callLater(() => {
if (root.openUpwards) {
const pos = dropdown.mapToItem(Overlay.overlay, 0, 0)
const popupWidth = dropdownMenu.width
const popupHeight = dropdownMenu.height
const overlayHeight = Overlay.overlay.height
if (root.openUpwards || pos.y + dropdown.height + popupHeight + 4 > overlayHeight) {
if (root.alignPopupRight) {
popup.x = pos.x + dropdown.width - popup.width
dropdownMenu.x = pos.x + dropdown.width - popupWidth
} else {
popup.x = pos.x - (root.popupWidthOffset / 2)
dropdownMenu.x = pos.x - (root.popupWidthOffset / 2)
}
popup.y = pos.y - popup.height - 4
dropdownMenu.y = pos.y - popupHeight - 4
} else {
const pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4)
if (root.alignPopupRight) {
popup.x = pos.x + dropdown.width - popup.width
dropdownMenu.x = pos.x + dropdown.width - popupWidth
} else {
popup.x = pos.x - (root.popupWidthOffset / 2)
dropdownMenu.x = pos.x - (root.popupWidthOffset / 2)
}
popup.y = pos.y
dropdownMenu.y = pos.y + dropdown.height + 4
}
})
} else {
const pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4)
popup.x = pos.x - (root.popupWidthOffset / 2)
popup.y = pos.y
popup.open()
if (root.enableFuzzySearch && searchField.visible) {
searchField.forceActiveFocus()
}
}
}
@@ -139,8 +119,10 @@ Rectangle {
id: contentRow
anchors.left: parent.left
anchors.right: expandIcon.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingS
spacing: Theme.spacingS
DankIcon {
@@ -149,9 +131,9 @@ Rectangle {
return currentIndex >= 0 && root.optionIcons.length > currentIndex ? root.optionIcons[currentIndex] : ""
}
size: 18
color: Theme.surfaceVariantText
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: name !== "" && root.width > 60
visible: name !== ""
}
StyledText {
@@ -159,36 +141,30 @@ Rectangle {
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
width: root.width <= 60 ? dropdown.width - expandIcon.width - Theme.spacingS * 2 : dropdown.width - contentRow.x - expandIcon.width - Theme.spacingM - Theme.spacingS
elide: root.width <= 60 ? Text.ElideNone : Text.ElideRight
horizontalAlignment: root.width <= 60 ? Text.AlignHCenter : Text.AlignLeft
width: contentRow.width - (contentRow.children[0].visible ? contentRow.children[0].width + contentRow.spacing : 0)
elide: Text.ElideRight
}
}
DankIcon {
id: expandIcon
name: "expand_more"
name: dropdownMenu.visible ? "expand_less" : "expand_more"
size: 20
color: Theme.surfaceVariantText
color: Theme.surfaceText
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Theme.spacingS
Behavior on rotation {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
Loader {
id: popupLoader
property bool recreateFlag: root.forceRecreate
active: true
onRecreateFlagChanged: {
active = false
active = true
}
sourceComponent: Component {
Popup {
id: dropdownMenu
@@ -239,18 +215,11 @@ Rectangle {
}
parent: Overlay.overlay
width: root.popupWidth > 0 ? root.popupWidth : (dropdown.width + root.popupWidthOffset)
width: root.popupWidth === -1 ? undefined : (root.popupWidth > 0 ? root.popupWidth : (dropdown.width + root.popupWidthOffset))
height: Math.min(root.maxPopupHeight, (root.enableFuzzySearch ? 54 : 0) + Math.min(filteredOptions.length, 10) * 36 + 16)
padding: 0
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
onOpened: {
searchQuery = ""
updateFilteredOptions()
if (root.enableFuzzySearch && searchField.visible) {
searchField.forceActiveFocus()
}
}
background: Rectangle {
color: "transparent"
@@ -258,10 +227,18 @@ Rectangle {
contentItem: Rectangle {
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
border.color: Theme.primarySelected
border.width: 1
border.color: Theme.primary
border.width: 2
radius: Theme.cornerRadius
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowBlur: 0.4
shadowColor: Theme.shadowStrong
shadowVerticalOffset: 4
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingS
@@ -273,7 +250,7 @@ Rectangle {
height: 42
visible: root.enableFuzzySearch
radius: Theme.cornerRadius
color: Theme.surfaceVariantAlpha
color: Theme.surfaceContainerHigh
DankTextField {
id: searchField
@@ -281,29 +258,29 @@ Rectangle {
anchors.fill: parent
anchors.margins: 1
placeholderText: "Search..."
text: searchQuery
text: dropdownMenu.searchQuery
topPadding: Theme.spacingS
bottomPadding: Theme.spacingS
onTextChanged: {
searchQuery = text
updateFilteredOptions()
dropdownMenu.searchQuery = text
dropdownMenu.updateFilteredOptions()
}
Keys.onDownPressed: selectNext()
Keys.onUpPressed: selectPrevious()
Keys.onReturnPressed: selectCurrent()
Keys.onEnterPressed: selectCurrent()
Keys.onDownPressed: dropdownMenu.selectNext()
Keys.onUpPressed: dropdownMenu.selectPrevious()
Keys.onReturnPressed: dropdownMenu.selectCurrent()
Keys.onEnterPressed: dropdownMenu.selectCurrent()
Keys.onPressed: event => {
if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
selectNext()
dropdownMenu.selectNext()
event.accepted = true
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
selectPrevious()
dropdownMenu.selectPrevious()
event.accepted = true
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
selectNext()
dropdownMenu.selectNext()
event.accepted = true
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
selectPrevious()
dropdownMenu.selectPrevious()
event.accepted = true
}
}
@@ -319,12 +296,10 @@ Rectangle {
DankListView {
id: listView
property var popupRef: dropdownMenu
width: parent.width
height: parent.height - (root.enableFuzzySearch ? searchContainer.height + Theme.spacingXS : 0)
clip: true
model: filteredOptions
model: dropdownMenu.filteredOptions
spacing: 2
interactive: true
@@ -336,7 +311,7 @@ Rectangle {
flickableDirection: Flickable.VerticalFlick
delegate: Rectangle {
property bool isSelected: selectedIndex === index
property bool isSelected: dropdownMenu.selectedIndex === index
property bool isCurrentValue: root.currentValue === modelData
property int optionIndex: root.options.indexOf(modelData)
@@ -354,7 +329,7 @@ Rectangle {
DankIcon {
name: optionIndex >= 0 && root.optionIcons.length > optionIndex ? root.optionIcons[optionIndex] : ""
size: 18
color: isCurrentValue ? Theme.primary : Theme.surfaceVariantText
color: isCurrentValue ? Theme.primary : Theme.surfaceText
visible: name !== ""
}
@@ -379,9 +354,7 @@ Rectangle {
onClicked: {
root.currentValue = modelData
root.valueChanged(modelData)
listView.popupRef.close()
}
}
dropdownMenu.close()
}
}
}