C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 11s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Failing after 27s
Rust / build (pull_request) Successful in 54s
Python / verify (pull_request) Failing after 1m26s
Rust / fmt (pull_request) Successful in 46s
Rust / clippy (pull_request) Successful in 1m6s
152 lines
5.0 KiB
QML
152 lines
5.0 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import QtQuick.Shapes
|
|
import ZShell
|
|
import ZShell.Components
|
|
import ZShell.Config
|
|
import qs.Services
|
|
|
|
Item {
|
|
id: root
|
|
|
|
// Ring geometry, quantized in the item's own device pixel space and never
|
|
// against its position: the parent layout places the item on the device
|
|
// grid, so these values cannot change when the item moves (no jitter), and
|
|
// the ring stays crisp wherever it lands.
|
|
// Device-pixel center of the ring: an even stroke width wants a pixel
|
|
// boundary, an odd one a pixel center. Floored, matching
|
|
// WavyLineRenderer::snapCenterDev, so the wave and this background arc
|
|
// always land on the same center even when the box's device size is odd.
|
|
readonly property real centerDev: Math.floor(
|
|
sizeDev / 2 - strokeParity / 2
|
|
) + strokeParity / 2
|
|
readonly property real centerX: centerDev / dpr
|
|
readonly property real centerY: centerDev / dpr
|
|
// Whole device pixels, and one pixel of slack on the outer edge: the ring's
|
|
// antialias feather needs somewhere to fade, otherwise it is cut flat at
|
|
// the top/left/right/bottom of the item rect.
|
|
readonly property real arcRadius: Math.max(
|
|
1,
|
|
Math.floor(
|
|
(sizeDev - Math.round(padding * dpr) - strokeDev * (1 + waveAmplitude * 2) - 2) / 2
|
|
)
|
|
) / dpr
|
|
property color bgColor: Colors.palette.m3secondaryContainer
|
|
property real clampedVal: Math.max(1 / 360, Math.min(1, isNaN(value) ? 0 : value))
|
|
readonly property real dotAngleRad: (startAngle + sweepAngle - gapAngle * (sweepAngle < 360 ? 0 : 1)) * Math.PI / 180
|
|
readonly property real dpr: ZUtils.dprFor(root)
|
|
property color fgColor: Colors.palette.m3primary
|
|
readonly property real gapAngle: ((spacing + strokeLogical) / (arcRadius || 1)) * (180 / Math.PI)
|
|
property alias hasEndIndicator: dot.active
|
|
property real implicitSize
|
|
property int padding: 0
|
|
// The item's own size in whole device pixels. Never adjusted upwards: the
|
|
// texture is exactly this wide, so ring values derived from it cannot spill
|
|
// past the item's bounds.
|
|
readonly property real size: sizeDev / dpr
|
|
readonly property real sizeDev: Math.round(Math.min(width, height) * dpr)
|
|
property int spacing: Tokens.spacing.small
|
|
readonly property real strokeDev: Math.max(1, Math.round(strokeWidth * dpr))
|
|
readonly property real strokeLogical: strokeDev / dpr
|
|
readonly property real strokeParity: strokeDev % 2
|
|
property int startAngle: -90
|
|
property int strokeWidth: Tokens.padding.extraSmall
|
|
property int sweepAngle: 360
|
|
readonly property real thickness: strokeLogical * (1 + waveAmplitude) * 2
|
|
property real value
|
|
property alias waveAmplitude: wave.amplitudeMultiplier
|
|
property alias waveDuration: waveProgAnim.duration
|
|
property alias waveFrequency: wave.frequency
|
|
property bool wavePaused
|
|
property bool wavy: false
|
|
|
|
implicitHeight: implicitSize
|
|
implicitWidth: implicitSize
|
|
|
|
Shape {
|
|
asynchronous: true
|
|
opacity: Math.min(1, remainingArc.sweepAngle)
|
|
preferredRendererType: Shape.CurveRenderer
|
|
|
|
ShapePath {
|
|
capStyle: ShapePath.RoundCap
|
|
fillColor: "transparent"
|
|
strokeColor: root.bgColor
|
|
strokeWidth: Math.max(1, Math.round(Math.min(1, remainingArc.sweepAngle) * root.strokeDev)) / root.dpr
|
|
|
|
Behavior on strokeColor {
|
|
CAnim {
|
|
}
|
|
}
|
|
|
|
PathAngleArc {
|
|
id: remainingArc
|
|
|
|
centerX: root.centerX
|
|
centerY: root.centerY
|
|
radiusX: root.arcRadius
|
|
radiusY: root.arcRadius
|
|
startAngle: root.startAngle + root.clampedVal * root.sweepAngle + root.gapAngle
|
|
sweepAngle: Math.max(1 / 360, root.sweepAngle * (1 - root.clampedVal) - root.gapAngle * (root.sweepAngle < 360 ? 1 : 2))
|
|
}
|
|
}
|
|
}
|
|
|
|
WavyLine {
|
|
id: wave
|
|
|
|
amplitudeMultiplier: root.wavy ? 0.5 : 0
|
|
anchors.fill: parent
|
|
anchors.margins: -Math.round(lineWidth * amplitudeMultiplier * root.dpr) / root.dpr
|
|
color: root.fgColor
|
|
frequency: 8
|
|
fullAngle: root.sweepAngle
|
|
lineWidth: root.strokeWidth
|
|
objectName: "wave"
|
|
pathType: WavyLine.Arc
|
|
radius: root.arcRadius
|
|
startAngle: root.startAngle
|
|
value: root.clampedVal
|
|
|
|
Behavior on amplitudeMultiplier {
|
|
Anim {
|
|
type: Anim.DefaultEffects
|
|
}
|
|
}
|
|
Behavior on color {
|
|
CAnim {
|
|
}
|
|
}
|
|
Anim on waveProgress {
|
|
id: waveProgAnim
|
|
|
|
duration: 2000
|
|
easing.type: Easing.Linear
|
|
from: 0
|
|
loops: Animation.Infinite
|
|
paused: root.wavePaused || wave.amplitudeMultiplier === 0
|
|
running: true
|
|
to: 1
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: dot
|
|
|
|
// Rounded in local device pixels: the dot's own size is a whole even
|
|
// device count, so an integral local position puts its center on the grid
|
|
// whatever the ring's angle, and moving the gauge moves the dot with it.
|
|
x: Math.round((root.centerX + root.arcRadius * Math.cos(root.dotAngleRad) - width / 2) * root.dpr) / root.dpr
|
|
y: Math.round((root.centerY + root.arcRadius * Math.sin(root.dotAngleRad) - height / 2) * root.dpr) / root.dpr
|
|
|
|
sourceComponent: CustomRect {
|
|
color: root.fgColor
|
|
implicitHeight: Math.min(1, remainingArc.sweepAngle) * Math.min(4, root.strokeWidth)
|
|
implicitWidth: Math.min(1, remainingArc.sweepAngle) * Math.min(4, root.strokeWidth)
|
|
opacity: Math.min(1, remainingArc.sweepAngle)
|
|
radius: Tokens.rounding.full
|
|
}
|
|
}
|
|
}
|