import QtQuick
import QtQuick.Controls
import qs.modules.common
import qs.modules.common.utils
/**
* A progress bar with optional text centered inside it.
* Partially based on https://github.com/end-4/dots-hyprland/blob/449df7f161e6435569bc7d9499b2e444dd8aa153/dots/.config/quickshell/ii/modules/common/widgets/ClippedProgressBar.qml
*/
ProgressBar {
id: root
property int orientation: Types.Orientation.Horizontal
property real valueBarWidth: 2
property real valueBarHeight: 1
property color highlightColor: "gray"
property color trackColor: ColorUtils.transparentize(highlightColor, 0.7)
property color textColor: "white"
property string text
property bool shimmer: false
property bool pulse: false
font.weight: text.length > 2 ? Font.Medium : Font.DemiBold
background: Item {
implicitHeight: valueBarHeight
implicitWidth: valueBarWidth
}
contentItem: Rectangle {
id: contentItem
anchors.fill: parent
color: root.trackColor
SequentialAnimation on color {
running: root.pulse
loops: Animation.Infinite
ColorAnimation {
from: root.trackColor
to: {
var c = Qt.color(root.trackColor);
var boostedLight = Math.min(1.0, c.hslLightness + 0.2);
return Qt.hsla(c.hslHue, c.hslSaturation, boostedLight, c.a);
}
duration: 1000
easing.type: Easing.InOutQuad
}
ColorAnimation {
from: {
var c = Qt.color(root.trackColor);
var boostedLight = Math.min(1.0, c.hslLightness + 0.2);
return Qt.hsla(c.hslHue, c.hslSaturation, boostedLight, c.a);
}
to: root.trackColor
duration: 1000
easing.type: Easing.InOutQuad
}
}
Rectangle {
id: progressFill
color: root.highlightColor
clip: true // ensure the shimmer is only visible inside progressFill
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
right: undefined
}
width: parent.width * root.visualPosition
height: parent.height
states: State {
name: "vertical"
when: root.orientation === Types.Orientation.Vertical
AnchorChanges {
target: progressFill
anchors {
top: undefined
bottom: parent.bottom
left: parent.left
right: parent.right
}
}
PropertyChanges {
target: progressFill
width: parent.width
height: parent.height * root.visualPosition
}
}
Rectangle {
id: shimmerOverlay
visible: root.shimmer
width: root.valueBarWidth
height: root.valueBarHeight
property real shimmerWidth: root.orientation === Types.Orientation.Vertical
? root.valueBarHeight * 0.2 : root.valueBarWidth * 0.2
x: root.orientation === Types.Orientation.Vertical ? 0 : -progressFill.x
y: root.orientation === Types.Orientation.Vertical ? -progressFill.y : 0
opacity: 0.5
gradient: Gradient {
orientation: root.orientation === Types.Orientation.Vertical
? Gradient.Vertical : Gradient.Horizontal
GradientStop { position: -0.3; color: "transparent" }
GradientStop {
position: Math.max(-0.3,
shimmerAnimation.position - shimmerOverlay.shimmerWidth
/ (root.orientation === Types.Orientation.Vertical
? root.valueBarHeight : root.valueBarWidth))
color: "transparent"
}
GradientStop { position: shimmerAnimation.position; color: "white" }
GradientStop {
position: Math.min(1.3,
shimmerAnimation.position + shimmerOverlay.shimmerWidth
/ (root.orientation === Types.Orientation.Vertical
? root.valueBarHeight : root.valueBarWidth))
color: "transparent"
}
GradientStop { position: 1.3; color: "transparent" }
}
SequentialAnimation on x {
id: shimmerAnimation
property real position: 0.0
running: root.shimmer
loops: Animation.Infinite
NumberAnimation {
target: shimmerAnimation
property: "position"
from: root.orientation === Types.Orientation.Vertical ? 1.2 : -0.2
to: root.orientation === Types.Orientation.Vertical ? 0.8 - value : value + 0.2
duration: 3000
easing.type: Easing.InOutExpo
}
}
}
}
}
Text {
id: overlayText
font: root.font
text: root.text
color: textColor
opacity: 0.5
width: root.width
height: root.height
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
style: Text.Outline
}
}