feat: bar battery popout

This commit is contained in:
2 * r + 2 * t 2025-06-04 17:01:36 +10:00
parent 58adddbe0b
commit 2ffaf4eb3c
4 changed files with 208 additions and 20 deletions

View file

@ -14,10 +14,16 @@ StyledRect {
function checkPopout(y: real): var {
const aw = activeWindow.child;
const awy = activeWindow.y + aw.y;
const b = statusIconsInner.battery;
const by = statusIcons.y + statusIconsInner.y + b.y;
if (y >= awy && y <= awy + aw.implicitHeight) {
Popouts.currentName = "activewindow";
Popouts.currentCenter = Qt.binding(() => activeWindow.y + aw.y + aw.implicitHeight / 2);
Popouts.hasCurrent = true;
} else if (y >= by && y <= by + b.implicitHeight) {
Popouts.currentName = "battery";
Popouts.currentCenter = Qt.binding(() => statusIcons.y + statusIconsInner.y + b.y + b.implicitHeight / 2);
Popouts.hasCurrent = true;
} else {
Popouts.hasCurrent = false;
}

View file

@ -10,6 +10,7 @@ Item {
id: root
property color colour: Colours.palette.m3secondary
readonly property Item battery: battery
clip: true
implicitWidth: Math.max(network.implicitWidth, bluetooth.implicitWidth, devices.implicitWidth, battery.implicitWidth)

View file

@ -0,0 +1,176 @@
pragma ComponentBehavior: Bound
import "root:/widgets"
import "root:/services"
import "root:/config"
import Quickshell.Services.UPower
import QtQuick
Column {
id: root
spacing: Appearance.spacing.normal
StyledText {
text: `Remaining: ${Math.round(UPower.displayDevice.percentage * 100)}%`
}
StyledText {
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;
}
text: `Time ${UPower.onBattery ? "remaining" : "until charged"}: ${UPower.onBattery ? formatSeconds(UPower.displayDevice.timeToEmpty, "Calculating...") : formatSeconds(UPower.displayDevice.timeToFull, "Fully charged!")}`
}
StyledRect {
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
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + Appearance.padding.normal * 2 + Appearance.spacing.large * 2
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + Appearance.padding.small * 2
color: Colours.palette.m3surfaceContainer
radius: Appearance.rounding.full
StyledRect {
id: indicator
color: Colours.palette.m3primary
radius: Appearance.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 {
AnchorAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
}
Profile {
id: saver
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.small
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: Appearance.padding.small
profile: PowerProfile.Performance
icon: "rocket_launch"
}
}
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: Item {
required property string icon
required property int profile
implicitWidth: icon.implicitHeight + Appearance.padding.small * 2
implicitHeight: icon.implicitHeight + Appearance.padding.small * 2
StateLayer {
radius: Appearance.rounding.full
color: profiles.current === parent.icon ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
function onClicked(): void {
PowerProfiles.profile = parent.profile;
}
}
MaterialIcon {
id: icon
anchors.centerIn: parent
text: parent.icon
font.pointSize: Appearance.font.size.large
color: profiles.current === text ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
fill: profiles.current === text ? 1 : 0
Behavior on fill {
NumberAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
}
}

View file

@ -23,7 +23,12 @@ Item {
Popout {
name: "activewindow"
sourceComponent: ActiveWindow {}
source: "ActiveWindow.qml"
}
Popout {
name: "battery"
source: "Battery.qml"
}
}
@ -51,25 +56,25 @@ Item {
active: shouldBeActive
asynchronous: true
Behavior on active {
SequentialAnimation {
Anim {
target: popout
property: "opacity"
from: popout.shouldBeActive ? 1 : 0
to: popout.shouldBeActive ? 0 : 1
duration: popout.shouldBeActive ? 0 : Appearance.anim.durations.normal
}
PropertyAction {}
Anim {
target: popout
property: "opacity"
from: popout.shouldBeActive ? 0 : 1
to: popout.shouldBeActive ? 1 : 0
duration: popout.shouldBeActive ? Appearance.anim.durations.normal : 0
}
}
}
// Behavior on active {
// SequentialAnimation {
// Anim {
// target: popout
// property: "opacity"
// from: popout.shouldBeActive ? 1 : 0
// to: popout.shouldBeActive ? 0 : 1
// duration: popout.shouldBeActive ? 0 : Appearance.anim.durations.normal
// }
// PropertyAction {}
// Anim {
// target: popout
// property: "opacity"
// from: popout.shouldBeActive ? 0 : 1
// to: popout.shouldBeActive ? 1 : 0
// duration: popout.shouldBeActive ? Appearance.anim.durations.normal : 0
// }
// }
// }
}
component Anim: NumberAnimation {