pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell.Services.UPower import ZShell.Config import qs.Modules.Bar.Components.Common import qs.Helpers import qs.Components import qs.Services Column { id: root required property bool horizontal readonly property int panelRadius: ((profiles.height / 2) + Tokens.padding.extraSmall) * Tokens.rounding.scale spacing: Tokens.spacing.medium Loader { active: Battery.isLaptop CustomText { text: qsTr("Remaining: %1%").arg(Math.round(UPower.displayDevice.percentage * 100)) } } Item { id: padding implicitHeight: root.horizontal ? 0 : Tokens.padding.small implicitWidth: 1 } Repeater { model: Battery.allPeripherals RowLayout { id: layout required property var modelData anchors.left: parent.left anchors.leftMargin: Tokens.padding.small anchors.right: parent.right anchors.rightMargin: Tokens.padding.small CustomText { Layout.fillWidth: true text: layout.modelData.model } BatteryIcon { devState: layout.modelData.state percentage: layout.modelData.percentage } } } CustomText { function formatSeconds(s: int, fallback: string): string { const day = Math.floor(s / 86400); const hr = Math.floor(s / 3600) % 60; const min = Math.floor(s / 60) % 60; let comps = []; if (day > 0) comps.push(`${day} days`); if (hr > 0) comps.push(`${hr} hours`); if (min > 0) comps.push(`${min} mins`); return comps.join(", ") || fallback; } anchors.left: parent.left anchors.leftMargin: Tokens.padding.small text: Battery.isLaptop ? qsTr("Time %1: %2").arg(Battery.onBattery ? "remaining" : "until charged").arg(Battery.onBattery ? formatSeconds(Battery.timeToEmpty, "Calculating...") : formatSeconds(Battery.timeToFull, "Fully charged!")) : qsTr("Power profile: %1").arg(PowerProfile.toString(PowerProfiles.profile)) } Loader { active: PowerProfiles.degradationReason !== PerformanceDegradationReason.None anchors.horizontalCenter: parent.horizontalCenter asynchronous: true height: active ? ((item as Item)?.implicitHeight ?? 0) : 0 sourceComponent: CustomRect { color: Colors.palette.m3error implicitHeight: child.implicitHeight + Tokens.padding.large implicitWidth: child.implicitWidth + Tokens.padding.small * 2 radius: Tokens.rounding.largeIncreased Column { id: child anchors.centerIn: parent Row { anchors.horizontalCenter: parent.horizontalCenter spacing: Tokens.spacing.small MaterialIcon { anchors.verticalCenter: parent.verticalCenter anchors.verticalCenterOffset: -font.pointSize / 10 color: Colors.palette.m3onError text: "warning" } CustomText { anchors.verticalCenter: parent.verticalCenter color: Colors.palette.m3onError font.family: Config.appearance.font.family.mono text: qsTr("Performance Degraded") } MaterialIcon { anchors.verticalCenter: parent.verticalCenter anchors.verticalCenterOffset: -font.pointSize / 10 color: Colors.palette.m3onError text: "warning" } } CustomText { anchors.horizontalCenter: parent.horizontalCenter color: Colors.palette.m3onError text: qsTr("Reason: %1").arg(PerformanceDegradationReason.toString(PowerProfiles.degradationReason)) } } } } CustomRect { id: profiles property string current: { const p = PowerProfiles.profile; if (p === PowerProfile.PowerSaver) return saver.icon; if (p === PowerProfile.Performance) return perf.icon; return balance.icon; } anchors.horizontalCenter: parent.horizontalCenter color: Colors.tPalette.m3surfaceContainer implicitHeight: indicator.height + Tokens.padding.extraSmall * 2 implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + Tokens.padding.small * 2 + Tokens.spacing.small * 8 radius: Tokens.rounding.full CustomRect { id: indicator color: Colors.palette.m3primary radius: Tokens.rounding.full state: profiles.current states: [ State { name: saver.icon Fill { item: saver } }, State { name: balance.icon Fill { item: balance } }, State { name: perf.icon Fill { item: perf } } ] transitions: Transition { AnchorAnim { } } } Profile { id: saver anchors.left: parent.left anchors.leftMargin: Tokens.padding.extraSmall anchors.verticalCenter: parent.verticalCenter icon: "energy_savings_leaf" profile: PowerProfile.PowerSaver } Profile { id: balance anchors.centerIn: parent icon: "balance" profile: PowerProfile.Balanced } Profile { id: perf anchors.right: parent.right anchors.rightMargin: Tokens.padding.extraSmall anchors.verticalCenter: parent.verticalCenter icon: "bolt" profile: PowerProfile.Performance } } component Fill: AnchorChanges { required property Item item anchors.bottom: item.bottom anchors.left: item.left anchors.right: item.right anchors.top: item.top target: indicator } component Profile: Item { required property string icon required property int profile implicitHeight: icon.implicitHeight + Tokens.padding.extraSmall implicitWidth: icon.implicitHeight + Tokens.padding.extraSmall StateLayer { color: profiles.current === parent.icon ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface radius: Tokens.rounding.full onClicked: { PowerProfiles.profile = parent.profile; } } MaterialIcon { id: icon anchors.centerIn: parent color: profiles.current === text ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface fill: profiles.current === text ? 1 : 0 font.pointSize: Tokens.font.size.large * 2 grade: Colors.light ? 0 : (text === "balance" ? -25 : 0) text: parent.icon Behavior on fill { Anim { type: Anim.DefaultEffects } } } } }