1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-27 23:12:49 -05: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 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] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width
text: "Automatically lock after" text: "Automatically lock after"
options: timeoutOptions 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 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] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width
text: "Turn off monitors after" text: "Turn off monitors after"
options: timeoutOptions 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 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] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width
text: "Suspend system after" text: "Suspend system after"
options: timeoutOptions 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 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] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width
text: "Hibernate system after" text: "Hibernate system after"
options: timeoutOptions options: timeoutOptions
visible: SessionService.hibernateSupported visible: SessionService.hibernateSupported

View File

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

View File

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

View File

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

View File

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

View File

@@ -163,7 +163,7 @@ Item {
DankDropdown { DankDropdown {
id: fontDropdown id: fontDropdown
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM width: parent.width + Theme.spacingM
text: "Font Family" text: "Font Family"

View File

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

View File

@@ -11,6 +11,8 @@ Item {
property var parentScreen: null property var parentScreen: null
property real widgetThickness: 30 property real widgetThickness: 30
property real barThickness: 48 property real barThickness: 48
property string pluginId: ""
property var pluginService: null
property Component horizontalBarPill: null property Component horizontalBarPill: null
property Component verticalBarPill: null property Component verticalBarPill: null
@@ -18,11 +20,42 @@ Item {
property real popoutWidth: 400 property real popoutWidth: 400
property real popoutHeight: 400 property real popoutHeight: 400
property var pluginData: ({})
readonly property bool isVertical: axis?.isVertical ?? false readonly property bool isVertical: axis?.isVertical ?? false
readonly property bool hasHorizontalPill: horizontalBarPill !== null readonly property bool hasHorizontalPill: horizontalBarPill !== null
readonly property bool hasVerticalPill: verticalBarPill !== null readonly property bool hasVerticalPill: verticalBarPill !== null
readonly property bool hasPopout: popoutContent !== 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) width: isVertical ? (hasVerticalPill ? verticalPill.width : 0) : (hasHorizontalPill ? horizontalPill.width : 0)
height: isVertical ? (hasVerticalPill ? verticalPill.height : 0) : (hasHorizontalPill ? horizontalPill.height : 0) height: isVertical ? (hasVerticalPill ? verticalPill.height : 0) : (hasHorizontalPill ? horizontalPill.height : 0)
@@ -60,6 +93,12 @@ Item {
} }
} }
function closePopout() {
if (pluginPopout) {
pluginPopout.close()
}
}
PluginPopout { PluginPopout {
id: pluginPopout id: pluginPopout
contentWidth: root.popoutWidth contentWidth: root.popoutWidth

View File

@@ -62,53 +62,24 @@ DankPopout {
Column { Column {
id: popoutColumn id: popoutColumn
width: parent.width - Theme.spacingL * 2 width: parent.width - Theme.spacingS * 2
anchors.left: parent.left height: parent.height - Theme.spacingS * 2
anchors.top: parent.top x: Theme.spacingS
anchors.margins: Theme.spacingL y: Theme.spacingS
spacing: Theme.spacingL spacing: Theme.spacingS
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()
}
}
}
}
Loader { Loader {
id: popoutContent id: popoutContent
width: parent.width width: parent.width
sourceComponent: root.pluginContent 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 property var pluginService: null
default property alias content: settingsColumn.children default property alias content: settingsColumn.children
signal settingChanged()
implicitHeight: hasPermission ? settingsColumn.implicitHeight : errorText.implicitHeight implicitHeight: hasPermission ? settingsColumn.implicitHeight : errorText.implicitHeight
height: implicitHeight height: implicitHeight
readonly property bool hasPermission: pluginService && pluginService.hasPermission ? pluginService.hasPermission(pluginId, "settings_write") : true 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) { function saveValue(key, value) {
if (!pluginService) { if (!pluginService) {
return return
@@ -25,6 +38,7 @@ Item {
} }
if (pluginService.savePluginData) { if (pluginService.savePluginData) {
pluginService.savePluginData(pluginId, key, value) 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 width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
function loadValue() {
const settings = findSettings()
if (settings && settings.pluginService) {
value = settings.loadValue(settingKey, defaultValue)
}
}
Component.onCompleted: {
loadValue()
}
readonly property var optionLabels: { readonly property var optionLabels: {
const labels = [] const labels = []
for (let i = 0; i < options.length; i++) { for (let i = 0; i < options.length; i++) {
@@ -49,13 +60,6 @@ Column {
return map return map
} }
Component.onCompleted: {
const settings = findSettings()
if (settings) {
value = settings.loadValue(settingKey, defaultValue)
}
}
onValueChanged: { onValueChanged: {
const settings = findSettings() const settings = findSettings()
if (settings) { if (settings) {
@@ -74,40 +78,14 @@ Column {
return null return null
} }
Row { DankDropdown {
width: parent.width width: parent.width
spacing: Theme.spacingM text: root.label
description: root.description
Column { currentValue: root.valueToLabel[root.value] || root.value
width: parent.width * 0.4 options: root.optionLabels
spacing: Theme.spacingXS onValueChanged: newValue => {
anchors.verticalCenter: parent.verticalCenter root.value = root.labelToValue[newValue] || newValue
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
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 width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Component.onCompleted: { function loadValue() {
const settings = findSettings() const settings = findSettings()
if (settings) { if (settings && settings.pluginService) {
value = settings.loadValue(settingKey, defaultValue) value = settings.loadValue(settingKey, defaultValue)
} }
} }
Component.onCompleted: {
loadValue()
}
onValueChanged: { onValueChanged: {
const settings = findSettings() const settings = findSettings()
if (settings) { if (settings) {
@@ -69,6 +73,7 @@ Column {
rightIcon: root.rightIcon rightIcon: root.rightIcon
unit: root.unit unit: root.unit
wheelEnabled: false wheelEnabled: false
thumbOutlineColor: Theme.surfaceContainerHighest
onSliderValueChanged: newValue => { onSliderValueChanged: newValue => {
root.value = newValue root.value = newValue
} }

View File

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

View File

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

View File

@@ -10,14 +10,6 @@ Item {
property string expandedPluginId: "" 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 { DankFlickable {
anchors.fill: parent anchors.fill: parent
@@ -186,9 +178,6 @@ Item {
property bool hasSettings: pluginData && pluginData.settings !== undefined && pluginData.settings !== "" property bool hasSettings: pluginData && pluginData.settings !== undefined && pluginData.settings !== ""
property bool isExpanded: pluginsTab.expandedPluginId === pluginId 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) color: pluginMouseArea.containsMouse ? Theme.surfacePressed : (isExpanded ? Theme.surfaceContainerHighest : Theme.surfaceContainerHigh)
border.width: 0 border.width: 0
@@ -200,15 +189,8 @@ Item {
hoverEnabled: true hoverEnabled: true
cursorShape: pluginDelegate.hasSettings ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: pluginDelegate.hasSettings ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: { onClicked: {
console.log("Plugin clicked:", pluginDelegate.pluginId, "hasSettings:", pluginDelegate.hasSettings, "isLoaded:", PluginService.isPluginLoaded(pluginDelegate.pluginId))
if (pluginDelegate.hasSettings) { if (pluginDelegate.hasSettings) {
if (pluginsTab.expandedPluginId === pluginDelegate.pluginId) { pluginsTab.expandedPluginId = pluginsTab.expandedPluginId === pluginDelegate.pluginId ? "" : pluginDelegate.pluginId
console.log("Collapsing plugin:", pluginDelegate.pluginId)
pluginsTab.expandedPluginId = ""
} else {
console.log("Expanding plugin:", pluginDelegate.pluginId)
pluginsTab.expandedPluginId = pluginDelegate.pluginId
}
} }
} }
} }
@@ -234,7 +216,7 @@ Item {
} }
Column { 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 spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -267,30 +249,69 @@ Item {
} }
} }
DankToggle { Row {
id: pluginToggle id: toggleRow
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
checked: PluginService.isPluginLoaded(pluginDelegate.pluginId) spacing: Theme.spacingXS
onToggled: isChecked => {
const currentPluginId = pluginDelegate.pluginId
const currentPluginName = pluginDelegate.pluginName
if (isChecked) { Rectangle {
if (PluginService.enablePlugin(currentPluginId)) { width: 28
ToastService.showInfo("Plugin enabled: " + currentPluginName) height: 28
} else { radius: 14
ToastService.showError("Failed to enable plugin: " + currentPluginName) color: reloadArea.containsMouse ? Theme.surfaceContainerHighest : "transparent"
checked = false 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
}
} }
} else { }
if (PluginService.disablePlugin(currentPluginId)) { }
ToastService.showInfo("Plugin disabled: " + currentPluginName)
if (pluginDelegate.isExpanded) { DankToggle {
expandedPluginId = "" id: pluginToggle
anchors.verticalCenter: parent.verticalCenter
checked: PluginService.isPluginLoaded(pluginDelegate.pluginId)
onToggled: isChecked => {
const currentPluginId = pluginDelegate.pluginId
const currentPluginName = pluginDelegate.pluginName
if (isChecked) {
if (PluginService.enablePlugin(currentPluginId)) {
ToastService.showInfo("Plugin enabled: " + currentPluginName)
} else {
ToastService.showError("Failed to enable plugin: " + currentPluginName)
checked = false
} }
} else { } else {
ToastService.showError("Failed to disable plugin: " + currentPluginName) if (PluginService.disablePlugin(currentPluginId)) {
checked = true ToastService.showInfo("Plugin disabled: " + currentPluginName)
if (pluginDelegate.isExpanded) {
expandedPluginId = ""
}
} else {
ToastService.showError("Failed to disable plugin: " + currentPluginName)
checked = true
}
} }
} }
} }
@@ -358,15 +379,8 @@ Item {
active: pluginDelegate.isExpanded && pluginDelegate.hasSettings && PluginService.isPluginLoaded(pluginDelegate.pluginId) active: pluginDelegate.isExpanded && pluginDelegate.hasSettings && PluginService.isPluginLoaded(pluginDelegate.pluginId)
asynchronous: false 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: { source: {
if (active && pluginDelegate.pluginSettingsPath) { if (active && pluginDelegate.pluginSettingsPath) {
console.log("Loading plugin settings from:", pluginDelegate.pluginSettingsPath)
var path = pluginDelegate.pluginSettingsPath var path = pluginDelegate.pluginSettingsPath
if (!path.startsWith("file://")) { if (!path.startsWith("file://")) {
path = "file://" + path path = "file://" + path
@@ -376,37 +390,9 @@ Item {
return "" 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: { onLoaded: {
if (item) { if (item && typeof PluginService !== "undefined") {
console.log("Plugin settings loaded for:", pluginDelegate.pluginId) item.pluginService = PluginService
if (typeof PluginService !== "undefined") {
console.log("Making PluginService available to plugin settings")
console.log("PluginService functions available:",
"savePluginData" in PluginService,
"loadPluginData" in PluginService)
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 { Connections {
target: PluginService target: PluginService
function onPluginLoaded() { function onPluginLoaded() {
pluginRepeater.model = PluginService.getAvailablePlugins() pluginRepeater.model = PluginService.getAvailablePlugins()
if (isReloading) {
isReloading = false
}
} }
function onPluginUnloaded() { function onPluginUnloaded() {
pluginRepeater.model = PluginService.getAvailablePlugins() pluginRepeater.model = PluginService.getAvailablePlugins()
if (pluginsTab.expandedPluginId !== "" && !PluginService.isPluginLoaded(pluginsTab.expandedPluginId)) { if (!isReloading && pluginsTab.expandedPluginId !== "" && !PluginService.isPluginLoaded(pluginsTab.expandedPluginId)) {
pluginsTab.expandedPluginId = "" pluginsTab.expandedPluginId = ""
} }
} }

View File

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

View File

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

View File

@@ -21,23 +21,23 @@ Popup {
filteredWidgets = allWidgets.slice() filteredWidgets = allWidgets.slice()
return return
} }
var filtered = [] var filtered = []
var query = searchQuery.toLowerCase() var query = searchQuery.toLowerCase()
for (var i = 0; i < allWidgets.length; i++) { for (var i = 0; i < allWidgets.length; i++) {
var widget = allWidgets[i] var widget = allWidgets[i]
var text = widget.text ? widget.text.toLowerCase() : "" var text = widget.text ? widget.text.toLowerCase() : ""
var description = widget.description ? widget.description.toLowerCase() : "" var description = widget.description ? widget.description.toLowerCase() : ""
var id = widget.id ? widget.id.toLowerCase() : "" var id = widget.id ? widget.id.toLowerCase() : ""
if (text.indexOf(query) !== -1 || if (text.indexOf(query) !== -1 ||
description.indexOf(query) !== -1 || description.indexOf(query) !== -1 ||
id.indexOf(query) !== -1) { id.indexOf(query) !== -1) {
filtered.push(widget) filtered.push(widget)
} }
} }
filteredWidgets = filtered filteredWidgets = filtered
selectedIndex = -1 selectedIndex = -1
keyboardNavigationActive = false keyboardNavigationActive = false

View File

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

View File

@@ -8,18 +8,13 @@ import qs.Modules.Plugins
PluginComponent { PluginComponent {
id: root 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 int currentIndex: 0
property var displayedEmojis: [] property var displayedEmojis: []
// Timer to cycle through emojis at the configured interval
Timer { Timer {
interval: root.cycleInterval interval: root.cycleInterval
running: true running: true
@@ -32,7 +27,6 @@ PluginComponent {
} }
} }
// Update the emojis shown in the bar when settings or index changes
function updateDisplayedEmojis() { function updateDisplayedEmojis() {
const maxToShow = Math.min(root.maxBarEmojis, root.enabledEmojis.length) const maxToShow = Math.min(root.maxBarEmojis, root.enabledEmojis.length)
let emojis = [] let emojis = []
@@ -51,58 +45,44 @@ PluginComponent {
onMaxBarEmojisChanged: updateDisplayedEmojis() onMaxBarEmojisChanged: updateDisplayedEmojis()
horizontalBarPill: Component { horizontalBarPill: Component {
StyledRect { Row {
width: emojiRow.implicitWidth + Theme.spacingM * 2 id: emojiRow
height: parent.widgetThickness spacing: Theme.spacingXS
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Row { Repeater {
id: emojiRow model: root.displayedEmojis
anchors.centerIn: parent StyledText {
spacing: Theme.spacingXS text: modelData
font.pixelSize: Theme.fontSizeLarge
Repeater {
model: root.displayedEmojis
StyledText {
text: modelData
font.pixelSize: Theme.fontSizeLarge
}
} }
} }
} }
} }
verticalBarPill: Component { verticalBarPill: Component {
StyledRect { Column {
width: parent.widgetThickness id: emojiColumn
height: emojiColumn.implicitHeight + Theme.spacingM * 2 spacing: Theme.spacingXS
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column { Repeater {
id: emojiColumn model: root.displayedEmojis
anchors.centerIn: parent StyledText {
spacing: Theme.spacingXS text: modelData
font.pixelSize: Theme.fontSizeMedium
Repeater { anchors.horizontalCenter: parent.horizontalCenter
model: root.displayedEmojis
StyledText {
text: modelData
font.pixelSize: Theme.fontSizeMedium
anchors.horizontalCenter: parent.horizontalCenter
}
} }
} }
} }
} }
popoutContent: Component { popoutContent: Component {
Item { PopoutComponent {
width: parent.width id: popoutColumn
height: parent.height
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: [ property var allEmojis: [
"😀", "😃", "😄", "😁", "😆", "😅", "🤣", "😂", "🙂", "🙃", "😀", "😃", "😄", "😁", "😆", "😅", "🤣", "😂", "🙂", "🙃",
"😉", "😊", "😇", "🥰", "😍", "🤩", "😘", "😗", "😚", "😙", "😉", "😊", "😇", "🥰", "😍", "🤩", "😘", "😗", "😚", "😙",
@@ -119,59 +99,43 @@ PluginComponent {
"👌", "🤌", "🤏", "👈", "👉", "👆", "👇", "☝️", "✋", "🤚" "👌", "🤌", "🤏", "👈", "👉", "👆", "👇", "☝️", "✋", "🤚"
] ]
Column { Item {
anchors.fill: parent width: parent.width
anchors.margins: Theme.spacingM implicitHeight: root.popoutHeight - popoutColumn.headerHeight - popoutColumn.detailsHeight - Theme.spacingXL
spacing: Theme.spacingM
StyledText { DankGridView {
text: "Click an emoji to copy it!" id: emojiGrid
font.pixelSize: Theme.fontSizeMedium anchors.horizontalCenter: parent.horizontalCenter
font.weight: Font.Medium width: Math.floor(parent.width / 50) * 50
color: Theme.surfaceText height: parent.height
}
DankFlickable {
width: parent.width
height: parent.height - parent.spacing - 30
contentWidth: emojiGrid.width
contentHeight: emojiGrid.height
clip: true clip: true
cellWidth: 50
cellHeight: 50
model: popoutColumn.allEmojis
Grid { delegate: StyledRect {
id: emojiGrid width: 45
width: parent.width - Theme.spacingM height: 45
columns: 8 radius: Theme.cornerRadius
spacing: Theme.spacingS color: emojiMouseArea.containsMouse ? Theme.surfaceContainerHighest : Theme.surfaceContainerHigh
border.width: 0
Repeater { StyledText {
model: allEmojis anchors.centerIn: parent
text: modelData
font.pixelSize: Theme.fontSizeXLarge
}
StyledRect { MouseArea {
width: 45 id: emojiMouseArea
height: 45 anchors.fill: parent
radius: Theme.cornerRadius hoverEnabled: true
color: emojiMouseArea.containsMouse ? Theme.surfaceContainerHighest : Theme.surfaceContainerHigh cursorShape: Qt.PointingHandCursor
border.width: 0
StyledText { onClicked: {
anchors.centerIn: parent Quickshell.execDetached(["sh", "-c", "echo -n '" + modelData + "' | wl-copy"])
text: modelData ToastService.showInfo("Copied " + modelData + " to clipboard")
font.pixelSize: Theme.fontSizeXLarge popoutColumn.closePopout()
}
MouseArea {
id: emojiMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
Quickshell.execDetached(["sh", "-c", "echo -n '" + modelData + "' | wl-copy"])
ToastService.show("Copied " + modelData + " to clipboard", 2000)
root.closePopout()
}
}
} }
} }
} }

View File

@@ -1,6 +1,6 @@
# Emoji Cycler Plugin # 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 ## Features

View File

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

View File

@@ -121,15 +121,21 @@ PluginComponent {
// Define popout content (optional) // Define popout content (optional)
popoutContent: Component { popoutContent: Component {
Column { PopoutComponent {
width: parent.width headerText: "My Plugin"
spacing: Theme.spacingM detailsText: "Optional description text goes here"
padding: Theme.spacingM showCloseButton: true
StyledText { // Your popout content goes here
text: "Popout Content" Column {
font.pixelSize: Theme.fontSizeLarge width: parent.width
color: Theme.surfaceText spacing: Theme.spacingM
StyledText {
text: "Popout Content"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
}
} }
} }
} }
@@ -160,6 +166,42 @@ The PluginComponent automatically handles:
- Proper positioning and anchoring - Proper positioning and anchoring
- Theme integration - 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 ### Settings Component
Optional settings UI loaded inline in the PluginsTab accordion interface. Use the simplified settings API with auto-storage components: 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> </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 ## Screenshots
@@ -122,6 +122,8 @@ curl -fsSL https://install.danklinux.com | sh
- Configure bluetooth, wifi, and audio input+output devices. - Configure bluetooth, wifi, and audio input+output devices.
- A lock screen - A lock screen
- Idle monitoring - configure auto lock, screen off, suspend, and hibernate with different knobs for battery + AC power. - 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* **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 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 ### Calendar Setup
Sync your caldev compatible calendar (Google, Office365, etc.) for dashboard integration: Sync your caldev compatible calendar (Google, Office365, etc.) for dashboard integration:

View File

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

View File

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