From d436fa4920c836816e9041ecba0359da74a99aa9 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 10 Mar 2026 15:48:13 +0100 Subject: [PATCH] fix(quickshell): stabilize control center numeric widths (#1943) --- .../DankBar/Widgets/ControlCenterButton.qml | 29 +++++++++++++------ quickshell/Widgets/DankSlider.qml | 16 ++++++++-- quickshell/Widgets/NumericText.qml | 22 ++++++++++++++ 3 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 quickshell/Widgets/NumericText.qml diff --git a/quickshell/Modules/DankBar/Widgets/ControlCenterButton.qml b/quickshell/Modules/DankBar/Widgets/ControlCenterButton.qml index 7bb10cd1..2d5077f2 100644 --- a/quickshell/Modules/DankBar/Widgets/ControlCenterButton.qml +++ b/quickshell/Modules/DankBar/Widgets/ControlCenterButton.qml @@ -390,10 +390,11 @@ BasePill { anchors.top: parent.top } - StyledText { + NumericText { id: audioPercentV visible: root.showAudioPercent text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%" + reserveText: "100%" font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) color: Theme.widgetTextColor anchors.horizontalCenter: parent.horizontalCenter @@ -416,10 +417,11 @@ BasePill { anchors.top: parent.top } - StyledText { + NumericText { id: micPercentV visible: root.showMicPercent text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%" + reserveText: "100%" font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) color: Theme.widgetTextColor anchors.horizontalCenter: parent.horizontalCenter @@ -442,10 +444,11 @@ BasePill { anchors.top: parent.top } - StyledText { + NumericText { id: brightnessPercentV visible: root.showBrightnessPercent text: Math.round(getBrightness() * 100) + "%" + reserveText: "100%" font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) color: Theme.widgetTextColor anchors.horizontalCenter: parent.horizontalCenter @@ -536,7 +539,8 @@ BasePill { } Rectangle { - width: audioIcon.implicitWidth + (root.showAudioPercent ? audioPercent.implicitWidth : 0) + 4 + width: audioIcon.implicitWidth + (root.showAudioPercent ? audioPercent.reservedWidth : 0) + 4 + implicitWidth: width height: root.widgetThickness - root.horizontalPadding * 2 color: "transparent" anchors.verticalCenter: parent.verticalCenter @@ -552,20 +556,23 @@ BasePill { anchors.leftMargin: 2 } - StyledText { + NumericText { id: audioPercent visible: root.showAudioPercent text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%" + reserveText: "100%" font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) color: Theme.widgetTextColor anchors.verticalCenter: parent.verticalCenter anchors.left: audioIcon.right anchors.leftMargin: 2 + width: reservedWidth } } Rectangle { - width: micIcon.implicitWidth + (root.showMicPercent ? micPercent.implicitWidth : 0) + 4 + width: micIcon.implicitWidth + (root.showMicPercent ? micPercent.reservedWidth : 0) + 4 + implicitWidth: width height: root.widgetThickness - root.horizontalPadding * 2 color: "transparent" anchors.verticalCenter: parent.verticalCenter @@ -581,20 +588,22 @@ BasePill { anchors.leftMargin: 2 } - StyledText { + NumericText { id: micPercent visible: root.showMicPercent text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%" + reserveText: "100%" font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) color: Theme.widgetTextColor anchors.verticalCenter: parent.verticalCenter anchors.left: micIcon.right anchors.leftMargin: 2 + width: reservedWidth } } Rectangle { - width: brightnessIcon.implicitWidth + (root.showBrightnessPercent ? brightnessPercent.implicitWidth : 0) + 4 + width: brightnessIcon.implicitWidth + (root.showBrightnessPercent ? brightnessPercent.reservedWidth : 0) + 4 height: root.widgetThickness - root.horizontalPadding * 2 color: "transparent" anchors.verticalCenter: parent.verticalCenter @@ -610,15 +619,17 @@ BasePill { anchors.leftMargin: 2 } - StyledText { + NumericText { id: brightnessPercent visible: root.showBrightnessPercent text: Math.round(getBrightness() * 100) + "%" + reserveText: "100%" font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) color: Theme.widgetTextColor anchors.verticalCenter: parent.verticalCenter anchors.left: brightnessIcon.right anchors.leftMargin: 2 + width: reservedWidth } } diff --git a/quickshell/Widgets/DankSlider.qml b/quickshell/Widgets/DankSlider.qml index 538c0a9f..f91a75e3 100644 --- a/quickshell/Widgets/DankSlider.qml +++ b/quickshell/Widgets/DankSlider.qml @@ -239,7 +239,7 @@ Item { StyledRect { id: valueTooltip - width: tooltipText.contentWidth + Theme.spacingS * 2 + width: tooltipText.reservedWidth + Theme.spacingS * 2 height: tooltipText.contentHeight + Theme.spacingXS * 2 radius: Theme.cornerRadius color: Theme.surfaceContainer @@ -251,10 +251,22 @@ Item { visible: slider.alwaysShowValue ? slider.showValue : ((sliderMouseArea.containsMouse && slider.showValue) || (slider.isDragging && slider.showValue)) opacity: visible ? 1 : 0 - StyledText { + NumericText { id: tooltipText text: (slider.valueOverride >= 0 ? Math.round(slider.valueOverride) : slider.value) + slider.unit + reserveText: { + let widest = ""; + const samples = [slider.minimum, slider.maximum]; + if (slider.valueOverride >= 0) + samples.push(slider.valueOverride); + for (let i = 0; i < samples.length; i++) { + const candidate = Math.round(samples[i]) + slider.unit; + if (candidate.length > widest.length) + widest = candidate; + } + return widest; + } font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceText font.weight: Font.Medium diff --git a/quickshell/Widgets/NumericText.qml b/quickshell/Widgets/NumericText.qml new file mode 100644 index 00000000..fc78c2df --- /dev/null +++ b/quickshell/Widgets/NumericText.qml @@ -0,0 +1,22 @@ +import QtQuick +import qs.Common + +StyledText { + id: root + + property string reserveText: "" + readonly property real reservedWidth: reserveText !== "" ? Math.max(contentWidth, reserveMetrics.width) : contentWidth + + isMonospace: true + wrapMode: Text.NoWrap + + StyledTextMetrics { + id: reserveMetrics + isMonospace: root.isMonospace + font.pixelSize: root.font.pixelSize + font.family: root.font.family + font.weight: root.font.weight + font.hintingPreference: root.font.hintingPreference + text: root.reserveText + } +}