1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-03 20:32:07 -04:00

Add disk usage component for TopBar and ControlCenter

This commit is contained in:
bbedward
2025-09-24 18:06:46 -04:00
parent 9be7d44765
commit b893694977
14 changed files with 676 additions and 12 deletions

View File

@@ -6,12 +6,14 @@ Item {
id: root
property string expandedSection: ""
property var expandedWidgetData: null
Loader {
width: parent.width
height: 250
y: Theme.spacingS
active: parent.height > 0
property string sectionKey: root.expandedSection
sourceComponent: {
switch (root.expandedSection) {
case "network":
@@ -20,9 +22,17 @@ Item {
case "audioOutput": return audioOutputDetailComponent
case "audioInput": return audioInputDetailComponent
case "battery": return batteryDetailComponent
default: return null
default:
if (root.expandedSection.startsWith("diskUsage_")) {
return diskUsageDetailComponent
}
return null
}
}
onSectionKeyChanged: {
active = false
active = true
}
}
Component {
@@ -49,4 +59,28 @@ Item {
id: batteryDetailComponent
BatteryDetail {}
}
Component {
id: diskUsageDetailComponent
DiskUsageDetail {
currentMountPath: root.expandedWidgetData?.mountPath || "/"
instanceId: root.expandedWidgetData?.instanceId || ""
onMountPathChanged: (newMountPath) => {
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
const widgets = SettingsData.controlCenterWidgets || []
const newWidgets = widgets.map(w => {
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w)
updatedWidget.mountPath = newMountPath
return updatedWidget
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
}
}
}
}
}

View File

@@ -12,6 +12,7 @@ Column {
property string expandedSection: ""
property int expandedWidgetIndex: -1
property var model: null
property var expandedWidgetData: null
signal expandClicked(var widgetData, int globalIndex)
signal removeWidget(int index)
@@ -28,12 +29,17 @@ Column {
return LayoutUtils.calculateRowsAndWidgets(root, expandedSection, expandedWidgetIndex)
}
property var layoutResult: {
const dummy = [expandedSection, expandedWidgetIndex, model?.controlCenterWidgets]
return calculateRowsAndWidgets()
}
onLayoutResultChanged: {
expandedRowIndex = layoutResult.expandedRowIndex
}
Repeater {
model: {
const result = root.calculateRowsAndWidgets()
root.expandedRowIndex = result.expandedRowIndex
return result.rows
}
model: root.layoutResult.rows
Column {
width: root.width
@@ -102,6 +108,8 @@ Column {
return inputAudioSliderComponent
} else if (id === "battery") {
return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent
} else if (id === "diskUsage") {
return diskUsagePillComponent
} else {
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent
}
@@ -115,9 +123,19 @@ Column {
DetailHost {
width: parent.width
height: active ? (250 + Theme.spacingS) : 0
property bool active: root.expandedSection !== "" && rowIndex === root.expandedRowIndex
property bool active: {
if (root.expandedSection === "") return false
if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) {
const expandedInstanceId = root.expandedWidgetData.instanceId
return rowWidgets.some(w => w.id === "diskUsage" && w.instanceId === expandedInstanceId)
}
return rowIndex === root.expandedRowIndex
}
visible: active
expandedSection: root.expandedSection
expandedWidgetData: root.expandedWidgetData
}
}
}
@@ -676,4 +694,35 @@ Column {
}
}
}
Component {
id: diskUsagePillComponent
DiskUsagePill {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
width: parent.width
height: 60
mountPath: widgetData.mountPath || "/"
instanceId: widgetData.instanceId || ""
onExpandClicked: {
if (!root.editMode) {
root.expandClicked(widgetData, widgetIndex)
}
}
EditModeOverlay {
anchors.fill: parent
editMode: root.editMode
widgetData: parent.widgetData
widgetIndex: parent.widgetIndex
showSizeControls: true
isSlider: false
onRemoveWidget: (index) => root.removeWidget(index)
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
}
}
}
}

View File

@@ -26,10 +26,29 @@ DankPopout {
property var triggerScreen: null
property bool editMode: false
property int expandedWidgetIndex: -1
property var expandedWidgetData: null
signal powerActionRequested(string action, string title, string message)
signal lockRequested
function collapseAll() {
expandedSection = ""
expandedWidgetIndex = -1
expandedWidgetData = null
}
onEditModeChanged: {
if (editMode) {
collapseAll()
}
}
onVisibleChanged: {
if (!visible) {
collapseAll()
}
}
readonly property color _containerBg: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60)
function setTriggerPosition(x, y, width, section, screen) {
@@ -132,10 +151,16 @@ DankPopout {
editMode: root.editMode
expandedSection: root.expandedSection
expandedWidgetIndex: root.expandedWidgetIndex
expandedWidgetData: root.expandedWidgetData
model: widgetModel
onExpandClicked: (widgetData, globalIndex) => {
root.expandedWidgetIndex = globalIndex
root.toggleSection(widgetData.id)
root.expandedWidgetData = widgetData
if (widgetData.id === "diskUsage") {
root.toggleSection("diskUsage_" + (widgetData.instanceId || "default"))
} else {
root.toggleSection(widgetData.id)
}
}
onRemoveWidget: (index) => widgetModel.removeWidget(index)
onMoveWidget: (fromIndex, toIndex) => widgetModel.moveWidget(fromIndex, toIndex)
@@ -147,7 +172,7 @@ DankPopout {
visible: editMode
availableWidgets: {
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id)
return widgetModel.baseWidgetDefinitions.filter(w => !existingIds.includes(w.id))
return widgetModel.baseWidgetDefinitions.filter(w => w.allowMultiple || !existingIds.includes(w.id))
}
onAddWidget: (widgetId) => widgetModel.addWidget(widgetId)
onResetToDefault: () => widgetModel.resetToDefault()

View File

@@ -0,0 +1,169 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
property string currentMountPath: "/"
property string instanceId: ""
signal mountPathChanged(string newMountPath)
implicitHeight: diskContent.height + Theme.spacingM
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
Component.onCompleted: {
DgopService.addRef(["diskmounts"])
}
Component.onDestruction: {
DgopService.removeRef(["diskmounts"])
}
DankFlickable {
id: diskContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM
contentHeight: diskColumn.height
clip: true
Column {
id: diskColumn
width: parent.width
spacing: Theme.spacingS
Item {
width: parent.width
height: 100
visible: !DgopService.dgopAvailable || !DgopService.diskMounts || DgopService.diskMounts.length === 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingM
DankIcon {
anchors.horizontalCenter: parent.horizontalCenter
name: DgopService.dgopAvailable ? "storage" : "error"
size: 32
color: DgopService.dgopAvailable ? Theme.primary : Theme.error
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: DgopService.dgopAvailable ? "No disk data available" : "dgop not available"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
}
}
}
Repeater {
model: DgopService.diskMounts || []
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 80
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, index % 2 === 0 ? 0.3 : 0.2)
border.color: modelData.mount === currentMountPath ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData.mount === currentMountPath ? 2 : 1
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingM
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
DankIcon {
name: "storage"
size: Theme.iconSize
color: {
const percentStr = modelData.percent?.replace("%", "") || "0"
const percent = parseFloat(percentStr) || 0
if (percent > 90) return Theme.error
if (percent > 75) return Theme.warning
return modelData.mount === currentMountPath ? Theme.primary : Theme.surfaceText
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
const percentStr = modelData.percent?.replace("%", "") || "0"
const percent = parseFloat(percentStr) || 0
return percent.toFixed(0) + "%"
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - 50 - Theme.spacingM
StyledText {
text: modelData.mount === "/" ? "Root Filesystem" : modelData.mount
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: modelData.mount === currentMountPath ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.mount
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
visible: modelData.mount !== "/"
}
StyledText {
text: `${modelData.used || "?"} / ${modelData.size || "?"}`
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData.mount !== currentMountPath) {
currentMountPath = modelData.mount
mountPathChanged(modelData.mount)
}
}
}
Behavior on border.color {
ColorAnimation { duration: Theme.shortDuration }
}
}
}
}
}
}

View File

@@ -106,6 +106,16 @@ QtObject {
"icon": "battery_std",
"type": "action",
"enabled": true
},
{
"id": "diskUsage",
"text": "Disk Usage",
"description": "Filesystem usage monitoring",
"icon": "storage",
"type": "action",
"enabled": DgopService.dgopAvailable,
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
"allowMultiple": true
}
]

View File

@@ -0,0 +1,78 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property string mountPath: "/"
property string instanceId: ""
iconName: "storage"
property var selectedMount: {
if (!DgopService.diskMounts || DgopService.diskMounts.length === 0) {
return null
}
const targetMount = DgopService.diskMounts.find(mount => mount.mount === mountPath)
return targetMount || DgopService.diskMounts.find(mount => mount.mount === "/") || DgopService.diskMounts[0]
}
property real usagePercent: {
if (!selectedMount || !selectedMount.percent) {
return 0
}
const percentStr = selectedMount.percent.replace("%", "")
return parseFloat(percentStr) || 0
}
isActive: DgopService.dgopAvailable && selectedMount !== null
primaryText: {
if (!DgopService.dgopAvailable) {
return "Disk Usage"
}
if (!selectedMount) {
return "No disk data"
}
return `Disk Usage ${selectedMount.mount}`
}
secondaryText: {
if (!DgopService.dgopAvailable) {
return "dgop not available"
}
if (!selectedMount) {
return "No disk data available"
}
return `${selectedMount.used} / ${selectedMount.size} (${usagePercent.toFixed(0)}%)`
}
iconColor: {
if (!DgopService.dgopAvailable || !selectedMount) {
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
}
if (usagePercent > 90) {
return Theme.error
}
if (usagePercent > 75) {
return Theme.warning
}
return Theme.surfaceText
}
Component.onCompleted: {
DgopService.addRef(["diskmounts"])
}
Component.onDestruction: {
DgopService.removeRef(["diskmounts"])
}
onToggled: {
expandClicked()
}
}

View File

@@ -32,7 +32,7 @@ function calculateRowsAndWidgets(controlCenterColumn, expandedSection, expandedW
currentWidth += (currentRow.length > 1 ? spacing : 0) + itemWidth
}
if (widget.id === expandedSection && expandedWidgetIndex === i) {
if (expandedWidgetIndex === i) {
expandedRow = rows.length
}
}

View File

@@ -9,10 +9,20 @@ function addWidget(widgetId) {
"enabled": true,
"width": 50
}
if (widgetId === "diskUsage") {
widget.instanceId = generateUniqueId()
widget.mountPath = "/"
}
widgets.push(widget)
SettingsData.setControlCenterWidgets(widgets)
}
function generateUniqueId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2)
}
function removeWidget(index) {
var widgets = SettingsData.controlCenterWidgets.slice()
if (index >= 0 && index < widgets.length) {