255 lines
7.8 KiB
QML
255 lines
7.8 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import QtQuick.Templates
|
|
import Caelestia
|
|
import Caelestia.Components
|
|
import Caelestia.Config
|
|
import Caelestia.Internal
|
|
import qs.components
|
|
import qs.services
|
|
|
|
ProgressBar {
|
|
id: root
|
|
|
|
enum IndeterminateAnimState {
|
|
Running,
|
|
Completing,
|
|
Stopped
|
|
}
|
|
|
|
property color fgColour: Colours.palette.m3primary
|
|
property color bgColour: Colours.palette.m3secondaryContainer
|
|
|
|
property bool wavy
|
|
property bool wavePaused
|
|
property int waveFrequency: 6
|
|
property real waveAmplitude: 0.5
|
|
property int waveDuration: 1000
|
|
property bool animateIndeterminate
|
|
|
|
property int indeterminateAnimState: StyledProgressBar.Stopped
|
|
|
|
function toBounds(startFrac: real, endFrac: real, gapSize: real): point {
|
|
startFrac = CUtils.clamp(startFrac, 0, 1);
|
|
endFrac = CUtils.clamp(endFrac, 0, 1);
|
|
|
|
// Ramp down gap size
|
|
const GAP_RAMP_DOWN_THRESHOLD = 0.01;
|
|
gapSize += height / 2;
|
|
const startGapSize = (gapSize * CUtils.clamp(startFrac, 0, GAP_RAMP_DOWN_THRESHOLD) / GAP_RAMP_DOWN_THRESHOLD);
|
|
const endGapSize = (gapSize * (1 - CUtils.clamp(endFrac, 1 - GAP_RAMP_DOWN_THRESHOLD, 1)) / GAP_RAMP_DOWN_THRESHOLD);
|
|
const start = width * startFrac + startGapSize;
|
|
const end = width * endFrac - endGapSize;
|
|
|
|
return start >= end ? Qt.point(0, 0) : Qt.point(start, end);
|
|
}
|
|
|
|
function updateIAnimState(): void {
|
|
if (indeterminate && animateIndeterminate) {
|
|
manager.completeEndProgress = 0;
|
|
indeterminateAnimState = StyledProgressBar.Running;
|
|
} else if (indeterminateAnimState === StyledProgressBar.Running) {
|
|
indeterminateAnimState = StyledProgressBar.Completing;
|
|
}
|
|
}
|
|
|
|
onIndeterminateChanged: updateIAnimState()
|
|
onAnimateIndeterminateChanged: updateIAnimState()
|
|
Component.onCompleted: updateIAnimState()
|
|
|
|
implicitWidth: 200
|
|
implicitHeight: 4
|
|
|
|
contentItem: Loader {
|
|
anchors.fill: parent
|
|
sourceComponent: root.indeterminate || root.indeterminateAnimState !== StyledProgressBar.Stopped ? indeterminateComp : determinateComp
|
|
}
|
|
|
|
LinearIndicatorManager {
|
|
id: manager
|
|
|
|
gap: Tokens.spacing.extraSmall
|
|
|
|
Anim on progress {
|
|
running: root.indeterminateAnimState !== StyledProgressBar.Stopped
|
|
from: 0
|
|
to: 1
|
|
duration: manager.duration
|
|
easing.type: Easing.Linear
|
|
loops: Animation.Infinite
|
|
}
|
|
|
|
Anim on completeEndProgress {
|
|
running: root.indeterminateAnimState === StyledProgressBar.Completing
|
|
to: 1
|
|
duration: manager.completeEndDuration
|
|
onFinished: {
|
|
if (root.indeterminateAnimState === StyledProgressBar.Completing)
|
|
root.indeterminateAnimState = StyledProgressBar.Stopped;
|
|
}
|
|
}
|
|
}
|
|
|
|
Behavior on value {
|
|
Anim {}
|
|
}
|
|
|
|
Component {
|
|
id: determinateComp
|
|
|
|
Item {
|
|
Line {
|
|
id: remaining
|
|
|
|
anchors.right: parent.right
|
|
implicitWidth: parent.width - wave.implicitWidth - Tokens.spacing.extraSmall
|
|
}
|
|
|
|
Line {
|
|
property real implicitSize
|
|
|
|
Component.onCompleted: implicitSize = Qt.binding(() => parent.width * (1 - root.visualPosition) < parent.height ? parent.height : 4)
|
|
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.rightMargin: (parent.height - implicitHeight) / 2
|
|
|
|
implicitWidth: implicitSize
|
|
implicitHeight: implicitSize
|
|
|
|
radius: Tokens.rounding.full
|
|
color: root.fgColour
|
|
|
|
Behavior on implicitSize {
|
|
Anim {
|
|
type: Anim.FastSpatial
|
|
}
|
|
}
|
|
}
|
|
|
|
Wave {
|
|
id: wave
|
|
|
|
readonly property real targetWidth: parent.width * root.visualPosition
|
|
|
|
anchors.left: parent.left
|
|
|
|
Anim on implicitWidth {
|
|
running: true
|
|
to: wave.targetWidth
|
|
onFinished: wave.implicitWidth = Qt.binding(() => wave.targetWidth)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: indeterminateComp
|
|
|
|
Item {
|
|
id: content
|
|
|
|
Line {
|
|
bounds: {
|
|
const i = manager.activeIndicators[0]; // qmllint disable unresolved-type
|
|
return i ? root.toBounds(0, i.startFraction, i.gapSize / 2) : Qt.point(0, 0);
|
|
}
|
|
}
|
|
|
|
Line {
|
|
bounds: {
|
|
const i = manager.activeIndicators[manager.activeIndicators.length - 1]; // qmllint disable unresolved-type
|
|
return i ? root.toBounds(i.endFraction, 1, i.gapSize / 2) : Qt.point(0, 0);
|
|
}
|
|
}
|
|
|
|
Instantiator {
|
|
model: Math.max(manager.activeIndicators.length, 1) - 1 // qmllint disable unresolved-type
|
|
|
|
delegate: Line {
|
|
required property int index
|
|
readonly property LinearIndicatorSegment cur: manager.activeIndicators[index] // qmllint disable unresolved-type
|
|
readonly property LinearIndicatorSegment next: manager.activeIndicators[index + 1 % manager.activeIndicators.length] // qmllint disable unresolved-type
|
|
|
|
bounds: root.toBounds(cur.endFraction, next.startFraction, cur.gapSize / 2)
|
|
}
|
|
|
|
onObjectAdded: (_, obj) => content.data.push(obj)
|
|
onObjectRemoved: (_, obj) => {
|
|
const idx = content.data.indexOf(obj);
|
|
if (idx !== -1)
|
|
content.data.splice(idx, 1);
|
|
}
|
|
}
|
|
|
|
Instantiator {
|
|
model: manager.activeIndicators // qmllint disable unresolved-type
|
|
|
|
delegate: Wave {
|
|
required property LinearIndicatorSegment modelData
|
|
readonly property point bounds: root.toBounds(modelData.startFraction, modelData.endFraction, modelData.gapSize / 2)
|
|
|
|
x: bounds.x
|
|
implicitWidth: bounds.y - bounds.x
|
|
|
|
color: root.fgColour
|
|
}
|
|
|
|
onObjectAdded: (_, obj) => content.data.push(obj)
|
|
onObjectRemoved: (_, obj) => {
|
|
const idx = content.data.indexOf(obj);
|
|
if (idx !== -1)
|
|
content.data.splice(idx, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
component Line: StyledRect {
|
|
property point bounds
|
|
|
|
x: bounds.x
|
|
implicitWidth: bounds.y - bounds.x
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
implicitHeight: parent.height
|
|
|
|
radius: Tokens.rounding.full
|
|
color: root.fgColour
|
|
}
|
|
|
|
component Wave: WavyLine {
|
|
id: wave
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
implicitHeight: lineWidth * amplitudeMultiplier * 2 + lineWidth
|
|
|
|
lineWidth: parent.height
|
|
amplitudeMultiplier: root.wavy ? root.waveAmplitude : 0
|
|
frequency: root.waveFrequency
|
|
startX: x
|
|
fullLength: parent.width
|
|
color: root.fgColour
|
|
|
|
Anim on waveProgress {
|
|
running: true
|
|
paused: wave.amplitudeMultiplier === 0 || root.wavePaused
|
|
from: 0
|
|
to: 1
|
|
duration: root.waveDuration
|
|
easing.type: Easing.Linear
|
|
loops: Animation.Infinite
|
|
}
|
|
|
|
Behavior on amplitudeMultiplier {
|
|
Anim {
|
|
type: Anim.DefaultEffects
|
|
}
|
|
}
|
|
|
|
Behavior on color {
|
|
CAnim {}
|
|
}
|
|
}
|
|
}
|