From d35ed0d7bb053ca8bac461345d17cf9dbe425931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Atoch?= Date: Thu, 31 Jul 2025 08:25:32 -0400 Subject: [PATCH] Battery widget rework - Convert the icon to a PillIndicator - Better looking horizontal battery icons - PillIndicator now supports conditionnal autoHide --- Bar/Modules/Battery.qml | 137 +++++++++++++++++------------------ Components/PillIndicator.qml | 53 +++++++++----- 2 files changed, 102 insertions(+), 88 deletions(-) diff --git a/Bar/Modules/Battery.qml b/Bar/Modules/Battery.qml index 899c829..9f57ee3 100644 --- a/Bar/Modules/Battery.qml +++ b/Bar/Modules/Battery.qml @@ -1,12 +1,13 @@ import QtQuick +import Quickshell import Quickshell.Services.UPower import QtQuick.Layouts -import qs.Settings import qs.Components +import qs.Settings Item { id: batteryWidget - + property var battery: UPower.displayDevice property bool isReady: battery && battery.ready && battery.isLaptopBattery && battery.isPresent property real percent: isReady ? (battery.percentage * 100) : 0 @@ -15,81 +16,79 @@ Item { // Choose icon based on charge and charging state function batteryIcon() { - if (!show) return ""; - - // Show charging icons with lightning when charging - if (charging) { - if (percent >= 95) return "battery_charging_full"; - if (percent >= 80) return "battery_charging_80"; - if (percent >= 60) return "battery_charging_60"; - if (percent >= 50) return "battery_charging_50"; - if (percent >= 30) return "battery_charging_30"; - if (percent >= 20) return "battery_charging_20"; - return "battery_charging_20"; // Use charging_20 for very low battery - } - - // Regular battery icons when not charging - if (percent >= 95) return "battery_full"; - if (percent >= 80) return "battery_80"; - if (percent >= 60) return "battery_60"; - if (percent >= 50) return "battery_50"; - if (percent >= 30) return "battery_30"; - if (percent >= 20) return "battery_20"; - return "battery_alert"; + if (!show) + return ""; + + if (charging) + return "battery_android_bolt"; + + if (percent >= 95) + return "battery_android_full"; + + var step = Math.round(percent / (100 / 6)); + return "battery_android_" + step } visible: isReady && battery.isLaptopBattery - width: 22 - height: 36 + width: pill.width + height: pill.height - RowLayout { - anchors.fill: parent - spacing: 4 - visible: show - Item { - height: 22 - width: 22 - Text { - text: batteryIcon() - font.family: "Material Symbols Outlined" - font.pixelSize: 14 - color: charging ? Theme.accentPrimary : Theme.textPrimary - verticalAlignment: Text.AlignVCenter - anchors.centerIn: parent + PillIndicator { + id: pill + icon: batteryIcon() + text: Math.round(batteryWidget.percent) + "%" + pillColor: Theme.surfaceVariant + iconCircleColor: Theme.accentPrimary + textColor: charging ? Theme.accentPrimary : Theme.textPrimary + autoHide: false + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + pill.show(); + batteryTooltip.tooltipVisible = true; } - MouseArea { - id: batteryMouseArea - anchors.fill: parent - hoverEnabled: true - onEntered: batteryWidget.containsMouse = true - onExited: batteryWidget.containsMouse = false - cursorShape: Qt.PointingHandCursor + onExited: { + pill.hide(); + batteryTooltip.tooltipVisible = false; } } + StyledTooltip { + id: batteryTooltip + text: { + let lines = []; + if (batteryWidget.isReady) { + lines.push(batteryWidget.charging ? "Charging" : "Discharging"); + lines.push(Math.round(batteryWidget.percent) + "%"); + if (batteryWidget.battery.changeRate !== undefined) + lines.push("Rate: " + batteryWidget.battery.changeRate.toFixed(2) + " W"); + if (batteryWidget.battery.timeToEmpty > 0) + lines.push("Time left: " + Math.floor(batteryWidget.battery.timeToEmpty / 60) + " min"); + if (batteryWidget.battery.timeToFull > 0) + lines.push("Time to full: " + Math.floor(batteryWidget.battery.timeToFull / 60) + " min"); + if (batteryWidget.battery.healthPercentage !== undefined) + lines.push("Health: " + Math.round(batteryWidget.battery.healthPercentage) + "%"); + } + return lines.join("\n"); + } + tooltipVisible: false + targetItem: pill + delay: 1500 + } } - property bool containsMouse: false - - StyledTooltip { - id: batteryTooltip - text: { - let lines = []; - if (batteryWidget.isReady) { - lines.push(batteryWidget.charging ? "Charging" : "Discharging"); - lines.push(Math.round(batteryWidget.percent) + "%"); - if (batteryWidget.battery.changeRate !== undefined) - lines.push("Rate: " + batteryWidget.battery.changeRate.toFixed(2) + " W"); - if (batteryWidget.battery.timeToEmpty > 0) - lines.push("Time left: " + Math.floor(batteryWidget.battery.timeToEmpty / 60) + " min"); - if (batteryWidget.battery.timeToFull > 0) - lines.push("Time to full: " + Math.floor(batteryWidget.battery.timeToFull / 60) + " min"); - if (batteryWidget.battery.healthPercentage !== undefined) - lines.push("Health: " + Math.round(batteryWidget.battery.healthPercentage) + "%"); - } - return lines.join("\n"); + Timer { + id: hideTimer + interval: 2000 + running: true + onTriggered: { + pill.hide(); } - tooltipVisible: batteryWidget.containsMouse - targetItem: batteryWidget - delay: 200 } -} \ No newline at end of file + + Component.onCompleted: { + if (isReady && battery.isLaptopBattery) { + pill.show(); + } + } +} diff --git a/Components/PillIndicator.qml b/Components/PillIndicator.qml index e87d4af..3baf04e 100644 --- a/Components/PillIndicator.qml +++ b/Components/PillIndicator.qml @@ -15,6 +15,7 @@ Item { property int pillHeight: 22 property int iconSize: 22 property int pillPaddingHorizontal: 14 + property bool autoHide: true // Internal state property bool showPill: false @@ -24,8 +25,8 @@ Item { readonly property int pillOverlap: iconSize / 2 readonly property int maxPillWidth: Math.max(1, textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap) - signal shown() - signal hidden() + signal shown + signal hidden width: iconSize + (showPill ? maxPillWidth - pillOverlap : 0) height: pillHeight @@ -54,11 +55,17 @@ Item { Behavior on width { enabled: showAnim.running || hideAnim.running - NumberAnimation { duration: 250; easing.type: Easing.OutCubic } + NumberAnimation { + duration: 250 + easing.type: Easing.OutCubic + } } Behavior on opacity { enabled: showAnim.running || hideAnim.running - NumberAnimation { duration: 250; easing.type: Easing.OutCubic } + NumberAnimation { + duration: 250 + easing.type: Easing.OutCubic + } } } @@ -73,7 +80,10 @@ Item { anchors.right: parent.right Behavior on color { - ColorAnimation { duration: 200; easing.type: Easing.InOutQuad } + ColorAnimation { + duration: 200 + easing.type: Easing.InOutQuad + } } Text { @@ -106,11 +116,11 @@ Item { easing.type: Easing.OutCubic } onStarted: { - showPill = true + showPill = true; } onStopped: { - delayedHideAnim.start() - shown() + delayedHideAnim.start(); + shown(); } } @@ -118,8 +128,13 @@ Item { SequentialAnimation { id: delayedHideAnim running: false - PauseAnimation { duration: 2500 } - ScriptAction { script: if (shouldAnimateHide) hideAnim.start() } + PauseAnimation { + duration: 2500 + } + ScriptAction { + script: if (shouldAnimateHide) + hideAnim.start() + } } // Hide animation @@ -143,27 +158,27 @@ Item { easing.type: Easing.InCubic } onStopped: { - showPill = false - shouldAnimateHide = false - hidden() + showPill = false; + shouldAnimateHide = false; + hidden(); } } // Exposed functions function show() { if (!showPill) { - shouldAnimateHide = true - showAnim.start() + shouldAnimateHide = autoHide; + showAnim.start(); } else { // Reset hide timer if already shown - hideAnim.stop() - delayedHideAnim.restart() + hideAnim.stop(); + delayedHideAnim.restart(); } } function hide() { if (showPill) { - hideAnim.start() + hideAnim.start(); } } -} \ No newline at end of file +}