Added a small battery popup to show information

This commit is contained in:
Runze 2025-08-05 17:21:36 +08:00
parent f8d162d995
commit 3a6c032782
5 changed files with 203 additions and 3 deletions

View file

@ -3,8 +3,9 @@ import qs.modules.common.widgets
import qs.services
import QtQuick
import QtQuick.Layouts
import Quickshell
Item {
MouseArea {
id: root
property bool borderless: Config.options.bar.borderless
readonly property var chargeState: Battery.chargeState
@ -18,6 +19,8 @@ Item {
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: 32
hoverEnabled: true
RowLayout {
id: rowLayout
@ -92,4 +95,25 @@ Item {
}
}
LazyLoader {
id: popupLoader
active: root.containsMouse
component: PopupWindow {
id: popupWindow
visible: true
implicitWidth: batteryPopup.implicitWidth
implicitHeight: batteryPopup.implicitHeight
anchor.item: root
anchor.rect.x: (root.implicitWidth - popupWindow.implicitWidth) / 2
anchor.rect.y: Config.options.bar.bottom
? (-batteryPopup.implicitHeight - 15)
: (root.implicitHeight + 15)
color: "transparent"
BatteryPopup {
id: batteryPopup
}
}
}
}

View file

@ -0,0 +1,125 @@
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import qs
import QtQuick
import QtQuick.Layouts
Rectangle {
id: root
readonly property real margin: 10
implicitWidth: columnLayout.implicitWidth + margin * 2
implicitHeight: columnLayout.implicitHeight + margin * 2
color: Appearance.colors.colLayer0
radius: Appearance.rounding.small
border.width: 1
border.color: Appearance.colors.colLayer0Border
clip: true
ColumnLayout {
id: columnLayout
anchors.centerIn: parent
spacing: 8
RowLayout {
spacing: 5
Layout.fillWidth: true
MaterialSymbol { text: "thermostat"; color: Appearance.m3colors.m3onSecondaryContainer }
StyledText { text: Translation.tr("Temperature:"); color: Appearance.colors.colOnLayer1 }
StyledText { Layout.fillWidth: true; horizontalAlignment: Text.AlignRight; color: Appearance.colors.colOnLayer1; text: `${Battery.temperature}°C` }
}
// This row is hidden when the battery is full.
RowLayout {
spacing: 5
Layout.fillWidth: true
property bool rowVisible: {
let timeValue = Battery.isCharging ? Battery.timeToFull : Battery.timeToEmpty;
let power = Battery.energyRate;
return !(Battery.chargeState == 4 || timeValue <= 0 || power <= 0.01);
}
visible: rowVisible
opacity: rowVisible ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 500 } }
MaterialSymbol { text: "schedule"; color: Appearance.m3colors.m3onSecondaryContainer }
StyledText { text: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:"); color: Appearance.colors.colOnLayer1 }
StyledText {
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
color: Appearance.colors.colOnLayer1
text: {
function formatTime(seconds) {
var h = Math.floor(seconds / 3600);
var m = Math.floor((seconds % 3600) / 60);
if (h > 0)
return `${h}h ${m}m`;
else
return `${m}m`;
}
if (Battery.isCharging)
return formatTime(Battery.timeToFull);
else
return formatTime(Battery.timeToEmpty);
}
}
}
RowLayout {
spacing: 5
Layout.fillWidth: true
property bool rowVisible: !(Battery.chargeState != 4 && Battery.energyRate == 0)
visible: rowVisible
opacity: rowVisible ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 500 } }
MaterialSymbol {
text: {
if (Battery.isCharging) {
return "power";
} else if (Battery.percentage >= 0.8) {
return "battery_full";
} else if (Battery.percentage >= 0.6) {
return "battery_5_bar";
} else if (Battery.percentage >= 0.4) {
return "battery_4_bar";
} else if (Battery.percentage >= 0.2) {
return "battery_2_bar";
} else {
return "battery_0_bar";
}
}
color: Appearance.m3colors.m3onSecondaryContainer
}
StyledText {
text: {
if (Battery.chargeState == 4) {
return Translation.tr("Fully charged");
} else if (Battery.chargeState == 1) {
return Translation.tr("Charging:");
} else {
return Translation.tr("Discharging:");
}
}
color: Appearance.colors.colOnLayer1
}
StyledText {
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
color: Appearance.colors.colOnLayer1
text: {
if (Battery.chargeState == 4) {
return "";
} else {
return `${Battery.energyRate.toFixed(2)}W`;
}
}
}
}
}
}

View file

@ -4,6 +4,8 @@ import qs
import qs.modules.common
import Quickshell
import Quickshell.Services.UPower
import QtQuick
import Quickshell.Io
Singleton {
property bool available: UPower.displayDevice.isLaptopBattery
@ -21,6 +23,43 @@ Singleton {
property bool isCriticalAndNotCharging: isCritical && !isCharging
property bool isSuspendingAndNotCharging: allowAutomaticSuspend && isSuspending && !isCharging
property real energyRate: UPower.displayDevice.changeRate
property real timeToEmpty: UPower.displayDevice.timeToEmpty
property real timeToFull: UPower.displayDevice.timeToFull
property real temperature: 0
Process {
id: tempProcess
command: ["bash", "-c", "cat /sys/class/thermal/thermal_zone0/temp"]
stdout: StdioCollector {
onStreamFinished: {
if (text && text.trim() !== "") {
Battery.temperature = parseInt(text.trim()) / 1000
} else {
Battery.temperature = 0
}
}
}
}
Timer {
interval: 3000
repeat: true
running: true
onTriggered: {
if (!tempProcess.running) {
tempProcess.running = true
}
}
}
Component.onCompleted: {
if (!tempProcess.running) {
tempProcess.running = true;
}
}
onIsLowAndNotChargingChanged: {
if (available && isLowAndNotCharging) Quickshell.execDetached([
"notify-send",

View file

@ -310,5 +310,11 @@
"Sunrise": "Sunrise",
"Pressure": "Pressure",
"Visibility": "Visibility",
"Precipitation": "Precipitation"
"Precipitation": "Precipitation",
"Temperature:": "Temperature:",
"Time to full:": "Time to full:",
"Time to empty:": "Time to empty:",
"Fully charged": "Fully charged",
"Charging:": "Charging:",
"Discharging:": "Discharging:"
}

View file

@ -310,5 +310,11 @@
"Sunset": "日落",
"Humidity": "湿度",
"Wind": "风",
"Precipitation": "降水量"
"Precipitation": "降水量",
"Temperature:": "温度:",
"Time to full:": "距离充满:",
"Time to empty:": "距离耗尽:",
"Fully charged": "已充满电",
"Charging:": "充电功率:",
"Discharging:": "放电功率:"
}