pragma ComponentBehavior: Bound import qs.config import qs.custom import qs.services import Quickshell.Services.UPower import QtQuick import QtQuick.Shapes import QtQuick.Layouts ColumnLayout { id: root spacing: 4 readonly property color color: UPower.onBattery && UPower.displayDevice.percentage < 0.15 ? Config.colors.batteryWarning : Config.colors.battery Loader { Layout.alignment: Qt.AlignHCenter active: UPower.displayDevice.isLaptopBattery asynchronous: true height: active ? (item?.implicitHeight ?? 0) : 0 sourceComponent: Item { anchors.horizontalCenter: parent.horizontalCenter implicitWidth: meter.width implicitHeight: meter.height + estimate.height + 8 Shape { id: meter preferredRendererType: Shape.CurveRenderer visible: false readonly property real size: 96 readonly property real padding: 8 readonly property real thickness: 8 readonly property real angle: 280 ShapePath { id: path fillColor: "transparent" strokeColor: Qt.alpha(root.color, 0.1) strokeWidth: meter.thickness capStyle: ShapePath.RoundCap PathAngleArc { centerX: detail.x + detail.width / 2 centerY: detail.y + detail.height / 2 radiusX: (meter.size + meter.thickness) / 2 + meter.padding radiusY: radiusX startAngle: -90 - meter.angle / 2 sweepAngle: meter.angle } Behavior on strokeColor { CAnim {} } } ShapePath { fillColor: "transparent" strokeColor: root.color strokeWidth: meter.thickness capStyle: ShapePath.RoundCap PathAngleArc { centerX: detail.x + detail.width / 2 centerY: detail.y + detail.height / 2 radiusX: (meter.size + meter.thickness) / 2 + meter.padding radiusY: radiusX startAngle: -90 - meter.angle / 2 sweepAngle: meter.angle * UPower.displayDevice.percentage } Behavior on strokeColor { CAnim {} } } } Column { id: detail anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: (meter.size + meter.thickness - height) / 2 + meter.padding spacing: -6 // HACK: Prevent load order issues Component.onCompleted: meter.visible = true; CustomText { anchors.horizontalCenter: parent.horizontalCenter text: Math.round(UPower.displayDevice.percentage * 100) + "%" font.pointSize: Config.font.size.largest } CustomText { anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 10 text: UPowerDeviceState.toString(UPower.displayDevice.state) animate: true font.pointSize: Config.font.size.smaller height: implicitHeight * 1.4 } } Column { id: estimate anchors.top: meter.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 3 spacing: -3 CustomText { id: estimateTime anchors.horizontalCenter: parent.horizontalCenter text: UPower.onBattery ? Time.formatSeconds(UPower.displayDevice.timeToEmpty) || "--" : Time.formatSeconds(UPower.displayDevice.timeToFull) || "--" animate: (from, to) => from === "--" || to === "--" font.family: Config.font.family.mono font.pointSize: Config.font.size.normal } CustomText { anchors.horizontalCenter: parent.horizontalCenter text: UPower.onBattery ? "remaining" : "to full" animate: true font.family: Config.font.family.mono font.pointSize: Config.font.size.small } } } } Loader { Layout.alignment: Qt.AlignHCenter active: PowerProfiles.degradationReason !== PerformanceDegradationReason.None asynchronous: true height: active ? (item?.implicitHeight ?? 0) : 0 sourceComponent: CustomRect { implicitWidth: child.implicitWidth + 20 implicitHeight: child.implicitHeight + 20 color: Config.colors.errorBg border.color: Config.colors.error radius: 12 Column { id: child anchors.centerIn: parent Row { anchors.horizontalCenter: parent.horizontalCenter spacing: 7 MaterialIcon { anchors.verticalCenter: parent.verticalCenter anchors.verticalCenterOffset: -font.pointSize / 10 text: "warning" color: Config.colors.error } CustomText { anchors.verticalCenter: parent.verticalCenter text: qsTr("Performance Degraded") color: Config.colors.error font.family: Config.font.family.mono font.weight: 500 } MaterialIcon { anchors.verticalCenter: parent.verticalCenter anchors.verticalCenterOffset: -font.pointSize / 10 text: "warning" color: Config.colors.error } } CustomText { anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Reason: %1").arg(PerformanceDegradationReason.toString(PowerProfiles.degradationReason)) color: Config.colors.secondary } } } } CustomRect { id: profiles Layout.topMargin: 4 property string current: { const p = PowerProfiles.profile; if (p === PowerProfile.PowerSaver) return saver.icon; if (p === PowerProfile.Performance) return perf.icon; return balance.icon; } Layout.alignment: Qt.AlignHCenter Layout.leftMargin: 10 Layout.rightMargin: 10 implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 60 implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 8 color: Config.colors.container radius: 1000 CustomRect { id: indicator color: root.color radius: 1000 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 { AnchorAnimation { duration: Config.anim.durations.normal easing.type: Easing.BezierSpline easing.bezierCurve: Config.anim.curves.emphasized } } } Profile { id: saver anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 4 profile: PowerProfile.PowerSaver icon: "energy_savings_leaf" } Profile { id: balance anchors.centerIn: parent profile: PowerProfile.Balanced icon: "balance" } Profile { id: perf anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 4 profile: PowerProfile.Performance icon: "rocket_launch" } } CustomText { Layout.alignment: Qt.AlignHCenter Layout.topMargin: -2 text: "Performance: " + PowerProfile.toString(PowerProfiles.profile) animate: true color: Config.colors.secondary font.pointSize: Config.font.size.small font.weight: 500 } component Fill: AnchorChanges { required property Item item target: indicator anchors.left: item.left anchors.right: item.right anchors.top: item.top anchors.bottom: item.bottom } component Profile: StateLayer { required property string icon required property int profile implicitWidth: icon.implicitHeight + 5 implicitHeight: icon.implicitHeight + 5 function onClicked(): void { PowerProfiles.profile = profile; } MaterialIcon { id: icon anchors.centerIn: parent text: parent.icon font.pointSize: Config.font.size.larger color: profiles.current === text ? Config.colors.primaryDark : Config.colors.primary fill: profiles.current === text ? 1 : 0 Behavior on fill { Anim {} } } } }