mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
plugins/desktop-widgets: create a new "desktop" widget plugin type
- Draggable per-monitor background layer widgets - Add basic dms version checks on plugins - Clock: built-in clock desktop plugin - dgop: built-in system monitor desktop plugin
This commit is contained in:
172
quickshell/PLUGINS/ExampleDesktopClock/DesktopClock.qml
Normal file
172
quickshell/PLUGINS/ExampleDesktopClock/DesktopClock.qml
Normal file
@@ -0,0 +1,172 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
|
||||
DesktopPluginComponent {
|
||||
id: root
|
||||
|
||||
minWidth: 120
|
||||
minHeight: 120
|
||||
|
||||
property bool showSeconds: pluginData.showSeconds ?? true
|
||||
property bool showDate: pluginData.showDate ?? true
|
||||
property string clockStyle: pluginData.clockStyle ?? "analog"
|
||||
property real backgroundOpacity: (pluginData.backgroundOpacity ?? 50) / 100
|
||||
|
||||
SystemClock {
|
||||
id: systemClock
|
||||
precision: root.showSeconds ? SystemClock.Seconds : SystemClock.Minutes
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
opacity: root.backgroundOpacity
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
sourceComponent: root.clockStyle === "digital" ? digitalClock : analogClock
|
||||
}
|
||||
|
||||
Component {
|
||||
id: analogClock
|
||||
|
||||
Item {
|
||||
id: analogClockRoot
|
||||
|
||||
property real clockSize: Math.min(width, height) - (root.showDate ? 30 : 0)
|
||||
|
||||
Item {
|
||||
id: clockFace
|
||||
width: analogClockRoot.clockSize
|
||||
height: analogClockRoot.clockSize
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: 12
|
||||
|
||||
Rectangle {
|
||||
required property int index
|
||||
property real markAngle: index * 30
|
||||
property real markRadius: clockFace.width / 2 - 8
|
||||
|
||||
x: clockFace.width / 2 + markRadius * Math.sin(markAngle * Math.PI / 180) - width / 2
|
||||
y: clockFace.height / 2 - markRadius * Math.cos(markAngle * Math.PI / 180) - height / 2
|
||||
width: index % 3 === 0 ? 8 : 4
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: index % 3 === 0 ? Theme.primary : Theme.outlineVariant
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: hourHand
|
||||
property int hours: systemClock.date?.getHours() % 12 ?? 0
|
||||
property int minutes: systemClock.date?.getMinutes() ?? 0
|
||||
|
||||
x: clockFace.width / 2 - width / 2
|
||||
y: clockFace.height / 2 - height + 4
|
||||
width: 6
|
||||
height: clockFace.height * 0.25
|
||||
radius: 3
|
||||
color: Theme.primary
|
||||
antialiasing: true
|
||||
transformOrigin: Item.Bottom
|
||||
rotation: (hours + minutes / 60) * 30
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: minuteHand
|
||||
property int minutes: systemClock.date?.getMinutes() ?? 0
|
||||
property int seconds: systemClock.date?.getSeconds() ?? 0
|
||||
|
||||
x: clockFace.width / 2 - width / 2
|
||||
y: clockFace.height / 2 - height + 4
|
||||
width: 4
|
||||
height: clockFace.height * 0.35
|
||||
radius: 2
|
||||
color: Theme.onSurface
|
||||
antialiasing: true
|
||||
transformOrigin: Item.Bottom
|
||||
rotation: (minutes + seconds / 60) * 6
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: secondHand
|
||||
visible: root.showSeconds
|
||||
property int seconds: systemClock.date?.getSeconds() ?? 0
|
||||
|
||||
x: clockFace.width / 2 - width / 2
|
||||
y: clockFace.height / 2 - height + 4
|
||||
width: 2
|
||||
height: clockFace.height * 0.4
|
||||
radius: 1
|
||||
color: Theme.error
|
||||
antialiasing: true
|
||||
transformOrigin: Item.Bottom
|
||||
rotation: seconds * 6
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 10
|
||||
height: 10
|
||||
radius: 5
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: root.showDate
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Theme.spacingXS
|
||||
text: systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: digitalClock
|
||||
|
||||
Item {
|
||||
id: digitalRoot
|
||||
|
||||
property real timeFontSize: Math.min(width * 0.16, height * (root.showDate ? 0.4 : 0.5))
|
||||
property real dateFontSize: Math.max(Theme.fontSizeSmall, timeFontSize * 0.35)
|
||||
|
||||
Text {
|
||||
id: timeText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: root.showDate ? -digitalRoot.dateFontSize * 0.8 : 0
|
||||
text: systemClock.date?.toLocaleTimeString(Qt.locale(), root.showSeconds ? "hh:mm:ss" : "hh:mm") ?? ""
|
||||
font.pixelSize: digitalRoot.timeFontSize
|
||||
font.weight: Font.Bold
|
||||
font.family: "monospace"
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
Text {
|
||||
id: dateText
|
||||
visible: root.showDate
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: timeText.bottom
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
text: systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? ""
|
||||
font.pixelSize: digitalRoot.dateFontSize
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginSettings {
|
||||
id: root
|
||||
pluginId: "exampleDesktopClock"
|
||||
|
||||
SelectionSetting {
|
||||
settingKey: "clockStyle"
|
||||
label: I18n.tr("Clock Style")
|
||||
options: [
|
||||
{
|
||||
label: I18n.tr("Analog"),
|
||||
value: "analog"
|
||||
},
|
||||
{
|
||||
label: I18n.tr("Digital"),
|
||||
value: "digital"
|
||||
}
|
||||
]
|
||||
defaultValue: "analog"
|
||||
}
|
||||
|
||||
ToggleSetting {
|
||||
settingKey: "showSeconds"
|
||||
label: I18n.tr("Show Seconds")
|
||||
defaultValue: true
|
||||
}
|
||||
|
||||
ToggleSetting {
|
||||
settingKey: "showDate"
|
||||
label: I18n.tr("Show Date")
|
||||
defaultValue: true
|
||||
}
|
||||
|
||||
SliderSetting {
|
||||
settingKey: "backgroundOpacity"
|
||||
label: I18n.tr("Background Opacity")
|
||||
defaultValue: 50
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
unit: "%"
|
||||
}
|
||||
}
|
||||
17
quickshell/PLUGINS/ExampleDesktopClock/plugin.json
Normal file
17
quickshell/PLUGINS/ExampleDesktopClock/plugin.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "exampleDesktopClock",
|
||||
"name": "Desktop Clock",
|
||||
"description": "An example desktop widget displaying an analog clock",
|
||||
"version": "1.0.0",
|
||||
"author": "DankMaterialShell",
|
||||
"type": "desktop",
|
||||
"capabilities": ["desktop-widget", "clock"],
|
||||
"component": "./DesktopClock.qml",
|
||||
"icon": "schedule",
|
||||
"settings": "./DesktopClockSettings.qml",
|
||||
"requires_dms": ">=1.2.0",
|
||||
"permissions": [
|
||||
"settings_read",
|
||||
"settings_write"
|
||||
]
|
||||
}
|
||||
@@ -80,7 +80,7 @@ The manifest file defines plugin metadata and configuration.
|
||||
- `description`: Short description of plugin functionality (displayed in UI)
|
||||
- `version`: Semantic version string (e.g., "1.0.0")
|
||||
- `author`: Plugin creator name or email
|
||||
- `type`: Plugin type - "widget", "daemon", or "launcher"
|
||||
- `type`: Plugin type - "widget", "daemon", "launcher", or "desktop"
|
||||
- `capabilities`: Array of plugin capabilities (e.g., ["dankbar-widget"], ["control-center"], ["monitoring"])
|
||||
- `component`: Relative path to main QML component file
|
||||
|
||||
@@ -1450,6 +1450,191 @@ See `PLUGINS/LauncherExample/` for a complete working example demonstrating:
|
||||
- Settings integration
|
||||
- Proper error handling
|
||||
|
||||
## Desktop Plugins
|
||||
|
||||
Desktop plugins are widgets that appear directly on the desktop background layer. They can be dragged, resized, and positioned freely by the user.
|
||||
|
||||
### Overview
|
||||
|
||||
Desktop plugins enable you to:
|
||||
- Display widgets on the desktop background
|
||||
- Support drag-and-drop positioning
|
||||
- Support resize via corner handles
|
||||
- Persist position and size across sessions
|
||||
- Provide settings for customization
|
||||
|
||||
### Plugin Type Configuration
|
||||
|
||||
To create a desktop plugin, set the plugin type in `plugin.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "myDesktopWidget",
|
||||
"name": "My Desktop Widget",
|
||||
"description": "A custom desktop widget",
|
||||
"version": "1.0.0",
|
||||
"author": "Your Name",
|
||||
"type": "desktop",
|
||||
"capabilities": ["desktop-widget"],
|
||||
"component": "./MyWidget.qml",
|
||||
"icon": "widgets",
|
||||
"settings": "./MySettings.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
```
|
||||
|
||||
### Desktop Widget Component Contract
|
||||
|
||||
Create your widget component (`MyWidget.qml`) with the following interface:
|
||||
|
||||
```qml
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Injected properties (provided by DesktopPluginWrapper)
|
||||
property var pluginService: null
|
||||
property string pluginId: ""
|
||||
property bool editMode: false
|
||||
property real widgetWidth: 200
|
||||
property real widgetHeight: 200
|
||||
|
||||
// Optional: Define minimum size constraints
|
||||
property real minWidth: 100
|
||||
property real minHeight: 100
|
||||
|
||||
// Your widget content
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
opacity: 0.85
|
||||
|
||||
// Widget content here
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Hello Desktop!"
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Injected Properties
|
||||
|
||||
Desktop widgets receive these properties automatically:
|
||||
|
||||
- `pluginService`: Reference to PluginService for data persistence
|
||||
- `pluginId`: The plugin's unique identifier
|
||||
- `editMode`: Boolean indicating if the user is in edit mode (dragging/resizing)
|
||||
- `widgetWidth`: Current width of the widget container
|
||||
- `widgetHeight`: Current height of the widget container
|
||||
|
||||
### Optional Properties
|
||||
|
||||
Define these properties on your widget to customize behavior:
|
||||
|
||||
- `minWidth`: Minimum allowed width (default: 100)
|
||||
- `minHeight`: Minimum allowed height (default: 100)
|
||||
|
||||
### Loading and Saving Data
|
||||
|
||||
Use the injected `pluginService` to persist widget-specific data:
|
||||
|
||||
```qml
|
||||
property string myValue: pluginService ? pluginService.loadPluginData(pluginId, "myValue", "default") : "default"
|
||||
|
||||
Connections {
|
||||
target: pluginService
|
||||
function onPluginDataChanged(changedPluginId) {
|
||||
if (changedPluginId !== pluginId) return;
|
||||
root.myValue = pluginService.loadPluginData(pluginId, "myValue", "default");
|
||||
}
|
||||
}
|
||||
|
||||
function saveMyValue(value) {
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData(pluginId, "myValue", value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Position and Size Persistence
|
||||
|
||||
Position (`desktopX`, `desktopY`) and size (`desktopWidth`, `desktopHeight`) are automatically persisted by the `DesktopPluginWrapper`. You don't need to handle this in your widget.
|
||||
|
||||
### Edit Mode
|
||||
|
||||
When `editMode` is true, the user is repositioning or resizing the widget. You can use this to:
|
||||
- Show visual indicators
|
||||
- Disable interactive elements
|
||||
- Display additional controls
|
||||
|
||||
```qml
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
border.color: root.editMode ? Theme.primary : "transparent"
|
||||
border.width: root.editMode ? 2 : 0
|
||||
|
||||
// Content that should be disabled during edit mode
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: !root.editMode
|
||||
onClicked: doSomething()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Settings Component
|
||||
|
||||
Create a settings component using `PluginSettings`:
|
||||
|
||||
```qml
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginSettings {
|
||||
pluginId: "myDesktopWidget"
|
||||
|
||||
ToggleSetting {
|
||||
settingKey: "showBorder"
|
||||
label: "Show Border"
|
||||
description: "Display a border around the widget"
|
||||
defaultValue: true
|
||||
}
|
||||
|
||||
SelectionSetting {
|
||||
settingKey: "theme"
|
||||
label: "Theme"
|
||||
options: [
|
||||
{label: "Light", value: "light"},
|
||||
{label: "Dark", value: "dark"}
|
||||
]
|
||||
defaultValue: "dark"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### User Interaction
|
||||
|
||||
Desktop widgets support:
|
||||
|
||||
1. **Drag**: Click and drag anywhere on the widget (when in edit mode)
|
||||
2. **Resize**: Drag the bottom-right corner handle (when in edit mode)
|
||||
3. **Edit Mode Toggle**: Click the edit button in the bottom-right corner of the screen
|
||||
|
||||
### Example Plugin
|
||||
|
||||
See `PLUGINS/ExampleDesktopClock/` for a complete working example demonstrating:
|
||||
- Analog and digital clock styles
|
||||
- Settings integration
|
||||
- Responsive sizing
|
||||
- Edit mode handling
|
||||
|
||||
## Resources
|
||||
|
||||
- **Plugin Schema**: `plugin-schema.json` - JSON Schema for validation
|
||||
@@ -1458,10 +1643,12 @@ See `PLUGINS/LauncherExample/` for a complete working example demonstrating:
|
||||
- [WorldClock](https://github.com/rochacbruno/WorldClock)
|
||||
- [LauncherExample](./LauncherExample/)
|
||||
- [Calculator](https://github.com/rochacbruno/DankCalculator)
|
||||
- [Desktop Clock](./ExampleDesktopClock/)
|
||||
- **PluginService**: `Services/PluginService.qml`
|
||||
- **Settings UI**: `Modules/Settings/PluginsTab.qml`
|
||||
- **DankBar Integration**: `Modules/DankBar/DankBar.qml`
|
||||
- **Launcher Integration**: `Modules/AppDrawer/AppLauncher.qml`
|
||||
- **Desktop Widget Integration**: `Modules/DesktopWidgetLayer.qml`
|
||||
- **Theme Reference**: `Common/Theme.qml`
|
||||
- **Widget Library**: `Widgets/`
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Plugin type",
|
||||
"enum": ["widget", "daemon", "launcher"]
|
||||
"enum": ["widget", "daemon", "launcher", "desktop"]
|
||||
},
|
||||
"capabilities": {
|
||||
"type": "array",
|
||||
|
||||
Reference in New Issue
Block a user