feat: add wavy circular indicator
This commit is contained in:
parent
eed9e4f8a0
commit
13713aa5d7
4 changed files with 86 additions and 92 deletions
|
|
@ -20,7 +20,7 @@ BusyIndicator {
|
|||
}
|
||||
|
||||
property real implicitSize: Tokens.font.body.medium.pointSize * 3
|
||||
property real strokeWidth: Tokens.padding.small * 0.8
|
||||
property real strokeWidth: Tokens.padding.extraSmall
|
||||
property color fgColour: Colours.palette.m3primary
|
||||
property color bgColour: Colours.palette.m3secondaryContainer
|
||||
|
||||
|
|
|
|||
|
|
@ -1,68 +1,101 @@
|
|||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import Caelestia.Components
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
|
||||
Shape {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real value
|
||||
property int startAngle: -90
|
||||
property int sweepAngle: 360
|
||||
property int strokeWidth: Tokens.padding.small
|
||||
property int padding: 0
|
||||
property int spacing: Tokens.spacing.small
|
||||
property color fgColour: Colours.palette.m3primary
|
||||
property color bgColour: Colours.palette.m3secondaryContainer
|
||||
|
||||
property bool wavy: false
|
||||
property alias waveFrequency: wave.frequency
|
||||
property alias waveAmplitude: wave.amplitudeMultiplier
|
||||
property alias wavePaused: waveProgAnim.paused
|
||||
property alias waveDuration: waveProgAnim.duration
|
||||
|
||||
readonly property real size: Math.min(width, height)
|
||||
readonly property real arcRadius: (size - padding - strokeWidth) / 2
|
||||
readonly property real vValue: value || 1 / 360
|
||||
readonly property real arcRadius: (size - padding - strokeWidth * (1 + waveAmplitude * 2)) / 2
|
||||
readonly property real clampedVal: Math.max(1 / 360, Math.min(1, value))
|
||||
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
|
||||
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
asynchronous: true
|
||||
readonly property real thickness: strokeWidth * (1 + waveAmplitude) * 2 // For consumers
|
||||
property real implicitSize
|
||||
|
||||
ShapePath {
|
||||
fillColor: "transparent"
|
||||
strokeColor: root.bgColour
|
||||
strokeWidth: root.strokeWidth
|
||||
capStyle: root.Tokens.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
|
||||
implicitWidth: implicitSize
|
||||
implicitHeight: implicitSize
|
||||
|
||||
PathAngleArc {
|
||||
startAngle: root.startAngle + 360 * root.vValue + root.gapAngle
|
||||
sweepAngle: Math.max(-root.gapAngle, 360 * (1 - root.vValue) - root.gapAngle * 2)
|
||||
radiusX: root.arcRadius
|
||||
radiusY: root.arcRadius
|
||||
centerX: root.size / 2
|
||||
centerY: root.size / 2
|
||||
}
|
||||
Shape {
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
asynchronous: true
|
||||
opacity: Math.min(1, remainingArc.sweepAngle)
|
||||
|
||||
Behavior on strokeColor {
|
||||
CAnim {
|
||||
duration: Tokens.anim.durations.large
|
||||
ShapePath {
|
||||
fillColor: "transparent"
|
||||
strokeColor: root.bgColour
|
||||
strokeWidth: Math.min(1, remainingArc.sweepAngle) * root.strokeWidth
|
||||
capStyle: ShapePath.RoundCap
|
||||
|
||||
PathAngleArc {
|
||||
id: remainingArc
|
||||
|
||||
radiusX: root.arcRadius
|
||||
radiusY: root.arcRadius
|
||||
centerX: root.size / 2
|
||||
centerY: root.size / 2
|
||||
startAngle: root.startAngle + root.clampedVal * root.sweepAngle + root.gapAngle
|
||||
sweepAngle: Math.max(1 / 360, root.sweepAngle * (1 - root.clampedVal) - root.gapAngle * 2)
|
||||
}
|
||||
|
||||
Behavior on strokeColor {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
fillColor: "transparent"
|
||||
strokeColor: root.fgColour
|
||||
strokeWidth: root.strokeWidth
|
||||
capStyle: root.Tokens.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
|
||||
WavyLine {
|
||||
id: wave
|
||||
|
||||
PathAngleArc {
|
||||
startAngle: root.startAngle
|
||||
sweepAngle: 360 * root.vValue
|
||||
radiusX: root.arcRadius
|
||||
radiusY: root.arcRadius
|
||||
centerX: root.size / 2
|
||||
centerY: root.size / 2
|
||||
anchors.fill: parent
|
||||
anchors.margins: -lineWidth * amplitudeMultiplier
|
||||
|
||||
lineWidth: root.strokeWidth
|
||||
color: root.fgColour
|
||||
pathType: WavyLine.Arc
|
||||
radius: root.arcRadius
|
||||
startAngle: root.startAngle
|
||||
fullAngle: root.sweepAngle
|
||||
value: root.clampedVal
|
||||
frequency: 8
|
||||
amplitudeMultiplier: root.wavy ? 0.5 : 0
|
||||
|
||||
Anim on waveProgress {
|
||||
id: waveProgAnim
|
||||
|
||||
running: true
|
||||
from: 0
|
||||
to: 1
|
||||
duration: 2000
|
||||
easing.type: Easing.Linear
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
|
||||
Behavior on strokeColor {
|
||||
CAnim {
|
||||
duration: Tokens.anim.durations.large
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
Behavior on amplitudeMultiplier {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ Item {
|
|||
return active?.length ? (active.position % active.length) / active.length : 0;
|
||||
}
|
||||
|
||||
readonly property real arcCoverGap: Tokens.spacing.small
|
||||
readonly property real arcRadius: (cover.width + Tokens.sizes.dashboard.mediaProgressThickness) / 2 + arcCoverGap
|
||||
readonly property real arcGap: (Tokens.spacing.extraSmall + Tokens.sizes.dashboard.mediaProgressThickness) / arcRadius * 180 / Math.PI
|
||||
readonly property real arcCoverGap: Tokens.spacing.extraSmall
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
|
|
@ -45,59 +43,22 @@ Item {
|
|||
service: Audio.beatTracker
|
||||
}
|
||||
|
||||
Shape {
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
opacity: Math.min(1, remainingArc.sweepAngle)
|
||||
CircularProgress {
|
||||
id: prog
|
||||
|
||||
ShapePath {
|
||||
fillColor: "transparent"
|
||||
strokeColor: Colours.palette.m3secondaryContainer
|
||||
strokeWidth: Math.min(1, remainingArc.sweepAngle) * root.Tokens.sizes.dashboard.mediaProgressThickness
|
||||
capStyle: ShapePath.RoundCap
|
||||
anchors.centerIn: cover
|
||||
implicitSize: cover.width + root.arcCoverGap + thickness * 2
|
||||
|
||||
PathAngleArc {
|
||||
id: remainingArc
|
||||
|
||||
centerX: cover.x + cover.width / 2
|
||||
centerY: cover.y + cover.height / 2
|
||||
radiusX: root.arcRadius
|
||||
radiusY: root.arcRadius
|
||||
startAngle: -90 - root.Tokens.sizes.dashboard.mediaProgressSweep / 2 + root.playerProgress * root.Tokens.sizes.dashboard.mediaProgressSweep + root.arcGap
|
||||
sweepAngle: Math.max(0.1, root.Tokens.sizes.dashboard.mediaProgressSweep * (1 - root.playerProgress) - root.arcGap)
|
||||
}
|
||||
|
||||
Behavior on strokeColor {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WavyLine {
|
||||
anchors.fill: cover
|
||||
anchors.margins: -lineWidth * amplitudeMultiplier * 2 - lineWidth - root.arcCoverGap
|
||||
|
||||
lineWidth: Tokens.sizes.dashboard.mediaProgressThickness
|
||||
color: Colours.palette.m3primary
|
||||
pathType: WavyLine.Arc
|
||||
radius: root.arcRadius
|
||||
frequency: 8
|
||||
startAngle: -fullAngle / 2
|
||||
fullAngle: Tokens.sizes.dashboard.mediaProgressSweep
|
||||
fgColour: Colours.palette.m3primary
|
||||
strokeWidth: Tokens.sizes.dashboard.mediaProgressThickness
|
||||
startAngle: -90 - sweepAngle / 2
|
||||
sweepAngle: Tokens.sizes.dashboard.mediaProgressSweep
|
||||
value: root.playerProgress
|
||||
|
||||
Anim on waveProgress {
|
||||
running: true
|
||||
paused: !Players.active?.isPlaying
|
||||
from: 0
|
||||
to: 1
|
||||
duration: 2000
|
||||
easing.type: Easing.Linear
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
wavy: true
|
||||
waveFrequency: 8
|
||||
waveDuration: 2000
|
||||
wavePaused: !Players.active?.isPlaying
|
||||
}
|
||||
|
||||
Item {
|
||||
|
|
@ -106,7 +67,7 @@ Item {
|
|||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Tokens.padding.large + Tokens.sizes.dashboard.mediaProgressThickness + root.arcCoverGap
|
||||
anchors.margins: Tokens.padding.medium + root.arcCoverGap + prog.thickness
|
||||
implicitHeight: width
|
||||
|
||||
// Slight glow to separate from bg
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ WavyLine::WavyLine(QQuickItem* parent)
|
|||
, m_fullAngle(360)
|
||||
, m_radius(-1)
|
||||
, m_value(1)
|
||||
, m_startAngleRad(-M_PI / 2.0)
|
||||
, m_startAngleRad(0)
|
||||
, m_fullAngleRad(2 * M_PI) {
|
||||
setAntialiasing(true);
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ qreal WavyLine::startAngle() const {
|
|||
void WavyLine::setStartAngle(qreal startAngle) {
|
||||
if (!qFuzzyCompare(m_startAngle + 1.0, startAngle + 1.0)) {
|
||||
m_startAngle = startAngle;
|
||||
m_startAngleRad = startAngle * M_PI / 180.0 - M_PI / 2.0;
|
||||
m_startAngleRad = startAngle * M_PI / 180.0;
|
||||
emit startAngleChanged();
|
||||
update();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue