1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

plugins: add pillClickAction + PopoutService

This commit is contained in:
bbedward
2025-10-04 01:12:17 -04:00
parent 3869955357
commit d83478239e
16 changed files with 1215 additions and 9 deletions

View File

@@ -34,11 +34,8 @@ ShellRoot {
Component.onCompleted: {
PortalService.init()
// Initialize DisplayService night mode functionality
DisplayService.nightModeEnabled
// Initialize WallpaperCyclingService
WallpaperCyclingService.cyclingActive
// Initialize PluginService by accessing its properties
PluginService.pluginDirectory
}
@@ -54,6 +51,9 @@ ShellRoot {
onLoaded: {
if (item) {
item.pluginService = PluginService
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
item.pluginId = pluginId
console.log("Daemon plugin loaded:", pluginId)
}
@@ -124,6 +124,10 @@ ShellRoot {
sourceComponent: Component {
DankDashPopout {
id: dankDashPopout
Component.onCompleted: {
PopoutService.dankDashPopout = dankDashPopout
}
}
}
}
@@ -145,6 +149,10 @@ ShellRoot {
NotificationCenterPopout {
id: notificationCenter
Component.onCompleted: {
PopoutService.notificationCenterPopout = notificationCenter
}
}
}
@@ -172,6 +180,10 @@ ShellRoot {
onLockRequested: {
lock.activate()
}
Component.onCompleted: {
PopoutService.controlCenterPopout = controlCenterPopout
}
}
}
@@ -182,6 +194,10 @@ ShellRoot {
WifiPasswordModal {
id: wifiPasswordModal
Component.onCompleted: {
PopoutService.wifiPasswordModal = wifiPasswordModal
}
}
}
@@ -192,6 +208,10 @@ ShellRoot {
NetworkInfoModal {
id: networkInfoModal
Component.onCompleted: {
PopoutService.networkInfoModal = networkInfoModal
}
}
}
@@ -202,6 +222,10 @@ ShellRoot {
BatteryPopout {
id: batteryPopout
Component.onCompleted: {
PopoutService.batteryPopout = batteryPopout
}
}
}
@@ -212,6 +236,10 @@ ShellRoot {
VpnPopout {
id: vpnPopout
Component.onCompleted: {
PopoutService.vpnPopout = vpnPopout
}
}
}
@@ -268,11 +296,19 @@ ShellRoot {
ProcessListPopout {
id: processListPopout
Component.onCompleted: {
PopoutService.processListPopout = processListPopout
}
}
}
SettingsModal {
id: settingsModal
Component.onCompleted: {
PopoutService.settingsModal = settingsModal
}
}
LazyLoader {
@@ -282,22 +318,43 @@ ShellRoot {
AppDrawerPopout {
id: appDrawerPopout
Component.onCompleted: {
PopoutService.appDrawerPopout = appDrawerPopout
}
}
}
SpotlightModal {
id: spotlightModal
Component.onCompleted: {
PopoutService.spotlightModal = spotlightModal
}
}
ClipboardHistoryModal {
id: clipboardHistoryModalPopup
Component.onCompleted: {
PopoutService.clipboardHistoryModal = clipboardHistoryModalPopup
}
}
NotificationModal {
id: notificationModal
Component.onCompleted: {
PopoutService.notificationModal = notificationModal
}
}
ColorPickerModal {
id: colorPickerModal
Component.onCompleted: {
PopoutService.colorPickerModal = colorPickerModal
}
}
LazyLoader {
@@ -307,6 +364,10 @@ ShellRoot {
ProcessListModal {
id: processListModal
Component.onCompleted: {
PopoutService.processListModal = processListModal
}
}
}
@@ -317,6 +378,10 @@ ShellRoot {
SystemUpdatePopout {
id: systemUpdatePopout
Component.onCompleted: {
PopoutService.systemUpdatePopout = systemUpdatePopout
}
}
}
@@ -384,6 +449,10 @@ ShellRoot {
}, function () {})
}
}
Component.onCompleted: {
PopoutService.powerMenuModal = powerMenuModal
}
}
}

View File

@@ -403,6 +403,10 @@ Item {
item.pluginService = PluginService
}
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
layoutTimer.restart()
}

View File

@@ -84,6 +84,10 @@ Loader {
}
item.pluginService = PluginService
}
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
}
}

View File

@@ -78,8 +78,8 @@ Variants {
}
property bool reveal: {
if (CompositorService.isNiri && NiriService.inOverview) {
return SettingsData.dockOpenOnOverview
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
return true
}
return (!autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky) && !windowIsFullscreen
}
@@ -99,7 +99,7 @@ Variants {
}
screen: modelData
visible: SettingsData.showDock
visible: SettingsData.showDock || (CompositorService.isNiri && SettingsData.dockOpenOnOverview && NiriService.inOverview)
color: "transparent"

View File

@@ -33,7 +33,8 @@ Rectangle {
Loader {
id: contentLoader
anchors.centerIn: parent
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
MouseArea {

View File

@@ -19,6 +19,7 @@ Item {
property Component popoutContent: null
property real popoutWidth: 400
property real popoutHeight: 400
property var pillClickAction: null
property var pluginData: ({})
@@ -70,7 +71,16 @@ Item {
barThickness: root.barThickness
content: root.horizontalBarPill
onClicked: {
if (hasPopout) {
if (pillClickAction) {
if (pillClickAction.length === 0) {
pillClickAction()
} else {
const globalPos = mapToGlobal(0, 0)
const currentScreen = parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
pillClickAction(pos.x, pos.y, pos.width, section, currentScreen)
}
} else if (hasPopout) {
pluginPopout.toggle()
}
}
@@ -88,7 +98,16 @@ Item {
content: root.verticalBarPill
isVerticalOrientation: true
onClicked: {
if (hasPopout) {
if (pillClickAction) {
if (pillClickAction.length === 0) {
pillClickAction()
} else {
const globalPos = mapToGlobal(0, 0)
const currentScreen = parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
pillClickAction(pos.x, pos.y, pos.width, section, currentScreen)
}
} else if (hasPopout) {
pluginPopout.toggle()
}
}

View File

@@ -394,6 +394,9 @@ Item {
if (item && typeof PluginService !== "undefined") {
item.pluginService = PluginService
}
if (item && typeof PopoutService !== "undefined") {
item.popoutService = PopoutService
}
}
}

285
PLUGINS/POPOUT_SERVICE.md Normal file
View File

@@ -0,0 +1,285 @@
# PopoutService for Plugins
## Overview
The `PopoutService` singleton provides plugins with access to all DankMaterialShell popouts and modals. It's automatically injected into plugin widgets and daemons, enabling them to control shell UI elements.
## Automatic Injection
The `popoutService` property is automatically injected into:
- Widget plugins (loaded in DankBar)
- Daemon plugins (background services)
- Plugin settings components
**Required**: Declare the property in your plugin component:
```qml
property var popoutService: null
```
**Note**: Without this declaration, the service cannot be injected and you'll see errors like `Cannot assign to non-existent property "popoutService"`
## API Reference
### Popouts (DankPopout-based)
| Component | Open | Close | Toggle |
|-----------|------|-------|--------|
| Control Center | `openControlCenter()` | `closeControlCenter()` | `toggleControlCenter()` |
| Notification Center | `openNotificationCenter()` | `closeNotificationCenter()` | `toggleNotificationCenter()` |
| App Drawer | `openAppDrawer()` | `closeAppDrawer()` | `toggleAppDrawer()` |
| Process List | `openProcessList()` | `closeProcessList()` | `toggleProcessList()` |
| DankDash | `openDankDash(tab)` | `closeDankDash()` | `toggleDankDash(tab)` |
| Battery | `openBattery()` | `closeBattery()` | `toggleBattery()` |
| VPN | `openVpn()` | `closeVpn()` | `toggleVpn()` |
| System Update | `openSystemUpdate()` | `closeSystemUpdate()` | `toggleSystemUpdate()` |
### Modals (DankModal-based)
| Modal | Show | Hide | Notes |
|-------|------|------|-------|
| Settings | `openSettings()` | `closeSettings()` | Full settings interface |
| Clipboard History | `openClipboardHistory()` | `closeClipboardHistory()` | Cliphist integration |
| Spotlight | `openSpotlight()` | `closeSpotlight()` | Command launcher |
| Power Menu | `openPowerMenu()` | `closePowerMenu()` | Also has `togglePowerMenu()` |
| Process List Modal | `showProcessListModal()` | `hideProcessListModal()` | Fullscreen version, has `toggleProcessListModal()` |
| Color Picker | `showColorPicker()` | `hideColorPicker()` | Theme color selection |
| Notification | `showNotificationModal()` | `hideNotificationModal()` | Notification details |
| WiFi Password | `showWifiPasswordModal()` | `hideWifiPasswordModal()` | Network authentication |
| Network Info | `showNetworkInfoModal()` | `hideNetworkInfoModal()` | Network details |
### Slideouts
| Component | Open | Close | Toggle |
|-----------|------|-------|--------|
| Notepad | `openNotepad()` | `closeNotepad()` | `toggleNotepad()` |
## Usage Examples
### Simple Widget with Popout Control
```qml
import QtQuick
import qs.Common
import qs.Widgets
Rectangle {
id: root
property var popoutService: null
width: 100
height: 30
color: Theme.surfaceContainerHigh
radius: Theme.cornerRadius
MouseArea {
anchors.fill: parent
onClicked: popoutService?.toggleControlCenter()
}
StyledText {
anchors.centerIn: parent
text: "Settings"
}
}
```
### Daemon with Event-Driven Popouts
```qml
import QtQuick
import qs.Services
Item {
id: root
property var popoutService: null
Connections {
target: NotificationService
function onNotificationReceived(notification) {
if (notification.urgency === "critical") {
popoutService?.openNotificationCenter()
}
}
}
Connections {
target: BatteryService
function onPercentageChanged() {
if (BatteryService.percentage < 10 && !BatteryService.isCharging) {
popoutService?.openBattery()
}
}
}
}
```
### Widget with Multiple Popout Options
```qml
import QtQuick
import QtQuick.Controls
import qs.Common
Rectangle {
property var popoutService: null
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
if (mouse.button === Qt.RightButton) {
contextMenu.popup()
} else {
popoutService?.toggleControlCenter()
}
}
}
Menu {
id: contextMenu
MenuItem {
text: "Settings"
onClicked: popoutService?.openSettings()
}
MenuItem {
text: "Notifications"
onClicked: popoutService?.toggleNotificationCenter()
}
MenuItem {
text: "Power Menu"
onClicked: popoutService?.openPowerMenu()
}
}
}
```
## Implementation Details
### Service Architecture
`PopoutService` is a singleton that holds references to popout instances:
```qml
// Services/PopoutService.qml
Singleton {
id: root
property var controlCenterPopout: null
property var notificationCenterPopout: null
// ... other popout references
function toggleControlCenter() {
controlCenterPopout?.toggle()
}
// ... other control functions
}
```
### Reference Assignment
References are assigned in `DMSShell.qml` when popouts are loaded:
```qml
LazyLoader {
ControlCenterPopout {
id: controlCenterPopout
Component.onCompleted: {
PopoutService.controlCenterPopout = controlCenterPopout
}
}
}
```
### Plugin Injection
The service is injected in three locations:
1. **DMSShell.qml** (daemon plugins):
```qml
Instantiator {
delegate: Loader {
onLoaded: {
if (item) {
item.popoutService = PopoutService
}
}
}
}
```
2. **WidgetHost.qml** (widget plugins):
```qml
onLoaded: {
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
}
```
3. **CenterSection.qml** (center widgets):
```qml
onLoaded: {
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
}
```
4. **PluginsTab.qml** (settings):
```qml
onLoaded: {
if (item && typeof PopoutService !== "undefined") {
item.popoutService = PopoutService
}
}
```
## Best Practices
1. **Use Optional Chaining**: Always use `?.` to handle null cases
```qml
popoutService?.toggleControlCenter()
```
2. **Check Availability**: Some popouts may not be available
```qml
if (popoutService && popoutService.controlCenterPopout) {
popoutService.toggleControlCenter()
}
```
3. **Lazy Loading**: First access may activate lazy loaders - this is normal
4. **Feature Detection**: Some popouts require specific features
```qml
if (BatteryService.batteryAvailable) {
popoutService?.openBattery()
}
```
5. **User Intent**: Only trigger popouts based on user actions or critical events
## Example Plugin
See `PLUGINS/PopoutControlExample/` for a complete working example that demonstrates:
- Widget creation with popout controls
- Menu-based popout selection
- Proper service usage
- Error handling
## Limitations
- Popouts are shared across all plugins - avoid conflicts
- Some popouts may be compositor or feature-dependent
- Lazy-loaded popouts need activation before use
- Multi-monitor considerations apply to positioned popouts

View File

@@ -0,0 +1,56 @@
import QtQuick
import qs.Common
import qs.Widgets
import qs.Modules.Plugins
PluginSettings {
id: root
pluginId: "popoutControlExample"
StyledText {
width: parent.width
text: "Popout Control Settings"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
width: parent.width
text: "Choose which popout/modal will open when clicking the widget"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
SelectionSetting {
settingKey: "selectedPopout"
label: "Popout to Open"
description: "Select which popout or modal opens when you click the widget"
options: [
{label: "Control Center", value: "controlCenter"},
{label: "Notification Center", value: "notificationCenter"},
{label: "App Drawer", value: "appDrawer"},
{label: "Process List", value: "processList"},
{label: "DankDash", value: "dankDash"},
{label: "Battery Info", value: "battery"},
{label: "VPN", value: "vpn"},
{label: "System Update", value: "systemUpdate"},
{label: "Settings", value: "settings"},
{label: "Clipboard History", value: "clipboardHistory"},
{label: "Spotlight", value: "spotlight"},
{label: "Power Menu", value: "powerMenu"},
{label: "Color Picker", value: "colorPicker"},
{label: "Notepad", value: "notepad"}
]
defaultValue: "controlCenter"
}
StyledText {
width: parent.width
text: "💡 Tip: The widget displays the name of the selected popout and opens it when clicked!"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
}

View File

@@ -0,0 +1,95 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
property var popoutService: null
property string selectedPopout: pluginData.selectedPopout || "controlCenter"
property var popoutActions: ({
"controlCenter": (x, y, w, s, scr) => popoutService?.toggleControlCenter(x, y, w, s, scr),
"notificationCenter": (x, y, w, s, scr) => popoutService?.toggleNotificationCenter(x, y, w, s, scr),
"appDrawer": (x, y, w, s, scr) => popoutService?.toggleAppDrawer(x, y, w, s, scr),
"processList": (x, y, w, s, scr) => popoutService?.toggleProcessList(x, y, w, s, scr),
"dankDash": (x, y, w, s, scr) => popoutService?.toggleDankDash(0, x, y, w, s, scr),
"battery": (x, y, w, s, scr) => popoutService?.toggleBattery(x, y, w, s, scr),
"vpn": (x, y, w, s, scr) => popoutService?.toggleVpn(x, y, w, s, scr),
"systemUpdate": (x, y, w, s, scr) => popoutService?.toggleSystemUpdate(x, y, w, s, scr),
"settings": () => popoutService?.openSettings(),
"clipboardHistory": () => popoutService?.openClipboardHistory(),
"spotlight": () => popoutService?.openSpotlight(),
"powerMenu": () => popoutService?.togglePowerMenu(),
"colorPicker": () => popoutService?.showColorPicker(),
"notepad": () => popoutService?.toggleNotepad()
})
property var popoutNames: ({
"controlCenter": "Control Center",
"notificationCenter": "Notification Center",
"appDrawer": "App Drawer",
"processList": "Process List",
"dankDash": "DankDash",
"battery": "Battery Info",
"vpn": "VPN",
"systemUpdate": "System Update",
"settings": "Settings",
"clipboardHistory": "Clipboard",
"spotlight": "Spotlight",
"powerMenu": "Power Menu",
"colorPicker": "Color Picker",
"notepad": "Notepad"
})
pillClickAction: (x, y, width, section, screen) => {
if (popoutActions[selectedPopout]) {
popoutActions[selectedPopout](x, y, width, section, screen)
}
}
horizontalBarPill: Component {
Row {
spacing: Theme.spacingXS
DankIcon {
name: "widgets"
color: Theme.primary
font.pixelSize: Theme.iconSize - 6
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: popoutNames[selectedPopout] || "Popouts"
color: Theme.primary
font.pixelSize: Theme.fontSizeMedium
anchors.verticalCenter: parent.verticalCenter
}
}
}
verticalBarPill: Component {
Column {
spacing: Theme.spacingXS
DankIcon {
name: "widgets"
color: Theme.primary
font.pixelSize: Theme.iconSize - 6
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: popoutNames[selectedPopout] || "Popouts"
color: Theme.primary
font.pixelSize: Theme.fontSizeSmall
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
}
}
}
}

View File

@@ -0,0 +1,207 @@
# Popout Control Example Plugin
This example plugin demonstrates:
- Using `PopoutService` to trigger positioned popouts and modals
- Using `pillClickAction` with position parameters
- Using `PluginSettings` with dropdown selection
- Dynamic widget text based on settings
The `pillClickAction` receives position parameters `(x, y, width, section, screen)` which are passed to PopoutService functions to properly position popouts relative to the widget.
## PopoutService API
The `PopoutService` is automatically injected into plugin widgets and daemons as `popoutService`. It provides access to all shell popouts and modals.
### Available Popouts
#### Control Center
```qml
popoutService.openControlCenter()
popoutService.closeControlCenter()
popoutService.toggleControlCenter()
```
#### Notification Center
```qml
popoutService.openNotificationCenter()
popoutService.closeNotificationCenter()
popoutService.toggleNotificationCenter()
```
#### App Drawer
```qml
popoutService.openAppDrawer()
popoutService.closeAppDrawer()
popoutService.toggleAppDrawer()
```
#### Process List (Popout)
```qml
popoutService.openProcessList()
popoutService.closeProcessList()
popoutService.toggleProcessList()
```
#### DankDash
```qml
popoutService.openDankDash(tabIndex) // tabIndex: 0=Calendar, 1=Media, 2=Weather
popoutService.closeDankDash()
popoutService.toggleDankDash(tabIndex)
```
#### Battery Popout
```qml
popoutService.openBattery()
popoutService.closeBattery()
popoutService.toggleBattery()
```
#### VPN Popout
```qml
popoutService.openVpn()
popoutService.closeVpn()
popoutService.toggleVpn()
```
#### System Update Popout
```qml
popoutService.openSystemUpdate()
popoutService.closeSystemUpdate()
popoutService.toggleSystemUpdate()
```
### Available Modals
#### Settings Modal
```qml
popoutService.openSettings()
popoutService.closeSettings()
```
#### Clipboard History Modal
```qml
popoutService.openClipboardHistory()
popoutService.closeClipboardHistory()
```
#### Spotlight Modal
```qml
popoutService.openSpotlight()
popoutService.closeSpotlight()
```
#### Power Menu Modal
```qml
popoutService.openPowerMenu()
popoutService.closePowerMenu()
popoutService.togglePowerMenu()
```
#### Process List Modal (fullscreen)
```qml
popoutService.showProcessListModal()
popoutService.hideProcessListModal()
popoutService.toggleProcessListModal()
```
#### Color Picker Modal
```qml
popoutService.showColorPicker()
popoutService.hideColorPicker()
```
#### Notification Modal
```qml
popoutService.showNotificationModal()
popoutService.hideNotificationModal()
```
#### WiFi Password Modal
```qml
popoutService.showWifiPasswordModal()
popoutService.hideWifiPasswordModal()
```
#### Network Info Modal
```qml
popoutService.showNetworkInfoModal()
popoutService.hideNetworkInfoModal()
```
#### Notepad Slideout
```qml
popoutService.openNotepad()
popoutService.closeNotepad()
popoutService.toggleNotepad()
```
## Usage in Plugins
### Widget Plugins
```qml
import QtQuick
import qs.Common
import qs.Widgets
Rectangle {
id: root
property var popoutService: null // REQUIRED: Must declare for injection
MouseArea {
anchors.fill: parent
onClicked: {
popoutService?.toggleControlCenter()
}
}
}
```
### Daemon Plugins
```qml
import QtQuick
import qs.Services
Item {
id: root
property var popoutService: null // REQUIRED: Must declare for injection
Connections {
target: NotificationService
function onNotificationReceived() {
popoutService?.openNotificationCenter()
}
}
}
```
**Important**: The `popoutService` property **must** be declared in your plugin component. Without it, you'll get errors like:
```
Error: Cannot assign to non-existent property "popoutService"
```
## Example Use Cases
1. **Custom Launcher**: Create a widget that opens the app drawer
2. **Quick Settings**: Toggle control center from a custom button
3. **Notification Manager**: Open notification center on new notifications
4. **System Monitor**: Open process list on high CPU usage
5. **Power Management**: Trigger power menu from a custom widget
## Installation
1. Copy the plugin directory to `~/.config/DankMaterialShell/plugins/`
2. Open Settings → Plugins
3. Click "Scan for Plugins"
4. Enable "Popout Control Example"
5. Add `popoutControlExample` to your DankBar widget list
## Notes
- The `popoutService` property is automatically injected - no manual setup required
- Always use optional chaining (`?.`) when calling methods to handle null cases
- Popouts are lazily loaded - first access may activate the loader
- Some popouts require specific system features to be available

View File

@@ -0,0 +1,12 @@
{
"id": "popoutControlExample",
"name": "Popout Control Example",
"description": "Example widget demonstrating PopoutService usage with pillClickAction",
"version": "1.0.0",
"author": "DankMaterialShell",
"icon": "widgets",
"type": "widget",
"component": "./PopoutControlWidget.qml",
"settings": "./PopoutControlSettings.qml",
"permissions": ["settings_read", "settings_write"]
}

View File

@@ -159,6 +159,29 @@ PluginComponent {
- `popoutContent`: Optional popout window content
- `popoutWidth`: Popout window width
- `popoutHeight`: Popout window height
- `pillClickAction`: Custom click handler function (overrides popout)
**Custom Click Actions:**
Override the default popout behavior with `pillClickAction`:
```qml
PluginComponent {
horizontalBarPill: Component {
StyledText { text: "Click Me" }
}
// Simple 0-parameter function
pillClickAction: () => {
Process.exec("bash", ["-c", "notify-send 'Clicked!'"])
}
// Or with position parameters for popouts: (x, y, width, section, screen)
pillClickAction: (x, y, width, section, screen) => {
popoutService?.toggleControlCenter(x, y, width, section, screen)
}
}
```
The PluginComponent automatically handles:
- Bar orientation detection

132
PLUGINS/THEME_REFERENCE.md Normal file
View File

@@ -0,0 +1,132 @@
# Theme Property Reference for Plugins
Quick reference for commonly used Theme properties in plugin development.
## Font Sizes
```qml
Theme.fontSizeSmall // 12px (scaled)
Theme.fontSizeMedium // 14px (scaled)
Theme.fontSizeLarge // 16px (scaled)
Theme.fontSizeXLarge // 20px (scaled)
```
**Note**: These are scaled by `SettingsData.fontScale`
## Icon Sizes
```qml
Theme.iconSizeSmall // 16px
Theme.iconSize // 24px (default)
Theme.iconSizeLarge // 32px
```
## Spacing
```qml
Theme.spacingXS // Extra small
Theme.spacingS // Small
Theme.spacingM // Medium
Theme.spacingL // Large
Theme.spacingXL // Extra large
```
## Border Radius
```qml
Theme.cornerRadius // Standard corner radius
Theme.cornerRadiusSmall // Smaller radius
Theme.cornerRadiusLarge // Larger radius
```
## Colors
### Surface Colors
```qml
Theme.surface
Theme.surfaceContainerLow
Theme.surfaceContainer
Theme.surfaceContainerHigh
Theme.surfaceContainerHighest
```
### Text Colors
```qml
Theme.onSurface // Primary text on surface
Theme.onSurfaceVariant // Secondary text on surface
Theme.outline // Border/divider color
```
### Semantic Colors
```qml
Theme.primary
Theme.onPrimary
Theme.secondary
Theme.onSecondary
Theme.error
Theme.warning
Theme.success
```
### Special Functions
```qml
Theme.popupBackground() // Popup background with opacity
```
## Common Patterns
### Icon with Text
```qml
DankIcon {
name: "icon_name"
color: Theme.onSurface
font.pixelSize: Theme.iconSize
}
StyledText {
text: "Label"
color: Theme.onSurface
font.pixelSize: Theme.fontSizeMedium
}
```
### Container with Border
```qml
Rectangle {
color: Theme.surfaceContainerHigh
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
}
```
### Hover Effect
```qml
MouseArea {
hoverEnabled: true
onEntered: parent.color = Qt.lighter(Theme.surfaceContainerHigh, 1.1)
onExited: parent.color = Theme.surfaceContainerHigh
}
```
## Common Mistakes
**Wrong**:
```qml
font.pixelSize: Theme.fontSizeS // Property doesn't exist
font.pixelSize: Theme.iconSizeS // Property doesn't exist
```
**Correct**:
```qml
font.pixelSize: Theme.fontSizeSmall // Use full name
font.pixelSize: Theme.iconSizeSmall // Use full name
```
## Checking Available Properties
To see all available Theme properties, check `Common/Theme.qml` or use:
```bash
grep "property" Common/Theme.qml
```

View File

@@ -9,6 +9,7 @@ PluginComponent {
id: root
property string scriptPath: pluginData.scriptPath || ""
property var popoutService: null
Connections {
target: SessionData

295
Services/PopoutService.qml Normal file
View File

@@ -0,0 +1,295 @@
import QtQuick
import Quickshell
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
property var controlCenterPopout: null
property var notificationCenterPopout: null
property var appDrawerPopout: null
property var processListPopout: null
property var dankDashPopout: null
property var batteryPopout: null
property var vpnPopout: null
property var systemUpdatePopout: null
property var settingsModal: null
property var clipboardHistoryModal: null
property var spotlightModal: null
property var powerMenuModal: null
property var processListModal: null
property var colorPickerModal: null
property var notificationModal: null
property var wifiPasswordModal: null
property var networkInfoModal: null
property var notepadSlideouts: []
function setPosition(popout, x, y, width, section, screen) {
if (popout && popout.setTriggerPosition && arguments.length >= 6) {
popout.setTriggerPosition(x, y, width, section, screen)
}
}
function openControlCenter(x, y, width, section, screen) {
if (controlCenterPopout) {
setPosition(controlCenterPopout, x, y, width, section, screen)
controlCenterPopout.open()
}
}
function closeControlCenter() {
controlCenterPopout?.close()
}
function toggleControlCenter(x, y, width, section, screen) {
if (controlCenterPopout) {
setPosition(controlCenterPopout, x, y, width, section, screen)
controlCenterPopout.toggle()
}
}
function openNotificationCenter(x, y, width, section, screen) {
if (notificationCenterPopout) {
setPosition(notificationCenterPopout, x, y, width, section, screen)
notificationCenterPopout.open()
}
}
function closeNotificationCenter() {
notificationCenterPopout?.close()
}
function toggleNotificationCenter(x, y, width, section, screen) {
if (notificationCenterPopout) {
setPosition(notificationCenterPopout, x, y, width, section, screen)
notificationCenterPopout.toggle()
}
}
function openAppDrawer(x, y, width, section, screen) {
if (appDrawerPopout) {
setPosition(appDrawerPopout, x, y, width, section, screen)
appDrawerPopout.open()
}
}
function closeAppDrawer() {
appDrawerPopout?.close()
}
function toggleAppDrawer(x, y, width, section, screen) {
if (appDrawerPopout) {
setPosition(appDrawerPopout, x, y, width, section, screen)
appDrawerPopout.toggle()
}
}
function openProcessList(x, y, width, section, screen) {
if (processListPopout) {
setPosition(processListPopout, x, y, width, section, screen)
processListPopout.open()
}
}
function closeProcessList() {
processListPopout?.close()
}
function toggleProcessList(x, y, width, section, screen) {
if (processListPopout) {
setPosition(processListPopout, x, y, width, section, screen)
processListPopout.toggle()
}
}
function openDankDash(tabIndex, x, y, width, section, screen) {
if (dankDashPopout) {
if (arguments.length >= 6) {
setPosition(dankDashPopout, x, y, width, section, screen)
}
dankDashPopout.currentTabIndex = tabIndex || 0
dankDashPopout.dashVisible = true
}
}
function closeDankDash() {
if (dankDashPopout) {
dankDashPopout.dashVisible = false
}
}
function toggleDankDash(tabIndex, x, y, width, section, screen) {
if (dankDashPopout) {
if (arguments.length >= 6) {
setPosition(dankDashPopout, x, y, width, section, screen)
}
if (dankDashPopout.dashVisible) {
dankDashPopout.dashVisible = false
} else {
dankDashPopout.currentTabIndex = tabIndex || 0
dankDashPopout.dashVisible = true
}
}
}
function openBattery(x, y, width, section, screen) {
if (batteryPopout) {
setPosition(batteryPopout, x, y, width, section, screen)
batteryPopout.open()
}
}
function closeBattery() {
batteryPopout?.close()
}
function toggleBattery(x, y, width, section, screen) {
if (batteryPopout) {
setPosition(batteryPopout, x, y, width, section, screen)
batteryPopout.toggle()
}
}
function openVpn(x, y, width, section, screen) {
if (vpnPopout) {
setPosition(vpnPopout, x, y, width, section, screen)
vpnPopout.open()
}
}
function closeVpn() {
vpnPopout?.close()
}
function toggleVpn(x, y, width, section, screen) {
if (vpnPopout) {
setPosition(vpnPopout, x, y, width, section, screen)
vpnPopout.toggle()
}
}
function openSystemUpdate(x, y, width, section, screen) {
if (systemUpdatePopout) {
setPosition(systemUpdatePopout, x, y, width, section, screen)
systemUpdatePopout.open()
}
}
function closeSystemUpdate() {
systemUpdatePopout?.close()
}
function toggleSystemUpdate(x, y, width, section, screen) {
if (systemUpdatePopout) {
setPosition(systemUpdatePopout, x, y, width, section, screen)
systemUpdatePopout.toggle()
}
}
function openSettings() {
settingsModal?.show()
}
function closeSettings() {
settingsModal?.close()
}
function openClipboardHistory() {
clipboardHistoryModal?.show()
}
function closeClipboardHistory() {
clipboardHistoryModal?.close()
}
function openSpotlight() {
spotlightModal?.show()
}
function closeSpotlight() {
spotlightModal?.close()
}
function openPowerMenu() {
powerMenuModal?.openCentered()
}
function closePowerMenu() {
powerMenuModal?.close()
}
function togglePowerMenu() {
if (powerMenuModal) {
if (powerMenuModal.shouldBeVisible) {
powerMenuModal.close()
} else {
powerMenuModal.openCentered()
}
}
}
function showProcessListModal() {
processListModal?.show()
}
function hideProcessListModal() {
processListModal?.hide()
}
function toggleProcessListModal() {
processListModal?.toggle()
}
function showColorPicker() {
colorPickerModal?.show()
}
function hideColorPicker() {
colorPickerModal?.close()
}
function showNotificationModal() {
notificationModal?.show()
}
function hideNotificationModal() {
notificationModal?.close()
}
function showWifiPasswordModal() {
wifiPasswordModal?.show()
}
function hideWifiPasswordModal() {
wifiPasswordModal?.close()
}
function showNetworkInfoModal() {
networkInfoModal?.show()
}
function hideNetworkInfoModal() {
networkInfoModal?.close()
}
function openNotepad() {
if (notepadSlideouts.length > 0) {
notepadSlideouts[0]?.show()
}
}
function closeNotepad() {
if (notepadSlideouts.length > 0) {
notepadSlideouts[0]?.hide()
}
}
function toggleNotepad() {
if (notepadSlideouts.length > 0) {
notepadSlideouts[0]?.toggle()
}
}
}