From 6e433e4a39df8040309340ae3df6a32502d6b489 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 15 Mar 2026 13:17:40 +0100 Subject: [PATCH] hefty: bar: resources indicator: add more stats --- .../ii/modules/common/config/HeftyConfig.qml | 11 +- .../widgets/CombinedCircularProgress.qml | 75 +++++++ .../modules/common/widgets/ConfigSwitch.qml | 1 + .../modules/common/widgets/StyledButton.qml | 7 + .../widgets/StyledCombinedProgressBar.qml | 9 +- .../hefty/topLayer/bar/HBarGroupContainer.qml | 8 +- .../hefty/topLayer/bar/widgets/HResources.qml | 187 +++++++++++++++++- 7 files changed, 283 insertions(+), 15 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/common/widgets/CombinedCircularProgress.qml diff --git a/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml b/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml index 6284263d0..2275e3704 100644 --- a/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml +++ b/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml @@ -4,11 +4,18 @@ import Quickshell.Io JsonObject { property JsonObject bar: JsonObject { - property list leftWidgets: ["HWindowInfo"] + property list leftWidgets: ["HLeftSidebarButton"] property list centerLeftWidgets: ["HTime"] property list centerWidgets: ["HWorkspaces"] property list centerRightWidgets: ["HResources"] - property list rightWidgets: [] + property list rightWidgets: ["HSystemIndicators"] property bool m3ExpressiveGrouping: true + + property JsonObject resources: JsonObject { + property bool showMemory: false + property bool showRam: false + property bool showSwap: false + property bool showCpu: false + } } } diff --git a/dots/.config/quickshell/ii/modules/common/widgets/CombinedCircularProgress.qml b/dots/.config/quickshell/ii/modules/common/widgets/CombinedCircularProgress.qml new file mode 100644 index 000000000..ed8b4808f --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/CombinedCircularProgress.qml @@ -0,0 +1,75 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Shapes +import qs.modules.common + +AbstractCombinedProgressBar { + id: root + + property int implicitSize: 30 + property int lineWidth: 2 + property real gapAngle: 360 / 18 + + valueHighlights: [Appearance.colors.colPrimary, Appearance.colors.colTertiary] + valueTroughs: [Appearance.colors.colSecondaryContainer, Appearance.colors.colTertiaryContainer] + + property bool enableAnimation: true + property int animationDuration: 800 + property var easingType: Easing.OutCubic + + implicitWidth: implicitSize + implicitHeight: implicitSize + + readonly property real centerX: root.width / 2 + readonly property real centerY: root.height / 2 + readonly property real arcRadius: root.implicitSize / 2 - root.lineWidth + readonly property real startAngle: -90 + + background: Item { + implicitWidth: root.implicitSize + implicitHeight: root.implicitSize + } + + function isNegligibleSegment(seg: var): bool { + const range = seg[1] - seg[0]; + return range < 1 / 360; // TODO make this less arbitrary + } + + Repeater { + model: root.visualSegments + delegate: Shape { + id: segShape + required property int index + required property var modelData + + property bool negligible: root.isNegligibleSegment(modelData) + property bool atStart: index == 0 + property bool atEnd: index == root.visualSegments.length - 1 + property real displaySegStart: { + var i = index; + while ((i > 0 && root.isNegligibleSegment(root.visualSegments[i - 1]))) + i--; + return root.visualSegments[i][0]; + } + + anchors.fill: parent + layer.enabled: true + layer.smooth: true + preferredRendererType: Shape.CurveRenderer + ShapePath { + strokeColor: segShape.negligible ? "transparent" : root.segmentColors[segShape.index % root.segmentColors.length] + strokeWidth: segShape.negligible ? 0 : root.lineWidth + capStyle: ShapePath.RoundCap + fillColor: "transparent" + PathAngleArc { + centerX: root.centerX + centerY: root.centerY + radiusX: root.arcRadius + radiusY: root.arcRadius + startAngle: root.startAngle + 360 * segShape.displaySegStart + root.gapAngle / 2 + sweepAngle: 360 * (segShape.modelData[1] - segShape.displaySegStart) - root.gapAngle + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/ConfigSwitch.qml b/dots/.config/quickshell/ii/modules/common/widgets/ConfigSwitch.qml index 53e6a27c0..d1e2baf4d 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/ConfigSwitch.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/ConfigSwitch.qml @@ -33,6 +33,7 @@ RippleButton { } StyledSwitch { id: switchWidget + focusPolicy: Qt.NoFocus down: root.down Layout.fillWidth: false checked: root.checked diff --git a/dots/.config/quickshell/ii/modules/common/widgets/StyledButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/StyledButton.qml index ebd176a5a..18ec4c853 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/StyledButton.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/StyledButton.qml @@ -22,6 +22,13 @@ Button { hoverEnabled: true opacity: root.enabled ? 1 : 0.5 + Behavior on colBackground { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + Behavior on colForeground { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + HoverHandler { cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor } diff --git a/dots/.config/quickshell/ii/modules/common/widgets/StyledCombinedProgressBar.qml b/dots/.config/quickshell/ii/modules/common/widgets/StyledCombinedProgressBar.qml index 15478a145..5f9e9f478 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/StyledCombinedProgressBar.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/StyledCombinedProgressBar.qml @@ -33,10 +33,6 @@ AbstractCombinedProgressBar { required property var modelData visible: !root.isNegligibleSegment(modelData) - anchors { - top: parent.top - bottom: parent.bottom - } property bool atStart: index == 0 property bool atEnd: index == root.visualSegments.length - 1 property real displaySegStart: { // swallow previous segments if they're "negligible" @@ -46,6 +42,11 @@ AbstractCombinedProgressBar { return root.visualSegments[i][0] } + anchors { + top: parent.top + bottom: parent.bottom + } + x: { var result = root.availableWidth * displaySegStart; if (!atStart) result += root.valueBarGap / 2; diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarGroupContainer.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarGroupContainer.qml index 91202f218..e2b013263 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarGroupContainer.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarGroupContainer.qml @@ -56,7 +56,13 @@ Item { property Item contentItem: GridLayout { id: layout columns: C.Config.options.bar.vertical ? 1 : -1 - anchors.centerIn: parent + anchors { + fill: parent + leftMargin: root.vertical ? 0 : root.padding + rightMargin: root.vertical ? 0 : root.padding + topMargin: root.vertical ? root.padding : 0 + bottomMargin: root.vertical ? root.padding : 0 + } property real spacing: 4 columnSpacing: spacing rowSpacing: spacing diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HResources.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HResources.qml index 676aa6421..08960fc33 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HResources.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HResources.qml @@ -54,19 +54,92 @@ HBarWidgetWithPopout { W.BoxLayout { id: contentGrid vertical: root.vertical - anchors.fill: parent + property int visibleChildren: children.filter(child => child.shown).length + property bool hasResourceIndicators: visibleChildren > 1 || (visibleChildren > 0 && !S.Battery.available) + anchors { + fill: parent + leftMargin: !root.vertical ? (hasResourceIndicators ? 1 : 4) : 0 + topMargin: root.vertical ? 2 : 0 + rightMargin: !root.vertical ? (root.endSide ? 1 : (battLoader.visible ? 0 : -3)) : 0 + bottomMargin: root.vertical ? (root.endSide ? 4 : 2) : 0 + } + spacing: 4 - Battery { - Layout.leftMargin: !root.vertical ? (root.startSide ? 8 : 6) : 0 - Layout.rightMargin: !root.vertical ? (root.endSide ? 0 : -3) : 0 - Layout.bottomMargin: root.vertical ? (root.endSide ? 4 : 2) : 0 - Layout.topMargin: root.vertical ? 2 : 0 + AlignedFadeLoader { + shown: C.Config.options.hefty.bar.resources.showMemory || ( + !battLoader.visible + && !C.Config.options.hefty.bar.resources.showRam + && !C.Config.options.hefty.bar.resources.showSwap + && !C.Config.options.hefty.bar.resources.showCpu + ) + sourceComponent: Memory {} + } + + AlignedFadeLoader { + shown: C.Config.options.hefty.bar.resources.showRam + sourceComponent: RamOnly {} + } + + AlignedFadeLoader { + shown: C.Config.options.hefty.bar.resources.showSwap + sourceComponent: SwapOnly {} + } + + AlignedFadeLoader { + shown: C.Config.options.hefty.bar.resources.showCpu + sourceComponent: Cpu {} + } + + AlignedFadeLoader { + id: battLoader + shown: S.Battery.available Layout.fillWidth: root.vertical Layout.fillHeight: !root.vertical + sourceComponent: Battery {} } } } + component AlignedFadeLoader: W.FadeLoader { + Layout.alignment: root.vertical ? Qt.AlignHCenter : Qt.AlignVCenter + } + + component Memory: SysResourceProgress { + valueWeights: [S.ResourceUsage.memoryTotal, S.ResourceUsage.swapTotal] + values: [S.ResourceUsage.memoryUsedPercentage, S.ResourceUsage.swapUsedPercentage] + centerChar: S.Translation.tr("Memory")[0] + } + + component RamOnly: SysResourceProgress { + values: [S.ResourceUsage.memoryUsedPercentage] + centerChar: S.Translation.tr("RAM")[0] + } + + component SwapOnly: SysResourceProgress { + values: [S.ResourceUsage.swapUsedPercentage] + centerChar: S.Translation.tr("Swap")[0] + } + + component Cpu: SysResourceProgress { + values: [S.ResourceUsage.cpuUsage] + centerChar: S.Translation.tr("CPU")[0] + } + + component SysResourceProgress: W.CombinedCircularProgress { + id: sysResProg + implicitSize: 22 + valueHighlights: [C.Appearance.colors.colPrimary] + valueTroughs: [C.Appearance.colors.colSecondaryContainer] + property string centerChar: "" + + W.StyledText { + renderType: Text.QtRendering + anchors.centerIn: parent + text: sysResProg.centerChar + font.pixelSize: 9 + } + } + component Battery: Item { implicitWidth: !root.vertical ? battShape.implicitWidth : battShape.implicitHeight implicitHeight: !root.vertical ? battShape.implicitHeight : battShape.implicitWidth @@ -105,7 +178,7 @@ HBarWidgetWithPopout { bottomMargin: (parent.height - height) / 2 } rotation: 180 * root.vertical - spacing: 0 + spacing: 4 W.MaterialSymbol { id: boltIcon @@ -125,6 +198,7 @@ HBarWidgetWithPopout { return ""; } iconSize: C.Appearance.font.pixelSize.small + renderType: Text.QtRendering // Better than Native for small sizes font.weight: Font.DemiBold visible: text != "" } @@ -149,9 +223,12 @@ HBarWidgetWithPopout { } component SysInfoPopupContent: W.ChoreographerLoader { + id: sysInfoPopupContent + property bool showSettings: false + sourceComponent: W.ChoreographerGridLayout { id: popupRoot - rowSpacing: 8 + rowSpacing: 10 onShownChanged: { if (shown) { @@ -159,6 +236,51 @@ HBarWidgetWithPopout { } } + W.FlyFadeEnterChoreographable { + z: 1 + Layout.fillWidth: true + Item { + anchors { + left: parent.left + right: parent.right + } + implicitHeight: popupHeaderLayout.implicitHeight + ColumnLayout { + anchors { + top: parent.top + left: parent.left + right: parent.right + } + spacing: 10 + + RowLayout { + id: popupHeaderLayout + Layout.fillWidth: true + W.StyledText { + Layout.fillWidth: true + text: S.Translation.tr("Resources") + elide: Text.ElideRight + font.pixelSize: C.Appearance.font.pixelSize.title + } + + W.StyledIconButton { + implicitSize: C.Appearance.rounding.normal * 2 + text: "settings" + iconSize: 20 + onClicked: sysInfoPopupContent.showSettings = !sysInfoPopupContent.showSettings; + checked: sysInfoPopupContent.showSettings + } + } + + W.FadeLoader { + shown: sysInfoPopupContent.showSettings + Layout.fillWidth: true + sourceComponent: SysInfoPopupConfig {} + } + } + } + } + W.FlyFadeEnterChoreographable { Layout.fillWidth: true @@ -354,6 +476,55 @@ HBarWidgetWithPopout { } } + component SysInfoPopupConfig: Rectangle { + implicitWidth: sysConfLayout.implicitWidth + implicitHeight: sysConfLayout.implicitHeight + radius: C.Appearance.rounding.normal + color: C.Appearance.colors.colLayer4Base + + W.StyledRectangularShadow { + target: parent + z: -1 + } + + ColumnLayout { + id: sysConfLayout + anchors.fill: parent + W.ConfigSwitch { + buttonIcon: "pie_chart" + text: S.Translation.tr("Show memory usage") + checked: C.Config.options.hefty.bar.resources.showMemory + onCheckedChanged: { + C.Config.options.hefty.bar.resources.showMemory = checked; + } + } + W.ConfigSwitch { + buttonIcon: "memory" + text: S.Translation.tr("Show physical memory usage") + checked: C.Config.options.hefty.bar.resources.showRam + onCheckedChanged: { + C.Config.options.hefty.bar.resources.showRam = checked; + } + } + W.ConfigSwitch { + buttonIcon: "swap_horiz" + text: S.Translation.tr("Show swap") + checked: C.Config.options.hefty.bar.resources.showSwap + onCheckedChanged: { + C.Config.options.hefty.bar.resources.showSwap = checked; + } + } + W.ConfigSwitch { + buttonIcon: "planner_review" + text: S.Translation.tr("Show CPU usage") + checked: C.Config.options.hefty.bar.resources.showCpu + onCheckedChanged: { + C.Config.options.hefty.bar.resources.showCpu = checked; + } + } + } + } + component BigSmallTextPair: RowLayout { id: txtPair property string materialSymbol: ""