Files

206 lines
6.8 KiB
QML

import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
// Battery widget with Android 16 style rendering (horizontal or vertical)
Item {
id: root
// Data (must be provided by parent)
required property real percentage
required property bool charging
required property bool pluggedIn
required property bool ready
required property bool low
required property bool critical
// Sizing - baseSize controls overall scaleFactor for bar/panel usage
property real baseSize: Style.fontSizeM
// Styling - no hardcoded colors, only theme colors
property color baseColor: Color.mOnSurface
property color lowColor: Color.mError
property color chargingColor: Color.mPrimary
property color textColor: Color.mSurface
// Display options
property bool showPercentageText: true
property bool vertical: false
// Alternating state icon display (toggles between percentage and icon when charging)
property bool showStateIcon: false
onChargingChanged: {
if (!charging)
showStateIcon = false;
}
// Internal sizing calculations based on baseSize
readonly property real scaleFactor: baseSize / Style.fontSizeM
readonly property real bodyWidth: {
const min = Style.toOdd(22 * scaleFactor);
if (!showPercentageText) {
return min;
}
// increase length when showing 100%
if (percentage > 99) {
const max = Style.toOdd(30 * scaleFactor);
return max;
}
return min;
}
readonly property real bodyHeight: Style.toOdd(14 * scaleFactor)
readonly property real terminalWidth: Math.round(2.5 * scaleFactor)
readonly property real terminalHeight: Math.round(7 * scaleFactor)
readonly property real cornerRadius: Math.round(3 * scaleFactor)
// Total size is just body + terminal (no external icon)
readonly property real totalWidth: vertical ? bodyHeight : bodyWidth + terminalWidth
readonly property real totalHeight: vertical ? bodyWidth + terminalWidth : bodyHeight
// Determine active color based on state
readonly property color activeColor: {
if (!ready) {
return Qt.alpha(baseColor, Style.opacityMedium);
}
if (charging) {
return chargingColor;
}
if (low || critical) {
return lowColor;
}
return baseColor;
}
// Background color for empty portion (semi-transparent)
readonly property color emptyColor: Qt.alpha(baseColor, 0.66)
// State icon logic
readonly property string stateIcon: {
if (!ready)
return "x";
if (charging)
return "bolt-filled";
if (pluggedIn)
return "plug-filled";
return "";
}
// Animated percentage for smooth transitions
property real animatedPercentage: percentage
Behavior on animatedPercentage {
enabled: !Settings.data.general.animationDisabled
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
// Timer to alternate between percentage text and state icon when charging/plugged
Timer {
id: alternateTimer
interval: 4000
repeat: true
running: root.charging && root.showPercentageText
onTriggered: root.showStateIcon = !root.showStateIcon
}
implicitWidth: Math.round(totalWidth)
implicitHeight: Math.round(totalHeight)
Layout.maximumWidth: implicitWidth
Layout.maximumHeight: implicitHeight
// Battery body container
Item {
id: batteryBody
width: root.vertical ? root.bodyHeight : root.bodyWidth + root.terminalWidth
height: root.vertical ? root.bodyWidth + root.terminalWidth : root.bodyHeight
anchors.left: root.vertical ? undefined : parent.left
anchors.bottom: root.vertical ? parent.bottom : undefined
anchors.horizontalCenter: root.vertical ? parent.horizontalCenter : undefined
anchors.verticalCenter: root.vertical ? undefined : parent.verticalCenter
// Battery body background
Rectangle {
id: bodyBackground
y: root.vertical ? root.terminalWidth : 0
width: root.vertical ? root.bodyHeight : root.bodyWidth
height: root.vertical ? root.bodyWidth : root.bodyHeight
radius: root.cornerRadius
color: root.emptyColor
}
// Terminal cap
Rectangle {
x: root.vertical ? (root.bodyHeight - root.terminalHeight) / 2 : root.bodyWidth
y: root.vertical ? 0 : (root.bodyHeight - root.terminalHeight) / 2
width: root.vertical ? root.terminalHeight : root.terminalWidth
height: root.vertical ? root.terminalWidth : root.terminalHeight
radius: root.cornerRadius / 2
color: root.critical ? root.lowColor : root.emptyColor
}
// Fill level
Rectangle {
id: fillRect
visible: root.ready && (root.animatedPercentage > 0 || root.critical)
x: 0
y: root.vertical ? root.terminalWidth + root.bodyWidth * (1 - (root.critical ? 1 : root.animatedPercentage / 100)) : 0
width: root.vertical ? root.bodyHeight : root.bodyWidth * (root.critical ? 1 : root.animatedPercentage / 100)
height: root.vertical ? root.bodyWidth * (root.critical ? 1 : root.animatedPercentage / 100) : root.bodyHeight
radius: root.cornerRadius
color: root.activeColor
}
}
// Percentage text overlaid on battery center
NText {
id: percentageText
visible: opacity > 0
opacity: root.showPercentageText && root.ready && (root.charging ? !root.showStateIcon : !root.pluggedIn) ? 1 : 0
x: batteryBody.x + Style.pixelAlignCenter(bodyBackground.width, width)
y: batteryBody.y + bodyBackground.y + Style.pixelAlignCenter(bodyBackground.height, height)
font.family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightBold
text: root.vertical ? String(Math.round(root.animatedPercentage)).split('').join('\n') : Math.round(root.animatedPercentage)
pointSize: root.baseSize * (root.vertical ? 0.82 : 0.82)
color: Qt.alpha(root.textColor, 0.75)
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
lineHeight: root.vertical ? 0.7 : 1.0
lineHeightMode: Text.ProportionalHeight
Behavior on opacity {
enabled: !Settings.data.general.animationDisabled
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
}
// State icon centered inside battery body (shown when alternating)
NIcon {
id: stateIconOverlay
visible: opacity > 0
opacity: !root.ready || (root.charging ? (root.showStateIcon || !root.showPercentageText) : root.pluggedIn) ? 1 : 0
x: batteryBody.x + Style.pixelAlignCenter(bodyBackground.width, width)
y: batteryBody.y + bodyBackground.y + Style.pixelAlignCenter(bodyBackground.height, height)
icon: root.stateIcon
pointSize: Style.toOdd(root.baseSize)
color: Qt.alpha(root.textColor, 0.75)
Behavior on opacity {
enabled: !Settings.data.general.animationDisabled
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
}
}