mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-28 14:07:47 -04:00
336 lines
11 KiB
QML
336 lines
11 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 maximumHeight: 0
|
|
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: ""
|
|
readonly property var pageItem: pageLoader.item
|
|
readonly property bool pluginPage: shownSection.startsWith("plugin_") || shownSection.startsWith("builtin_")
|
|
// A page measures itself a tick after it loads; waiting for that keeps the first target the final one.
|
|
readonly property real pageHeight: {
|
|
const own = pageItem?.implicitHeight ?? 0;
|
|
if (own > 0)
|
|
return own;
|
|
if (!pluginPage)
|
|
return 0;
|
|
const preferred = pageItem?.preferredHeight ?? 0;
|
|
return preferred > 0 ? preferred : CcMetrics.detailHeightDefault;
|
|
}
|
|
// The band's extent includes the gap above and below its card, so they grow and shrink with it.
|
|
readonly property real chromeHeight: CcMetrics.gridGap * 2 + CcMetrics.detailHeaderHeight + CcMetrics.detailDialogPadding * 2
|
|
readonly property real contentCap: Math.max(CcMetrics.detailHeightFloor(shownSection, pageItem?.preferredHeight ?? 0), maximumHeight - chromeHeight)
|
|
readonly property real targetHeight: section === "" || pageHeight <= 0 ? 0 : chromeHeight + Math.min(pageHeight, contentCap)
|
|
readonly property bool transitioning: heightAnimation.running
|
|
// Only IPC can ask for a plugin section whose tile is not placed, so there is nothing to show.
|
|
readonly property bool pageEmpty: pageItem?.empty === true
|
|
readonly property string title: {
|
|
const own = pageItem?.title ?? "";
|
|
if (own)
|
|
return own;
|
|
const parsed = Sections.parse(shownSection);
|
|
return model?.getWidgetForId(parsed.base)?.text ?? "";
|
|
}
|
|
|
|
height: targetHeight
|
|
visible: shownSection !== ""
|
|
clip: true
|
|
Accessible.role: Accessible.Grouping
|
|
Accessible.name: title
|
|
|
|
// Same timing as the popout's own resize, so the surface and the content move as one.
|
|
Behavior on height {
|
|
enabled: CcMetrics.animationsEnabled
|
|
NumberAnimation {
|
|
id: heightAnimation
|
|
duration: CcMetrics.transitionDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.standard
|
|
}
|
|
}
|
|
|
|
onPageEmptyChanged: {
|
|
if (pageEmpty && section !== "")
|
|
Qt.callLater(collapseRequested);
|
|
}
|
|
|
|
function finishClose() {
|
|
if (section !== "" || shownSection === "")
|
|
return;
|
|
pageLoader.sourceComponent = null;
|
|
shownSection = "";
|
|
dismissed();
|
|
}
|
|
|
|
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: {
|
|
if (section === "") {
|
|
if (!CcMetrics.animationsEnabled)
|
|
finishClose();
|
|
return;
|
|
}
|
|
shownSection = section;
|
|
pageLoader.sourceComponent = _componentFor(section);
|
|
(pageItem ?? root).forceActiveFocus();
|
|
}
|
|
|
|
onHeightChanged: {
|
|
if (section === "" && height <= 0.5)
|
|
finishClose();
|
|
}
|
|
|
|
Rectangle {
|
|
y: CcMetrics.gridGap
|
|
width: parent.width
|
|
height: Math.max(0, parent.height - CcMetrics.gridGap * 2)
|
|
radius: CcMetrics.tallTileRadius
|
|
color: CcMetrics.dialogColor
|
|
border.width: Theme.layerOutlineWidth
|
|
border.color: Theme.outlineMedium
|
|
|
|
Item {
|
|
id: panel
|
|
|
|
anchors.fill: parent
|
|
anchors.margins: CcMetrics.detailDialogPadding
|
|
opacity: root.section !== "" ? (CcMetrics.hideCoveredContent ? 1 - root.coverage : 1) : 0
|
|
|
|
Behavior on opacity {
|
|
enabled: CcMetrics.animationsEnabled
|
|
NumberAnimation {
|
|
duration: CcMetrics.fadeDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: header
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
height: CcMetrics.detailHeaderHeight
|
|
|
|
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.detailTitleSize
|
|
font.weight: Theme.fontWeightMedium
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|