Files
DankMaterialShell/quickshell/Modules/ControlCenter/Components/WidgetConfigOverlay.qml
T
2026-09-28 14:26:42 -04:00

284 lines
11 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import Quickshell.Widgets
import qs.Common
import qs.Modules.ControlCenter
import qs.Modules.ControlCenter.Widgets
import qs.Modules.DankBar.Widgets
import qs.Modules.DankDash
import qs.Modules.Settings.Widgets
import "../utils/widgets.js" as WidgetUtils
import qs.Services
import qs.Widgets
Item {
id: root
property Item backdrop: null
property int widgetIndex: -1
property real anchorX: 0
property real anchorY: 0
property real anchorWidth: 0
property real anchorHeight: 0
readonly property var widgetData: {
if (widgetIndex < 0)
return null;
const widgets = SettingsData.controlCenterWidgets || [];
return widgets[widgetIndex] || null;
}
readonly property string widgetId: String(widgetData?.id ?? "")
readonly property bool isPlugin: widgetId.startsWith("plugin_")
readonly property bool isDisk: widgetId === "diskUsage"
readonly property bool isIdleInhibitor: widgetId === "idleInhibitor"
readonly property bool isUser: widgetId === "userCard"
readonly property bool isQuickActions: widgetId === "quickActions"
readonly property var quickActions: isQuickActions ? WidgetUtils.quickActions(widgetData) : []
visible: widgetIndex >= 0
z: CcMetrics.overlayZ
function open(index, data, anchorItem) {
const pos = anchorItem.mapToItem(root, 0, 0);
anchorX = pos.x;
anchorY = pos.y;
anchorWidth = anchorItem.width;
anchorHeight = anchorItem.height;
widgetIndex = index;
focusScope.forceActiveFocus();
}
function close() {
widgetIndex = -1;
}
function toggleAction(id, enabled) {
persistOption("actions", quickActions.map(action => action.id === id ? Object.assign({}, action, {
"enabled": enabled
}) : action));
}
function persistOption(key, value) {
const widgets = (SettingsData.controlCenterWidgets || []).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length)
return;
widgets[widgetIndex] = Object.assign({}, widgets[widgetIndex], {
[key]: value
});
SettingsData.set("controlCenterWidgets", widgets);
}
MouseArea {
anchors.fill: parent
enabled: root.visible
acceptedButtons: Qt.AllButtons
onClicked: root.close()
onWheel: wheel => wheel.accepted = true
}
FocusScope {
id: focusScope
anchors.fill: parent
focus: root.visible
Keys.onEscapePressed: event => {
root.close();
event.accepted = true;
}
}
Rectangle {
id: panel
readonly property real preferredY: root.anchorY - height - Theme.spacingS < Theme.spacingS ? root.anchorY + root.anchorHeight + Theme.spacingS : root.anchorY - height - Theme.spacingS
width: CcMetrics.configMenuWidth
height: Math.min(menu.implicitHeight, root.height - Theme.spacingS * 4) + Theme.spacingS * 2
radius: Theme.windowRadius
color: CcMetrics.dialogColor
border.width: Theme.layerOutlineWidth
border.color: Theme.outlineMedium
x: Math.max(Theme.spacingS, Math.min(root.anchorX + root.anchorWidth - width, root.width - width - Theme.spacingS))
y: Math.max(Theme.spacingS, Math.min(preferredY, root.height - height - Theme.spacingS))
opacity: root.visible ? 1 : 0
scale: root.visible ? 1 : CcMetrics.popupEnterScale
transformOrigin: Item.TopRight
Behavior on opacity {
enabled: CcMetrics.animationsEnabled
NumberAnimation {
duration: Theme.expressiveDurations.expressiveEffects
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
}
}
Behavior on scale {
enabled: CcMetrics.animationsEnabled
NumberAnimation {
duration: Theme.expressiveDurations.expressiveFastSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial
}
}
// The popout is not a real subsurface, so fake compositor blur the same way the detail dialog does, but from the sheet content it covers.
Loader {
anchors.fill: parent
z: -1
active: root.visible && root.backdrop !== null && CcMetrics.hideCoveredContent
sourceComponent: ClippingRectangle {
id: clipArea
// Children land in an internal content item, so `parent` never reaches this pad.
readonly property real pad: CcMetrics.backdropBlurRadius
radius: panel.radius
color: "transparent"
ShaderEffectSource {
id: backdropSource
x: -clipArea.pad
y: -clipArea.pad
width: clipArea.width + clipArea.pad * 2
height: clipArea.height + clipArea.pad * 2
visible: false
sourceItem: root.backdrop
// mapFromItem is not reactive, so the capture is rebuilt from the panel's own geometry.
sourceRect: {
const origin = root.backdrop.mapFromItem(root, panel.x - clipArea.pad, panel.y - clipArea.pad);
return Qt.rect(origin.x, origin.y, width, height);
}
}
MultiEffect {
anchors.fill: backdropSource
source: backdropSource
blurEnabled: true
blur: 1
blurMax: CcMetrics.backdropBlurRadius
autoPaddingEnabled: false
}
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.AllButtons
onClicked: mouse => mouse.accepted = true
}
DankFlickable {
anchors.fill: parent
anchors.margins: Theme.spacingS
clip: true
contentWidth: width
contentHeight: menu.implicitHeight
interactive: contentHeight > height
CcGroup {
id: menu
width: parent.width
CcListRow {
visible: root.isPlugin
iconName: "settings"
title: I18n.tr("Plugin settings")
clickable: true
onClicked: {
PopoutService.openSettingsWithTab(SettingsTabs.pluginPrefix + root.widgetId.replace("plugin_", ""));
root.close();
}
}
Repeater {
model: root.isUser ? DashRegistry.sheetOptionSpecs("user") : []
CcToggleRow {
required property var modelData
text: modelData.text
checked: DashRegistry.optionValue(modelData, root.widgetData?.[modelData.key])
onToggled: checked => root.persistOption(modelData.key, checked)
}
}
SettingsReorderList {
id: actionList
visible: root.isQuickActions
model: root.quickActions
onReordered: indices => root.persistOption("actions", indices.map(i => root.quickActions[i]))
delegate: SettingsReorderRow {
required property var modelData
readonly property bool locked: modelData.id === "edit"
reorderList: actionList
paddingH: CcMetrics.rowPaddingH
paddingV: CcMetrics.rowPaddingV
rowColor: dragging ? Theme.blend(CcMetrics.rowColor, Theme.onSurface, Theme.stateLayerDrag) : CcMetrics.rowColor
iconName: WidgetUtils.quickActionIcon(modelData.id)
title: I18n.tr(WidgetUtils.quickActionLabel(modelData.id))
clickable: !locked
onClicked: root.toggleAction(modelData.id, !modelData.enabled)
DankToggle {
hideText: true
text: parent.title
activeFocusOnTab: false
checked: modelData.enabled
enabled: !locked
onToggled: value => root.toggleAction(modelData.id, value)
}
}
}
CcToggleRow {
visible: root.isUser
text: I18n.tr("Background")
checked: root.widgetData?.background !== false
onToggled: checked => root.persistOption("background", checked)
}
CcToggleRow {
visible: root.isQuickActions
text: I18n.tr("Highlight power", "toggle that gives the control center power button the error color")
checked: root.widgetData?.powerAccent === true
onToggled: checked => root.persistOption("powerAccent", checked)
}
CcToggleRow {
visible: root.isDisk
text: I18n.tr("Show mount path", "toggle in control center disk usage widget to turn mount path display on or off")
checked: root.widgetData?.showMountPath !== false
onToggled: checked => root.persistOption("showMountPath", checked)
}
CcListRow {
visible: root.isIdleInhibitor
iconName: "timer"
title: I18n.tr("Duration")
body: DankDropdown {
readonly property var presets: IdleInhibitPresets.presetOptions
compactMode: true
dropdownWidth: parent.width
currentValue: presets.find(p => p.minutes === (root.widgetData?.durationMinutes ?? 0))?.label ?? ""
options: presets.map(p => p.label)
onValueChanged: value => {
const preset = presets.find(p => p.label === value);
if (preset)
root.persistOption("durationMinutes", preset.minutes);
}
}
}
}
}
}
}