mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-15 07:44:01 -04:00
deprecation notice to plugin compatibility shims - When qml-common became a thing, we left qs.Widgets shims in place for plugin compatibility - It's annoying to need to maintain them when qml-common has any change, add a warning so we can safely deprecate them in the future - One shot rename the common stuff to D to make it shorter and nicer to write
431 lines
14 KiB
QML
431 lines
14 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import qs.Common
|
|
import qs.Modules.ControlCenter
|
|
import qs.Modules.ControlCenter.Details
|
|
import qs.DCommon.Widgets
|
|
import "../utils/sections.js" as Sections
|
|
|
|
FocusScope {
|
|
id: root
|
|
|
|
LayoutMirroring.enabled: I18n.isRtl
|
|
LayoutMirroring.childrenInherit: true
|
|
|
|
property string section: ""
|
|
property var model: null
|
|
property string screenName: ""
|
|
property string screenModel: ""
|
|
property var transientSurfaceTracker: null
|
|
property real topInset: 0
|
|
property vector4d cornerRadii: Qt.vector4d(Theme.windowRadius, Theme.windowRadius, Theme.windowRadius, Theme.windowRadius)
|
|
property real coverage: 0
|
|
property var runningToplevels: []
|
|
|
|
signal dismissed
|
|
signal backRequested
|
|
signal collapseRequested
|
|
signal closeRequested
|
|
signal codecSelectorRequested(var device)
|
|
signal portSelectorRequested(var node)
|
|
|
|
property string shownSection: ""
|
|
property bool enterPending: false
|
|
readonly property bool animationsEnabled: CcMetrics.animationsEnabled && !SettingsData.reduceMotion
|
|
readonly property bool transitioning: enterPending || enterAnimation.running
|
|
readonly property var pageItem: pageLoader.item
|
|
readonly property real pageHeight: (pageItem?.implicitHeight ?? 0) > 0 ? pageItem.implicitHeight : CcMetrics.preferredDetailHeight(shownSection, pageItem?.preferredHeight ?? 0)
|
|
readonly property real contentHeight: pageHeight
|
|
readonly property real chromeHeight: header.height + CcMetrics.detailDialogPadding * 2
|
|
readonly property real maximumHeight: height - topInset - CcMetrics.detailDialogInset
|
|
// plugin detail content may not scroll itself, so the panel grows to fit it
|
|
readonly property bool pageScrollsItself: !shownSection.startsWith("plugin_")
|
|
readonly property real minimumHeight: chromeHeight + (pageScrollsItself ? Math.min(contentHeight, CcMetrics.detailMinContentHeight) : pageHeight)
|
|
readonly property string title: {
|
|
const own = pageItem?.title ?? "";
|
|
if (own)
|
|
return own;
|
|
const parsed = Sections.parse(shownSection);
|
|
return model?.getWidgetForId(parsed.base)?.text ?? "";
|
|
}
|
|
visible: shownSection !== ""
|
|
opacity: 0
|
|
layer.enabled: enterPending || enterAnimation.running || exitAnimation.running
|
|
layer.smooth: true
|
|
Accessible.role: Accessible.Dialog
|
|
Accessible.name: title
|
|
|
|
function finishClose() {
|
|
if (section !== "")
|
|
return;
|
|
pageLoader.sourceComponent = null;
|
|
shownSection = "";
|
|
dismissed();
|
|
}
|
|
|
|
function containsItem(item) {
|
|
for (let ancestor = item; ancestor; ancestor = ancestor.parent) {
|
|
if (ancestor === root)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function moveFocus(backwards) {
|
|
const current = root.Window.window?.activeFocusItem ?? root;
|
|
const next = current.nextItemInFocusChain(!backwards);
|
|
const target = root.containsItem(next) ? next : backwards ? closeButton : root.nextItemInFocusChain(true);
|
|
(root.containsItem(target) ? target : closeButton).forceActiveFocus(backwards ? Qt.BacktabFocusReason : Qt.TabFocusReason);
|
|
}
|
|
|
|
Shortcut {
|
|
sequence: "Tab"
|
|
enabled: root.section !== "" && root.activeFocus
|
|
onActivated: root.moveFocus(false)
|
|
}
|
|
|
|
Shortcut {
|
|
sequences: ["Backtab", "Shift+Tab"]
|
|
enabled: root.section !== "" && root.activeFocus
|
|
onActivated: root.moveFocus(true)
|
|
}
|
|
|
|
function dismissTransient() {
|
|
const item = pageItem;
|
|
if (!item || typeof item.dismissTransient !== "function")
|
|
return false;
|
|
return item.dismissTransient() === true;
|
|
}
|
|
|
|
function _componentFor(sectionId) {
|
|
const parsed = Sections.parse(sectionId);
|
|
switch (parsed.base) {
|
|
case "network":
|
|
case "wifi":
|
|
return networkComponent;
|
|
case "bluetooth":
|
|
return bluetoothComponent;
|
|
case "audioOutput":
|
|
return audioOutputComponent;
|
|
case "audioInput":
|
|
return audioInputComponent;
|
|
case "battery":
|
|
return batteryComponent;
|
|
case "doNotDisturb":
|
|
return dndComponent;
|
|
case "idleInhibitor":
|
|
return idleInhibitComponent;
|
|
case "diskUsage":
|
|
return diskUsageComponent;
|
|
case "brightnessSlider":
|
|
return brightnessComponent;
|
|
case "runningApps":
|
|
return runningAppsComponent;
|
|
}
|
|
if (sectionId.startsWith("builtin_") || sectionId.startsWith("plugin_"))
|
|
return pluginComponent;
|
|
return null;
|
|
}
|
|
|
|
onSectionChanged: {
|
|
enterPending = false;
|
|
enterAnimation.stop();
|
|
exitAnimation.stop();
|
|
if (section === "") {
|
|
if (!animationsEnabled) {
|
|
root.opacity = 0;
|
|
finishClose();
|
|
return;
|
|
}
|
|
exitBlocker.forceActiveFocus();
|
|
exitAnimation.start();
|
|
return;
|
|
}
|
|
const opening = shownSection === "";
|
|
enterPending = opening && animationsEnabled;
|
|
root.opacity = enterPending ? 0 : 1;
|
|
dialogSurface.entryScale = enterPending ? CcMetrics.popupEnterScale : 1;
|
|
shownSection = section;
|
|
pageLoader.sourceComponent = _componentFor(section);
|
|
(pageItem ?? root).forceActiveFocus();
|
|
}
|
|
|
|
Connections {
|
|
target: root.Window.window
|
|
enabled: root.enterPending
|
|
|
|
function onFrameSwapped() {
|
|
root.enterPending = false;
|
|
enterAnimation.start();
|
|
}
|
|
}
|
|
|
|
ParallelAnimation {
|
|
id: enterAnimation
|
|
|
|
NumberAnimation {
|
|
target: root
|
|
property: "opacity"
|
|
to: 1
|
|
duration: CcMetrics.fadeDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
NumberAnimation {
|
|
target: dialogSurface
|
|
property: "entryScale"
|
|
to: 1
|
|
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
|
}
|
|
}
|
|
|
|
NumberAnimation {
|
|
id: exitAnimation
|
|
target: root
|
|
property: "opacity"
|
|
to: 0
|
|
duration: CcMetrics.fadeDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
onFinished: root.finishClose()
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
topLeftRadius: root.cornerRadii.x
|
|
topRightRadius: root.cornerRadii.y
|
|
bottomRightRadius: root.cornerRadii.z
|
|
bottomLeftRadius: root.cornerRadii.w
|
|
color: Theme.scrimColor
|
|
opacity: Theme.scrimAlpha
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
acceptedButtons: Qt.AllButtons
|
|
onClicked: {
|
|
if (root.section !== "")
|
|
root.backRequested();
|
|
}
|
|
onWheel: wheel => wheel.accepted = true
|
|
}
|
|
|
|
Rectangle {
|
|
id: dialogSurface
|
|
|
|
x: CcMetrics.detailDialogInset
|
|
y: root.topInset + Math.max(0, (root.maximumHeight - height) / 2)
|
|
width: Math.max(0, root.width - CcMetrics.detailDialogInset * 2)
|
|
height: Math.max(0, Math.min(root.chromeHeight + root.contentHeight, root.maximumHeight))
|
|
radius: Theme.cornerRadiusXL
|
|
|
|
Behavior on height {
|
|
enabled: root.animationsEnabled && !root.transitioning && root.section !== ""
|
|
NumberAnimation {
|
|
duration: Theme.expressiveDurations.expressiveFastSpatial
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.standard
|
|
}
|
|
}
|
|
color: CcMetrics.dialogColor
|
|
border.width: Theme.layerOutlineWidth
|
|
border.color: Theme.outlineMedium
|
|
property real entryScale: 1
|
|
scale: Math.min(1, entryScale)
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.AllButtons
|
|
}
|
|
|
|
Item {
|
|
id: panel
|
|
|
|
anchors.fill: parent
|
|
anchors.margins: CcMetrics.detailDialogPadding
|
|
opacity: CcMetrics.hideCoveredContent ? 1 - root.coverage : 1
|
|
|
|
Item {
|
|
id: header
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
height: CcMetrics.pageHeaderHeight
|
|
|
|
StyledText {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: CcMetrics.rowPaddingH
|
|
anchors.right: headerSlot.left
|
|
anchors.rightMargin: Theme.spacingM
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: root.title
|
|
font.pixelSize: CcMetrics.pageTitleSize
|
|
color: Theme.surfaceText
|
|
elide: Text.ElideRight
|
|
horizontalAlignment: Text.AlignLeft
|
|
}
|
|
|
|
Item {
|
|
id: headerSlot
|
|
anchors.right: closeButton.left
|
|
anchors.rightMargin: Theme.spacingS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: childrenRect.width
|
|
height: childrenRect.height
|
|
}
|
|
|
|
DActionButton {
|
|
id: closeButton
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: CcMetrics.headerEdgeInset
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
buttonSize: CcMetrics.headerActionSize
|
|
iconName: "close"
|
|
iconSize: CcMetrics.headerActionIconSize
|
|
iconColor: Theme.surfaceText
|
|
Accessible.name: I18n.tr("Close")
|
|
onClicked: root.backRequested()
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: pageLoader
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.top: header.bottom
|
|
anchors.bottom: parent.bottom
|
|
active: root.shownSection !== ""
|
|
onLoaded: {
|
|
const actions = item.headerActions ?? null;
|
|
if (actions)
|
|
actions.parent = headerSlot;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: exitBlocker
|
|
|
|
anchors.fill: parent
|
|
visible: root.section === ""
|
|
hoverEnabled: true
|
|
acceptedButtons: Qt.AllButtons
|
|
onWheel: wheel => wheel.accepted = true
|
|
Keys.onPressed: event => event.accepted = true
|
|
Keys.onReleased: event => event.accepted = true
|
|
}
|
|
|
|
Connections {
|
|
target: root.pageItem
|
|
ignoreUnknownSignals: true
|
|
|
|
function onMountPathChanged(newMountPath) {
|
|
Sections.updateWidgetForSection(root.shownSection, {
|
|
"mountPath": newMountPath
|
|
});
|
|
root.collapseRequested();
|
|
}
|
|
|
|
function onDeviceNameChanged(newDeviceName) {
|
|
Sections.updateWidgetForSection(root.shownSection, {
|
|
"deviceName": newDeviceName
|
|
});
|
|
}
|
|
|
|
function onShowCodecSelector(device) {
|
|
root.codecSelectorRequested(device);
|
|
}
|
|
|
|
function onShowPortSelector(node) {
|
|
root.portSelectorRequested(node);
|
|
}
|
|
|
|
function onDismissRequested() {
|
|
root.backRequested();
|
|
}
|
|
|
|
function onCloseRequested() {
|
|
root.closeRequested();
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: networkComponent
|
|
NetworkDetail {
|
|
transientSurfaceTracker: root.transientSurfaceTracker
|
|
transitioning: root.transitioning
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: bluetoothComponent
|
|
BluetoothDetail {
|
|
transientSurfaceTracker: root.transientSurfaceTracker
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: audioOutputComponent
|
|
AudioOutputDetail {}
|
|
}
|
|
|
|
Component {
|
|
id: audioInputComponent
|
|
AudioInputDetail {}
|
|
}
|
|
|
|
Component {
|
|
id: batteryComponent
|
|
BatteryDetail {}
|
|
}
|
|
|
|
Component {
|
|
id: dndComponent
|
|
DoNotDisturbDetail {}
|
|
}
|
|
|
|
Component {
|
|
id: idleInhibitComponent
|
|
IdleInhibitorDetail {}
|
|
}
|
|
|
|
Component {
|
|
id: diskUsageComponent
|
|
DiskUsageDetail {
|
|
readonly property var widgetEntry: Sections.widgetForSection(root.shownSection)
|
|
currentMountPath: widgetEntry?.mountPath || "/"
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: brightnessComponent
|
|
BrightnessDetail {
|
|
readonly property var widgetEntry: Sections.widgetForSection(root.shownSection)
|
|
initialDeviceName: widgetEntry?.deviceName || ""
|
|
instanceId: widgetEntry?.instanceId || ""
|
|
screenName: root.screenName
|
|
screenModel: root.screenModel
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: runningAppsComponent
|
|
RunningAppsDetail {
|
|
toplevels: root.runningToplevels
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: pluginComponent
|
|
PluginDetailShell {
|
|
pluginId: root.shownSection.startsWith("plugin_") ? root.shownSection.replace("plugin_", "") : ""
|
|
builtinInstance: root.shownSection.startsWith("builtin_") ? (root.model?.builtinInstances[root.shownSection] ?? null) : null
|
|
}
|
|
}
|
|
}
|