1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-28 23:42:51 -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

@@ -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,35 +78,10 @@ Column {
return null 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 { DankDropdown {
width: parent.width * 0.6 - Theme.spacingM width: parent.width
anchors.verticalCenter: parent.verticalCenter text: root.label
description: root.description
currentValue: root.valueToLabel[root.value] || root.value currentValue: root.valueToLabel[root.value] || root.value
options: root.optionLabels options: root.optionLabels
onValueChanged: newValue => { onValueChanged: newValue => {
@@ -110,4 +89,3 @@ Column {
} }
} }
} }
}

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,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 { DankToggle {
id: pluginToggle id: pluginToggle
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -296,6 +316,7 @@ Item {
} }
} }
} }
}
StyledText { StyledText {
width: parent.width width: parent.width
@@ -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)
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 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

@@ -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,15 +45,8 @@ PluginComponent {
onMaxBarEmojisChanged: updateDisplayedEmojis() onMaxBarEmojisChanged: updateDisplayedEmojis()
horizontalBarPill: Component { horizontalBarPill: Component {
StyledRect {
width: emojiRow.implicitWidth + Theme.spacingM * 2
height: parent.widgetThickness
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Row { Row {
id: emojiRow id: emojiRow
anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
Repeater { Repeater {
@@ -71,18 +58,10 @@ PluginComponent {
} }
} }
} }
}
verticalBarPill: Component { verticalBarPill: Component {
StyledRect {
width: parent.widgetThickness
height: emojiColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column { Column {
id: emojiColumn id: emojiColumn
anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
Repeater { Repeater {
@@ -95,14 +74,15 @@ PluginComponent {
} }
} }
} }
}
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,35 +99,21 @@ PluginComponent {
"👌", "🤌", "🤏", "👈", "👉", "👆", "👇", "☝️", "✋", "🤚" "👌", "🤌", "🤏", "👈", "👉", "👆", "👇", "☝️", "✋", "🤚"
] ]
Column { Item {
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 {
width: parent.width width: parent.width
height: parent.height - parent.spacing - 30 implicitHeight: root.popoutHeight - popoutColumn.headerHeight - popoutColumn.detailsHeight - Theme.spacingXL
contentWidth: emojiGrid.width
contentHeight: emojiGrid.height
clip: true
Grid { DankGridView {
id: emojiGrid id: emojiGrid
width: parent.width - Theme.spacingM anchors.horizontalCenter: parent.horizontalCenter
columns: 8 width: Math.floor(parent.width / 50) * 50
spacing: Theme.spacingS height: parent.height
clip: true
cellWidth: 50
cellHeight: 50
model: popoutColumn.allEmojis
Repeater { delegate: StyledRect {
model: allEmojis
StyledRect {
width: 45 width: 45
height: 45 height: 45
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -168,10 +134,8 @@ PluginComponent {
onClicked: { onClicked: {
Quickshell.execDetached(["sh", "-c", "echo -n '" + modelData + "' | wl-copy"]) Quickshell.execDetached(["sh", "-c", "echo -n '" + modelData + "' | wl-copy"])
ToastService.show("Copied " + modelData + " to clipboard", 2000) ToastService.showInfo("Copied " + modelData + " to clipboard")
root.closePopout() popoutColumn.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,10 +121,15 @@ PluginComponent {
// Define popout content (optional) // Define popout content (optional)
popoutContent: Component { popoutContent: Component {
PopoutComponent {
headerText: "My Plugin"
detailsText: "Optional description text goes here"
showCloseButton: true
// Your popout content goes here
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
padding: Theme.spacingM
StyledText { StyledText {
text: "Popout Content" text: "Popout Content"
@@ -133,6 +138,7 @@ PluginComponent {
} }
} }
} }
}
// Popout dimensions (required if popoutContent is set) // Popout dimensions (required if popoutContent is set)
popoutWidth: 400 popoutWidth: 400
@@ -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
} dropdownMenu.open()
if (root.openUpwards || root.alignPopupRight) {
popup.open()
Qt.callLater(() => {
if (root.openUpwards) {
const pos = dropdown.mapToItem(Overlay.overlay, 0, 0) 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) { if (root.alignPopupRight) {
popup.x = pos.x + dropdown.width - popup.width dropdownMenu.x = pos.x + dropdown.width - popupWidth
} else { } 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 { } else {
const pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4)
if (root.alignPopupRight) { if (root.alignPopupRight) {
popup.x = pos.x + dropdown.width - popup.width dropdownMenu.x = pos.x + dropdown.width - popupWidth
} else { } 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 { if (root.enableFuzzySearch && searchField.visible) {
const pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4) searchField.forceActiveFocus()
popup.x = pos.x - (root.popupWidthOffset / 2)
popup.y = pos.y
popup.open()
} }
} }
} }
@@ -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,36 +141,30 @@ 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 {
id: popupLoader
property bool recreateFlag: root.forceRecreate
active: true
onRecreateFlagChanged: {
active = false
active = true
}
sourceComponent: Component {
Popup { Popup {
id: dropdownMenu id: dropdownMenu
@@ -239,18 +215,11 @@ Rectangle {
} }
parent: Overlay.overlay 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) height: Math.min(root.maxPopupHeight, (root.enableFuzzySearch ? 54 : 0) + Math.min(filteredOptions.length, 10) * 36 + 16)
padding: 0 padding: 0
modal: true modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
onOpened: {
searchQuery = ""
updateFilteredOptions()
if (root.enableFuzzySearch && searchField.visible) {
searchField.forceActiveFocus()
}
}
background: Rectangle { background: Rectangle {
color: "transparent" color: "transparent"
@@ -258,10 +227,18 @@ Rectangle {
contentItem: Rectangle { contentItem: Rectangle {
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
border.color: Theme.primarySelected border.color: Theme.primary
border.width: 1 border.width: 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowBlur: 0.4
shadowColor: Theme.shadowStrong
shadowVerticalOffset: 4
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
@@ -273,7 +250,7 @@ Rectangle {
height: 42 height: 42
visible: root.enableFuzzySearch visible: root.enableFuzzySearch
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceVariantAlpha color: Theme.surfaceContainerHigh
DankTextField { DankTextField {
id: searchField id: searchField
@@ -281,29 +258,29 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.margins: 1 anchors.margins: 1
placeholderText: "Search..." placeholderText: "Search..."
text: searchQuery text: dropdownMenu.searchQuery
topPadding: Theme.spacingS topPadding: Theme.spacingS
bottomPadding: Theme.spacingS bottomPadding: Theme.spacingS
onTextChanged: { onTextChanged: {
searchQuery = text dropdownMenu.searchQuery = text
updateFilteredOptions() dropdownMenu.updateFilteredOptions()
} }
Keys.onDownPressed: selectNext() Keys.onDownPressed: dropdownMenu.selectNext()
Keys.onUpPressed: selectPrevious() Keys.onUpPressed: dropdownMenu.selectPrevious()
Keys.onReturnPressed: selectCurrent() Keys.onReturnPressed: dropdownMenu.selectCurrent()
Keys.onEnterPressed: selectCurrent() Keys.onEnterPressed: dropdownMenu.selectCurrent()
Keys.onPressed: event => { Keys.onPressed: event => {
if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) { if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
selectNext() dropdownMenu.selectNext()
event.accepted = true event.accepted = true
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) { } else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
selectPrevious() dropdownMenu.selectPrevious()
event.accepted = true event.accepted = true
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) { } else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
selectNext() dropdownMenu.selectNext()
event.accepted = true event.accepted = true
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) { } else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
selectPrevious() dropdownMenu.selectPrevious()
event.accepted = true event.accepted = true
} }
} }
@@ -319,12 +296,10 @@ Rectangle {
DankListView { DankListView {
id: listView id: listView
property var popupRef: dropdownMenu
width: parent.width width: parent.width
height: parent.height - (root.enableFuzzySearch ? searchContainer.height + Theme.spacingXS : 0) height: parent.height - (root.enableFuzzySearch ? searchContainer.height + Theme.spacingXS : 0)
clip: true clip: true
model: filteredOptions model: dropdownMenu.filteredOptions
spacing: 2 spacing: 2
interactive: true interactive: true
@@ -336,7 +311,7 @@ Rectangle {
flickableDirection: Flickable.VerticalFlick flickableDirection: Flickable.VerticalFlick
delegate: Rectangle { delegate: Rectangle {
property bool isSelected: selectedIndex === index property bool isSelected: dropdownMenu.selectedIndex === index
property bool isCurrentValue: root.currentValue === modelData property bool isCurrentValue: root.currentValue === modelData
property int optionIndex: root.options.indexOf(modelData) property int optionIndex: root.options.indexOf(modelData)
@@ -354,7 +329,7 @@ Rectangle {
DankIcon { DankIcon {
name: optionIndex >= 0 && root.optionIcons.length > optionIndex ? root.optionIcons[optionIndex] : "" name: optionIndex >= 0 && root.optionIcons.length > optionIndex ? root.optionIcons[optionIndex] : ""
size: 18 size: 18
color: isCurrentValue ? Theme.primary : Theme.surfaceVariantText color: isCurrentValue ? Theme.primary : Theme.surfaceText
visible: name !== "" visible: name !== ""
} }
@@ -379,9 +354,7 @@ Rectangle {
onClicked: { onClicked: {
root.currentValue = modelData root.currentValue = modelData
root.valueChanged(modelData) root.valueChanged(modelData)
listView.popupRef.close() dropdownMenu.close()
}
}
} }
} }
} }