mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-14 23:34:01 -04:00
284 lines
11 KiB
QML
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|