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

plugins: support for multiple widgets per-plugin (variants)

This commit is contained in:
bbedward
2025-10-06 12:33:58 -04:00
parent 11a1af89f4
commit 89793d2d62
10 changed files with 676 additions and 14 deletions

View File

@@ -87,8 +87,18 @@ Loader {
}
if (item.pluginService !== undefined) {
var parts = widgetId.split(":")
var pluginId = parts[0]
var variantId = parts.length > 1 ? parts[1] : null
if (item.pluginId !== undefined) {
item.pluginId = widgetId
item.pluginId = pluginId
}
if (item.variantId !== undefined) {
item.variantId = variantId
}
if (item.variantData !== undefined && variantId) {
item.variantData = PluginService.getPluginVariantData(pluginId, variantId)
}
item.pluginService = PluginService
}
@@ -134,8 +144,11 @@ Loader {
return componentMap[widgetId]
}
var parts = widgetId.split(":")
var pluginId = parts[0]
let pluginMap = PluginService.getWidgetComponents()
return pluginMap[widgetId] || null
return pluginMap[pluginId] || null
}
function getWidgetVisible(widgetId, dgopAvailable) {

View File

@@ -34,6 +34,7 @@ Item {
signal ccWidgetExpanded()
property var pluginData: ({})
property var variants: []
readonly property bool isVertical: axis?.isVertical ?? false
readonly property bool hasHorizontalPill: horizontalBarPill !== null
@@ -64,9 +65,32 @@ Item {
function loadPluginData() {
if (!pluginService || !pluginId) {
pluginData = {}
variants = []
return
}
pluginData = SettingsData.getPluginSettingsForPlugin(pluginId)
variants = pluginService.getPluginVariants(pluginId)
}
function createVariant(variantName, variantConfig) {
if (!pluginService || !pluginId) {
return null
}
return pluginService.createPluginVariant(pluginId, variantName, variantConfig)
}
function removeVariant(variantId) {
if (!pluginService || !pluginId) {
return
}
pluginService.removePluginVariant(pluginId, variantId)
}
function updateVariant(variantId, variantConfig) {
if (!pluginService || !pluginId) {
return
}
pluginService.updatePluginVariant(pluginId, variantId, variantConfig)
}
width: isVertical ? (hasVerticalPill ? verticalPill.width : 0) : (hasHorizontalPill ? horizontalPill.width : 0)

View File

@@ -12,13 +12,20 @@ Item {
signal settingChanged()
property var variants: []
implicitHeight: hasPermission ? settingsColumn.implicitHeight : errorText.implicitHeight
height: implicitHeight
readonly property bool hasPermission: pluginService && pluginService.hasPermission ? pluginService.hasPermission(pluginId, "settings_write") : true
Component.onCompleted: {
loadVariants()
}
onPluginServiceChanged: {
if (pluginService) {
loadVariants()
for (let i = 0; i < settingsColumn.children.length; i++) {
const child = settingsColumn.children[i]
if (child.loadValue) {
@@ -28,6 +35,44 @@ Item {
}
}
Connections {
target: pluginService
function onPluginDataChanged(changedPluginId) {
if (changedPluginId === pluginId) {
loadVariants()
}
}
}
function loadVariants() {
if (!pluginService || !pluginId) {
variants = []
return
}
variants = pluginService.getPluginVariants(pluginId)
}
function createVariant(variantName, variantConfig) {
if (!pluginService || !pluginId) {
return null
}
return pluginService.createPluginVariant(pluginId, variantName, variantConfig)
}
function removeVariant(variantId) {
if (!pluginService || !pluginId) {
return
}
pluginService.removePluginVariant(pluginId, variantId)
}
function updateVariant(variantId, variantConfig) {
if (!pluginService || !pluginId) {
return
}
pluginService.updatePluginVariant(pluginId, variantId, variantConfig)
}
function saveValue(key, value) {
if (!pluginService) {
return

View File

@@ -190,18 +190,16 @@ Item {
"enabled": SystemUpdateService.distributionSupported
}]
// Add all available plugins (loaded and unloaded)
var allPlugins = PluginService.getAvailablePlugins()
for (var i = 0; i < allPlugins.length; i++) {
var plugin = allPlugins[i]
var isLoaded = PluginService.isPluginLoaded(plugin.id)
var allPluginVariants = PluginService.getAllPluginVariants()
for (var i = 0; i < allPluginVariants.length; i++) {
var variant = allPluginVariants[i]
coreWidgets.push({
"id": plugin.id,
"text": plugin.name,
"description": plugin.description || "Plugin widget",
"icon": plugin.icon || "extension",
"enabled": isLoaded,
"warning": !isLoaded ? "Plugin is disabled - enable in Plugins settings to use" : undefined
"id": variant.fullId,
"text": variant.name,
"description": variant.description,
"icon": variant.icon,
"enabled": variant.loaded,
"warning": !variant.loaded ? "Plugin is disabled - enable in Plugins settings to use" : undefined
})
}

View File

@@ -378,6 +378,7 @@ Item {
anchors.margins: Theme.spacingL
active: pluginDelegate.isExpanded && pluginDelegate.hasSettings && PluginService.isPluginLoaded(pluginDelegate.pluginId)
asynchronous: false
focus: true
source: {
if (active && pluginDelegate.pluginSettingsPath) {
@@ -394,9 +395,13 @@ Item {
if (item && typeof PluginService !== "undefined") {
item.pluginService = PluginService
}
if (item && typeof PopoutService !== "undefined") {
if (item && typeof PopoutService !== "undefined" && "popoutService" in item) {
item.popoutService = PopoutService
}
if (item) {
item.focus = true
item.forceActiveFocus()
}
}
}

View File

@@ -0,0 +1,116 @@
# Example with Variants Plugin
This plugin demonstrates the dynamic variant system for DankMaterialShell plugins.
## What are Variants?
Variants allow a single plugin to create multiple widget instances with different configurations. Each variant appears as a separate widget option in the Bar Settings "Add Widget" menu.
## How It Works
### Plugin Architecture
1. **Single Component**: The plugin defines one widget component (`VariantWidget.qml`)
2. **Variant Data**: Each variant stores its configuration in plugin settings
3. **Dynamic Creation**: Variants are created through the plugin's settings UI
4. **Widget ID Format**: Variants use the format `pluginId:variantId` (e.g., `exampleVariants:variant_1234567890`)
### Widget Properties
Each variant widget receives:
- `pluginService`: Reference to PluginService
- `pluginId`: The base plugin ID (e.g., "exampleVariants")
- `variantId`: The specific variant ID (e.g., "variant_1234567890")
- `variantData`: The variant's configuration object
### Variant Configuration
This example stores:
```javascript
{
id: "variant_1234567890",
name: "My Variant",
text: "Display Text",
icon: "star",
color: "#FF5722"
}
```
## Creating Your Own Variant Plugin
### 1. Widget Component
Create a widget that accepts variant data:
```qml
Rectangle {
property var pluginService: null
property string pluginId: ""
property string variantId: ""
property var variantData: null
// Use variantData for configuration
property string displayText: variantData?.text || "Default"
}
```
### 2. Settings Component
Provide UI to manage variants:
```qml
FocusScope {
property var pluginService: null
// Create variant
pluginService.createPluginVariant(pluginId, variantName, variantConfig)
// Remove variant
pluginService.removePluginVariant(pluginId, variantId)
// Update variant
pluginService.updatePluginVariant(pluginId, variantId, variantConfig)
// Get all variants
var variants = pluginService.getPluginVariants(pluginId)
}
```
### 3. Plugin Manifest
Standard plugin manifest - no special configuration needed:
```json
{
"id": "yourPlugin",
"name": "Your Plugin",
"component": "./Widget.qml",
"settings": "./Settings.qml"
}
```
## Use Cases
- **Script Runner**: Multiple variants running different scripts
- **System Monitors**: Different monitoring targets (CPU, GPU, Network)
- **Quick Launchers**: Different apps or commands per variant
- **Custom Indicators**: Different data sources or APIs
- **Time Zones**: Multiple clocks for different time zones
## API Reference
### PluginService Functions
- `getPluginVariants(pluginId)`: Get all variants for a plugin
- `getAllPluginVariants()`: Get all variants across all plugins
- `createPluginVariant(pluginId, name, config)`: Create new variant
- `removePluginVariant(pluginId, variantId)`: Delete variant
- `updatePluginVariant(pluginId, variantId, config)`: Update variant
- `getPluginVariantData(pluginId, variantId)`: Get specific variant data
### Widget Properties
- `pluginId`: Base plugin identifier
- `variantId`: Variant identifier (null if no variant)
- `variantData`: Variant configuration object
- `pluginService`: PluginService reference

View File

@@ -0,0 +1,303 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginSettings {
id: root
pluginId: "exampleVariants"
onVariantsChanged: {
variantsModel.clear()
for (var i = 0; i < variants.length; i++) {
variantsModel.append(variants[i])
}
}
StyledText {
width: parent.width
text: "Variant Manager"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
width: parent.width
text: "Create multiple widget variants with different text, icons, and colors"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
StyledRect {
width: parent.width
height: addVariantColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: addVariantColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
StyledText {
text: "Add New Variant"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
Row {
width: parent.width
spacing: Theme.spacingM
Column {
width: (parent.width - Theme.spacingM * 2) / 3
spacing: Theme.spacingXS
StyledText {
text: "Name"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: nameField
width: parent.width
placeholderText: "Variant Name"
}
}
Column {
width: (parent.width - Theme.spacingM * 2) / 3
spacing: Theme.spacingXS
StyledText {
text: "Icon"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: iconField
width: parent.width
placeholderText: "star"
}
}
Column {
width: (parent.width - Theme.spacingM * 2) / 3
spacing: Theme.spacingXS
StyledText {
text: "Text"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: textField
width: parent.width
placeholderText: "Display Text"
}
}
}
DankButton {
text: "Create Variant"
iconName: "add"
onClicked: {
if (!nameField.text) {
ToastService.showError("Please enter a variant name")
return
}
var variantConfig = {
text: textField.text || nameField.text,
icon: iconField.text || "widgets"
}
createVariant(nameField.text, variantConfig)
ToastService.showInfo("Variant created: " + nameField.text)
nameField.text = ""
iconField.text = ""
textField.text = ""
}
}
}
}
StyledRect {
width: parent.width
height: Math.max(200, variantsColumn.implicitHeight + Theme.spacingL * 2)
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: variantsColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
StyledText {
text: "Existing Variants"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
ListView {
width: parent.width
height: Math.max(100, contentHeight)
clip: true
spacing: Theme.spacingXS
model: ListModel {
id: variantsModel
}
delegate: StyledRect {
required property var model
width: ListView.view.width
height: variantRow.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: variantMouseArea.containsMouse ? Theme.surfaceContainerHighest : Theme.surfaceContainer
Row {
id: variantRow
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
Item {
width: Theme.iconSize
height: Theme.iconSize
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: model.icon || "widgets"
size: Theme.iconSize
color: Theme.surfaceText
width: Theme.iconSize
height: Theme.iconSize
clip: true
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
width: parent.width - Theme.iconSize - deleteButton.width - Theme.spacingM * 4
StyledText {
text: model.name || "Unnamed"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
elide: Text.ElideRight
}
StyledText {
text: "Text: " + (model.text || "") + " | Icon: " + (model.icon || "")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
elide: Text.ElideRight
}
}
Rectangle {
id: deleteButton
width: 32
height: 32
radius: 16
color: deleteArea.containsMouse ? Theme.error : "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: "delete"
size: 16
color: deleteArea.containsMouse ? Theme.onError : Theme.surfaceVariantText
}
MouseArea {
id: deleteArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
removeVariant(model.id)
ToastService.showInfo("Variant removed: " + model.name)
}
}
}
}
MouseArea {
id: variantMouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
}
}
StyledText {
anchors.centerIn: parent
text: "No variants created yet"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
visible: variantsModel.count === 0
}
}
}
}
StyledRect {
width: parent.width
height: instructionsColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surface
Column {
id: instructionsColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
spacing: Theme.spacingM
DankIcon {
name: "info"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "How to Use Variants"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: "1. Create variants with different names, icons, and text\n2. Go to Bar Settings and click 'Add Widget'\n3. Each variant will appear as a separate widget option\n4. Add variants to your bar just like any other widget"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
lineHeight: 1.4
}
}
}
}

View File

@@ -0,0 +1,57 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
property string variantId: ""
property var variantData: null
property string displayText: variantData?.text || "Default Text"
property string displayIcon: variantData?.icon || "widgets"
horizontalBarPill: Component {
Row {
spacing: 3
DankIcon {
name: root.displayIcon
size: Theme.iconSize - 8
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: root.displayText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
}
verticalBarPill: Component {
Column {
spacing: 1
DankIcon {
name: root.displayIcon
size: Theme.iconSize - 8
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: root.displayText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}

View File

@@ -0,0 +1,14 @@
{
"id": "exampleVariants",
"name": "Example with Variants",
"description": "Demonstrates dynamic variant creation for plugins",
"version": "1.0.0",
"author": "DMS",
"icon": "widgets",
"component": "./VariantWidget.qml",
"settings": "./VariantSettings.qml",
"permissions": [
"settings_read",
"settings_write"
]
}

View File

@@ -333,6 +333,93 @@ Singleton {
return result
}
function getPluginVariants(pluginId) {
var plugin = availablePlugins[pluginId]
if (!plugin) {
return []
}
var variants = SettingsData.getPluginSetting(pluginId, "variants", [])
return variants
}
function getAllPluginVariants() {
var result = []
for (var pluginId in availablePlugins) {
var plugin = availablePlugins[pluginId]
if (plugin.type !== "widget") {
continue
}
var variants = getPluginVariants(pluginId)
if (variants.length === 0) {
result.push({
pluginId: pluginId,
variantId: null,
fullId: pluginId,
name: plugin.name,
icon: plugin.icon || "extension",
description: plugin.description || "Plugin widget",
loaded: plugin.loaded
})
} else {
for (var i = 0; i < variants.length; i++) {
var variant = variants[i]
result.push({
pluginId: pluginId,
variantId: variant.id,
fullId: pluginId + ":" + variant.id,
name: plugin.name + " - " + variant.name,
icon: variant.icon || plugin.icon || "extension",
description: variant.description || plugin.description || "Plugin widget variant",
loaded: plugin.loaded
})
}
}
}
return result
}
function createPluginVariant(pluginId, variantName, variantConfig) {
var variants = getPluginVariants(pluginId)
var variantId = "variant_" + Date.now()
var newVariant = Object.assign({}, variantConfig, {
id: variantId,
name: variantName
})
variants.push(newVariant)
SettingsData.setPluginSetting(pluginId, "variants", variants)
pluginDataChanged(pluginId)
return variantId
}
function removePluginVariant(pluginId, variantId) {
var variants = getPluginVariants(pluginId)
var newVariants = variants.filter(function(v) { return v.id !== variantId })
SettingsData.setPluginSetting(pluginId, "variants", newVariants)
pluginDataChanged(pluginId)
}
function updatePluginVariant(pluginId, variantId, variantConfig) {
var variants = getPluginVariants(pluginId)
for (var i = 0; i < variants.length; i++) {
if (variants[i].id === variantId) {
variants[i] = Object.assign({}, variants[i], variantConfig)
break
}
}
SettingsData.setPluginSetting(pluginId, "variants", variants)
pluginDataChanged(pluginId)
}
function getPluginVariantData(pluginId, variantId) {
var variants = getPluginVariants(pluginId)
for (var i = 0; i < variants.length; i++) {
if (variants[i].id === variantId) {
return variants[i]
}
}
return null
}
function getLoadedPlugins() {
var result = []
for (var key in loadedPlugins) {