diff options
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Popouts/BatteryPopout.qml')
| -rw-r--r-- | raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Popouts/BatteryPopout.qml | 633 |
1 files changed, 633 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Popouts/BatteryPopout.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Popouts/BatteryPopout.qml new file mode 100644 index 0000000..f0cb9d8 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Popouts/BatteryPopout.qml @@ -0,0 +1,633 @@ +import QtQuick +import Quickshell +import Quickshell.Services.UPower +import qs.Common +import qs.Services +import qs.Widgets + +DankPopout { + id: root + + layerNamespace: "dms:battery" + + property var triggerScreen: null + + function isActiveProfile(profile) { + if (typeof PowerProfiles === "undefined") { + return false; + } + + return PowerProfiles.profile === profile; + } + + function setProfile(profile) { + if (typeof PowerProfiles === "undefined") { + ToastService.showError(I18n.tr("power-profiles-daemon not available")); + return; + } + PowerProfiles.profile = profile; + if (PowerProfiles.profile !== profile) { + ToastService.showError(I18n.tr("Failed to set power profile")); + } + } + + popupWidth: 400 + popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 0 + triggerWidth: 70 + positioning: "" + screen: triggerScreen + + onBackgroundClicked: close() + + content: Component { + Rectangle { + id: batteryContent + + LayoutMirroring.enabled: I18n.isRtl + LayoutMirroring.childrenInherit: true + + implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2 + color: "transparent" + focus: true + Component.onCompleted: { + if (root.shouldBeVisible) { + forceActiveFocus(); + } + } + Keys.onPressed: function (event) { + if (event.key === Qt.Key_Escape) { + root.close(); + event.accepted = true; + } + } + + Connections { + function onShouldBeVisibleChanged() { + if (root.shouldBeVisible) { + Qt.callLater(function () { + batteryContent.forceActiveFocus(); + }); + } + } + + target: root + } + + Column { + id: contentColumn + + width: parent.width - Theme.spacingL * 2 + anchors.left: parent.left + anchors.top: parent.top + anchors.margins: Theme.spacingL + spacing: Theme.spacingL + + Row { + width: parent.width + height: 48 + spacing: Theme.spacingM + + DankIcon { + name: { + if (!BatteryService.batteryAvailable) + return "power"; + + if (!BatteryService.isCharging && BatteryService.isPluggedIn) { + if (BatteryService.batteryLevel >= 90) { + return "battery_charging_full"; + } + if (BatteryService.batteryLevel >= 80) { + return "battery_charging_90"; + } + if (BatteryService.batteryLevel >= 60) { + return "battery_charging_80"; + } + if (BatteryService.batteryLevel >= 50) { + return "battery_charging_60"; + } + if (BatteryService.batteryLevel >= 30) { + return "battery_charging_50"; + } + if (BatteryService.batteryLevel >= 20) { + return "battery_charging_30"; + } + return "battery_charging_20"; + } + if (BatteryService.isCharging) { + if (BatteryService.batteryLevel >= 90) { + return "battery_charging_full"; + } + if (BatteryService.batteryLevel >= 80) { + return "battery_charging_90"; + } + if (BatteryService.batteryLevel >= 60) { + return "battery_charging_80"; + } + if (BatteryService.batteryLevel >= 50) { + return "battery_charging_60"; + } + if (BatteryService.batteryLevel >= 30) { + return "battery_charging_50"; + } + if (BatteryService.batteryLevel >= 20) { + return "battery_charging_30"; + } + return "battery_charging_20"; + } else { + if (BatteryService.batteryLevel >= 95) { + return "battery_full"; + } + if (BatteryService.batteryLevel >= 85) { + return "battery_6_bar"; + } + if (BatteryService.batteryLevel >= 70) { + return "battery_5_bar"; + } + if (BatteryService.batteryLevel >= 55) { + return "battery_4_bar"; + } + if (BatteryService.batteryLevel >= 40) { + return "battery_3_bar"; + } + if (BatteryService.batteryLevel >= 25) { + return "battery_2_bar"; + } + return "battery_1_bar"; + } + } + size: Theme.iconSizeLarge + color: { + if (BatteryService.isLowBattery && !BatteryService.isCharging) + return Theme.error; + if (BatteryService.isCharging || BatteryService.isPluggedIn) + return Theme.primary; + return Theme.surfaceText; + } + anchors.verticalCenter: parent.verticalCenter + } + + Column { + id: headerInfoColumn + spacing: Theme.spacingXS + anchors.verticalCenter: parent.verticalCenter + width: parent.width - Theme.iconSizeLarge - 32 - Theme.spacingM * 2 + readonly property string timeInfoText: { + if (!BatteryService.batteryAvailable) + return I18n.tr("Power profile management available"); + const time = BatteryService.formatTimeRemaining(); + if (time !== "Unknown") { + return BatteryService.isCharging ? I18n.tr("Time until full: %1").arg(time) : I18n.tr("Time remaining: %1").arg(time); + } + return ""; + } + readonly property bool showPowerRate: BatteryService.batteryAvailable && Math.abs(BatteryService.changeRate) > 0.05 + readonly property bool isOnAC: BatteryService.batteryAvailable && (BatteryService.isCharging || BatteryService.isPluggedIn) + readonly property bool isDischarging: BatteryService.batteryAvailable && !BatteryService.isCharging && !BatteryService.isPluggedIn + + Row { + spacing: Theme.spacingS + + StyledText { + text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : I18n.tr("Power") + font.pixelSize: Theme.fontSizeXLarge + color: { + if (BatteryService.isLowBattery && !BatteryService.isCharging) { + return Theme.error; + } + if (BatteryService.isCharging) { + return Theme.primary; + } + return Theme.surfaceText; + } + font.weight: Font.Bold + } + + StyledText { + text: BatteryService.batteryStatus + font.pixelSize: Theme.fontSizeLarge + color: { + if (BatteryService.isLowBattery && !BatteryService.isCharging) { + return Theme.error; + } + if (BatteryService.isCharging) { + return Theme.primary; + } + return Theme.surfaceText; + } + font.weight: Font.Medium + visible: BatteryService.batteryAvailable + anchors.verticalCenter: parent.verticalCenter + } + } + + Row { + width: parent.width + spacing: Theme.spacingS + visible: headerInfoColumn.timeInfoText.length > 0 + + StyledText { + id: powerRateText + text: `${headerInfoColumn.isOnAC ? "+" : (headerInfoColumn.isDischarging ? "-" : "")}${Math.abs(BatteryService.changeRate).toFixed(1)}W` + font.pixelSize: Theme.fontSizeSmall + color: { + if (headerInfoColumn.isOnAC) { + return Theme.primary; + } + if (headerInfoColumn.isDischarging) { + return Theme.warning; + } + return Theme.surfaceTextMedium; + } + font.weight: Font.Medium + visible: headerInfoColumn.showPowerRate + } + + StyledText { + text: headerInfoColumn.timeInfoText + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceTextMedium + elide: Text.ElideRight + width: parent.width - (powerRateText.visible ? (powerRateText.implicitWidth + parent.spacing) : 0) + } + } + } + + Rectangle { + width: 32 + height: 32 + radius: 16 + color: closeBatteryArea.containsMouse ? Theme.errorHover : "transparent" + anchors.top: parent.top + + DankIcon { + anchors.centerIn: parent + name: "close" + size: Theme.iconSize - 4 + color: closeBatteryArea.containsMouse ? Theme.error : Theme.surfaceText + } + + MouseArea { + id: closeBatteryArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onPressed: { + root.close(); + } + } + } + } + + Row { + width: parent.width + spacing: Theme.spacingM + visible: BatteryService.batteryAvailable + + StyledRect { + width: (parent.width - Theme.spacingM) / 2 + height: 64 + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.width: 0 + + Column { + anchors.centerIn: parent + spacing: Theme.spacingXS + + StyledText { + text: I18n.tr("Health") + font.pixelSize: Theme.fontSizeSmall + color: Theme.primary + font.weight: Font.Medium + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: BatteryService.batteryHealth + font.pixelSize: Theme.fontSizeLarge + color: { + if (BatteryService.batteryHealth === "N/A") { + return Theme.surfaceText; + } + const healthNum = parseInt(BatteryService.batteryHealth); + return healthNum < 80 ? Theme.error : Theme.surfaceText; + } + font.weight: Font.Bold + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + + StyledRect { + width: (parent.width - Theme.spacingM) / 2 + height: 64 + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.width: 0 + + Column { + anchors.centerIn: parent + spacing: Theme.spacingXS + + StyledText { + text: I18n.tr("Capacity") + font.pixelSize: Theme.fontSizeSmall + color: Theme.primary + font.weight: Font.Medium + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: BatteryService.batteryCapacity > 0 ? `${BatteryService.batteryCapacity.toFixed(1)} Wh` : I18n.tr("Unknown") + font.pixelSize: Theme.fontSizeLarge + color: Theme.surfaceText + font.weight: Font.Bold + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + } + + // Individual battery details for multiple batteries + Column { + width: parent.width + spacing: Theme.spacingS + visible: !BatteryService.usePreferred && BatteryService.batteries.length > 1 + + StyledText { + text: I18n.tr("Individual Batteries") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceTextMedium + font.weight: Font.Medium + } + + Repeater { + model: ScriptModel { + values: BatteryService.batteries + } + + delegate: StyledRect { + required property var modelData + required property int index + + width: parent.width + height: batteryColumn.implicitHeight + Theme.spacingM * 2 + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) + border.width: 0 + + Column { + id: batteryColumn + anchors.fill: parent + anchors.margins: Theme.spacingM + spacing: Theme.spacingS + + // Top row: name and percentage + Row { + width: parent.width + spacing: Theme.spacingM + + Column { + spacing: Theme.spacingXS + anchors.verticalCenter: parent.verticalCenter + width: parent.width - percentText.width - chargingIcon.width - Theme.spacingM * 2 + + StyledText { + text: modelData.model || I18n.tr("Battery %1").arg(index + 1) + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Medium + elide: Text.ElideRight + width: parent.width + } + + StyledText { + text: modelData.nativePath + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceTextMedium + elide: Text.ElideMiddle + width: parent.width + } + } + + Item { + width: 1 + height: parent.height + } + + StyledText { + id: percentText + text: `${Math.round(100 * modelData.percentage)}%` + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + font.weight: Font.Bold + anchors.verticalCenter: parent.verticalCenter + } + + DankIcon { + id: chargingIcon + name: modelData.state === UPowerDeviceState.Charging ? "bolt" : "" + size: Theme.iconSizeSmall + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + visible: modelData.state === UPowerDeviceState.Charging + } + } + + // Bottom row: Health, Capacity and Time + Flow { + width: parent.width + spacing: Theme.spacingS + + StyledRect { + width: (parent.width - Theme.spacingS * 2) / 3 + height: 48 + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.width: 0 + + Column { + anchors.centerIn: parent + spacing: 2 + + StyledText { + text: I18n.tr("Health") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceTextMedium + font.weight: Font.Medium + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: { + if (!modelData.healthSupported || modelData.healthPercentage <= 0) + return "N/A"; + return `${Math.round(modelData.healthPercentage)}%`; + } + font.pixelSize: Theme.fontSizeSmall + color: { + if (!modelData.healthSupported || modelData.healthPercentage <= 0) + return Theme.surfaceText; + return modelData.healthPercentage < 80 ? Theme.error : Theme.surfaceText; + } + font.weight: Font.Bold + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + + StyledRect { + width: (parent.width - Theme.spacingS * 2) / 3 + height: 48 + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.width: 0 + + Column { + anchors.centerIn: parent + spacing: 2 + + StyledText { + text: I18n.tr("Capacity") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceTextMedium + font.weight: Font.Medium + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: modelData.energyCapacity > 0 ? `${modelData.energyCapacity.toFixed(1)}` : "N/A" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Bold + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + + StyledRect { + width: (parent.width - Theme.spacingS * 2) / 3 + height: 48 + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.width: 0 + + Column { + anchors.centerIn: parent + spacing: 2 + + StyledText { + text: modelData.state === UPowerDeviceState.Charging ? I18n.tr("To Full") : modelData.state === UPowerDeviceState.Discharging ? I18n.tr("Left") : "" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceTextMedium + font.weight: Font.Medium + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: { + const time = modelData.state === UPowerDeviceState.Charging ? modelData.timeToFull : modelData.state === UPowerDeviceState.Discharging && BatteryService.changeRate > 0 ? (3600 * modelData.energy) / BatteryService.changeRate : 0; + + if (!time || time <= 0 || time > 86400) + return "N/A"; + + const hours = Math.floor(time / 3600); + const minutes = Math.floor((time % 3600) / 60); + return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`; + } + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Bold + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + } + } + } + } + } + + Item { + width: parent.width + height: profileButtonGroup.height * profileButtonGroup.scale + + DankButtonGroup { + id: profileButtonGroup + + property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance] + property int currentProfileIndex: { + if (typeof PowerProfiles === "undefined") + return 1; + return profileModel.findIndex(profile => root.isActiveProfile(profile)); + } + + scale: Math.min(1, parent.width / implicitWidth) + transformOrigin: Item.Center + anchors.horizontalCenter: parent.horizontalCenter + model: profileModel.map(profile => Theme.getPowerProfileLabel(profile)) + currentIndex: currentProfileIndex + selectionMode: "single" + onSelectionChanged: (index, selected) => { + if (!selected) + return; + root.setProfile(profileModel[index]); + } + } + } + + StyledRect { + width: parent.width + height: degradationContent.implicitHeight + Theme.spacingL * 2 + radius: Theme.cornerRadius + color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) + border.color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3) + border.width: 0 + visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None + + Column { + id: degradationContent + width: parent.width - Theme.spacingL * 2 + anchors.left: parent.left + anchors.top: parent.top + anchors.margins: Theme.spacingL + spacing: Theme.spacingS + + Row { + width: parent.width + spacing: Theme.spacingM + + DankIcon { + name: "warning" + size: Theme.iconSize + color: Theme.error + anchors.verticalCenter: parent.verticalCenter + } + + Column { + spacing: Theme.spacingXS + anchors.verticalCenter: parent.verticalCenter + width: parent.width - Theme.iconSize - Theme.spacingM + + StyledText { + text: I18n.tr("Power Profile Degradation") + font.pixelSize: Theme.fontSizeLarge + color: Theme.error + font.weight: Font.Medium + } + + StyledText { + text: (typeof PowerProfiles !== "undefined") ? PerformanceDegradationReason.toString(PowerProfiles.degradationReason) : "" + font.pixelSize: Theme.fontSizeSmall + color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.8) + wrapMode: Text.WordWrap + width: parent.width + } + } + } + } + } + } + } + } +} |