quickshell-toki-night/modules/bar/popouts/Battery.qml

351 lines
10 KiB
QML

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 {}
}
}
}
}