make media control seekable (closes #1615)

This commit is contained in:
end-4
2025-09-28 10:45:16 +02:00
parent f430b22884
commit 28c37c08d2
6 changed files with 166 additions and 74 deletions
@@ -72,8 +72,8 @@ RippleButton {
Layout.bottomMargin: 5
Layout.fillWidth: true
value: 0.7
sperm: true
animateSperm: lightDarkButtonRoot.toggled
wavy: true
animateWave: lightDarkButtonRoot.toggled
highlightColor: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg
trackColor: ColorUtils.mix(lightDarkButtonRoot.previewBg, lightDarkButtonRoot.previewFg, 0.5)
}
@@ -1,12 +1,9 @@
import qs.services
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Qt5Compat.GraphicalEffects
/**
* Material 3 progress bar. See https://m3.material.io/components/progress-indicators/overview
@@ -18,13 +15,13 @@ ProgressBar {
property real valueBarGap: 4
property color highlightColor: Appearance?.colors.colPrimary ?? "#685496"
property color trackColor: Appearance?.m3colors.m3secondaryContainer ?? "#F1D3F9"
property bool sperm: false // If true, the progress bar will have a wavy fill effect
property bool animateSperm: true
property real spermAmplitudeMultiplier: sperm ? 0.5 : 0
property real spermFrequency: 6
property real spermFps: 60
property bool wavy: false // If true, the progress bar will have a wavy fill effect
property bool animateWave: true
property real waveAmplitudeMultiplier: wavy ? 0.5 : 0
property real waveFrequency: 6
property real waveFps: 60
Behavior on spermAmplitudeMultiplier {
Behavior on waveAmplitudeMultiplier {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
@@ -38,64 +35,62 @@ ProgressBar {
}
contentItem: Item {
id: contentItem
anchors.fill: parent
Canvas {
id: wavyFill
Loader {
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
height: parent.height * 6
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
var progress = root.visualPosition;
var fillWidth = progress * width;
var amplitude = parent.height * root.spermAmplitudeMultiplier;
var frequency = root.spermFrequency;
var phase = Date.now() / 400.0;
var centerY = height / 2;
ctx.strokeStyle = root.highlightColor;
ctx.lineWidth = parent.height;
ctx.lineCap = "round";
ctx.beginPath();
for (var x = ctx.lineWidth / 2; x <= fillWidth; x += 1) {
var waveY = centerY + amplitude * Math.sin(frequency * 2 * Math.PI * x / width + phase);
if (x === 0)
ctx.moveTo(x, waveY);
else
ctx.lineTo(x, waveY);
active: root.wavy
sourceComponent: WavyLine {
id: wavyFill
frequency: root.waveFrequency
color: root.highlightColor
amplitudeMultiplier: root.wavy ? 0.5 : 0
height: contentItem.height * 6
width: contentItem.width * root.visualPosition
lineWidth: contentItem.height
fullLength: root.width
Connections {
target: root
function onValueChanged() { wavyFill.requestPaint(); }
function onHighlightColorChanged() { wavyFill.requestPaint(); }
}
FrameAnimation {
running: root.animateWave
onTriggered: {
wavyFill.requestPaint()
}
}
ctx.stroke();
}
Connections {
target: root
function onValueChanged() { wavyFill.requestPaint(); }
function onHighlightColorChanged() { wavyFill.requestPaint(); }
}
Timer {
interval: 1000 / root.spermFps
running: root.animateSperm
repeat: root.sperm
onTriggered: wavyFill.requestPaint()
}
}
Loader {
active: !root.wavy
sourceComponent: Rectangle {
anchors.left: parent.left
width: contentItem.width * root.visualPosition
height: contentItem.height
radius: height / 2
color: root.highlightColor
}
}
Rectangle { // Right remaining part fill
anchors.right: parent.right
width: (1 - root.visualPosition) * parent.width - valueBarGap
height: parent.height
radius: Appearance?.rounding.full ?? 9999
radius: height / 2
color: root.trackColor
}
Rectangle { // Stop point
anchors.right: parent.right
width: valueBarGap
height: valueBarGap
radius: Appearance?.rounding.full ?? 9999
radius: height / 2
color: root.highlightColor
}
}
@@ -1,3 +1,4 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import qs.services
@@ -17,6 +18,7 @@ Slider {
property list<real> stopIndicatorValues: [1]
enum Configuration {
Wavy = 4,
XS = 12,
S = 18,
M = 30,
@@ -28,10 +30,9 @@ Slider {
property real handleDefaultWidth: 3
property real handlePressedWidth: 1.5
property color highlightColor: Appearance.colors.colPrimary
property color trackColor: Appearance.colors.colSecondaryContainer
property color handleColor: Appearance.m3colors.m3onSecondaryContainer
property color handleColor: Appearance.colors.colPrimary
property color dotColor: Appearance.m3colors.m3onSecondaryContainer
property color dotColorHighlighted: Appearance.m3colors.m3onPrimary
property real unsharpenRadius: Appearance.rounding.unsharpen
@@ -39,15 +40,18 @@ Slider {
property real trackRadius: trackWidth >= StyledSlider.Configuration.XL ? 21
: trackWidth >= StyledSlider.Configuration.L ? 12
: trackWidth >= StyledSlider.Configuration.M ? 9
: 6
property real handleHeight: Math.max(33, trackWidth + 9)
: trackWidth >= StyledSlider.Configuration.S ? 6
: height / 2
property real handleHeight: (configuration === StyledSlider.Configuration.Wavy) ? 24 : Math.max(33, trackWidth + 9)
property real handleWidth: root.pressed ? handlePressedWidth : handleDefaultWidth
property real handleMargins: 4
onHandleMarginsChanged: {
console.log("Handle margins changed to", handleMargins);
}
property real trackDotSize: 3
property string tooltipContent: `${Math.round(value * 100)}%`
property bool wavy: configuration === StyledSlider.Configuration.Wavy // If true, the progress bar will have a wavy fill effect
property bool animateWave: true
property real waveAmplitudeMultiplier: wavy ? 0.5 : 0
property real waveFrequency: 6
property real waveFps: 60
leftPadding: handleMargins
rightPadding: handleMargins
@@ -93,18 +97,51 @@ Slider {
implicitHeight: trackWidth
// Fill left
Rectangle {
Loader {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
}
width: root.handleMargins + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2 + root.handleMargins)
height: trackWidth
color: root.highlightColor
topLeftRadius: root.trackRadius
bottomLeftRadius: root.trackRadius
topRightRadius: root.unsharpenRadius
bottomRightRadius: root.unsharpenRadius
height: root.trackWidth
active: !root.wavy
sourceComponent: Rectangle {
color: root.highlightColor
topLeftRadius: root.trackRadius
bottomLeftRadius: root.trackRadius
topRightRadius: root.unsharpenRadius
bottomRightRadius: root.unsharpenRadius
}
}
Loader {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
}
width: root.handleMargins + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2 + root.handleMargins)
height: root.height
active: root.wavy
sourceComponent: WavyLine {
id: wavyFill
frequency: root.waveFrequency
fullLength: root.width
color: root.highlightColor
amplitudeMultiplier: root.wavy ? 0.5 : 0
width: root.handleMargins + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2 + root.handleMargins)
height: root.trackWidth
Connections {
target: root
function onValueChanged() { wavyFill.requestPaint(); }
function onHighlightColorChanged() { wavyFill.requestPaint(); }
}
FrameAnimation {
running: root.animateWave
onTriggered: {
wavyFill.requestPaint()
}
}
}
}
// Fill right
@@ -0,0 +1,34 @@
import qs.modules.common
import QtQuick
Canvas {
id: root
property real amplitudeMultiplier: 0.5
property real frequency: 6
property color color: Appearance?.colors.colPrimary ?? "#685496"
property real lineWidth: 4
property real fullLength: width
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
var amplitude = root.lineWidth * root.amplitudeMultiplier;
var frequency = root.frequency;
var phase = Date.now() / 400.0;
var centerY = height / 2;
ctx.strokeStyle = root.color;
ctx.lineWidth = root.lineWidth;
ctx.lineCap = "round";
ctx.beginPath();
for (var x = ctx.lineWidth / 2; x <= root.width - ctx.lineWidth / 2; x += 1) {
var waveY = centerY + amplitude * Math.sin(frequency * 2 * Math.PI * x / root.fullLength + phase);
if (x === 0)
ctx.moveTo(x, waveY);
else
ctx.lineTo(x, waveY);
}
ctx.stroke();
}
}
@@ -1,3 +1,4 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.models
import qs.modules.common.widgets
@@ -233,16 +234,41 @@ Item { // Player instance
Item {
id: progressBarContainer
Layout.fillWidth: true
implicitHeight: progressBar.implicitHeight
implicitHeight: Math.max(sliderLoader.implicitHeight, progressBarLoader.implicitHeight)
StyledProgressBar {
id: progressBar
Loader {
id: sliderLoader
anchors.fill: parent
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length
sperm: playerController.player?.isPlaying
active: playerController.player?.canSeek ?? false
sourceComponent: StyledSlider {
configuration: StyledSlider.Configuration.Wavy
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
handleColor: blendedColors.colPrimary
value: playerController.player?.position / playerController.player?.length
onMoved: {
playerController.player.position = value * playerController.player.length;
}
}
}
Loader {
id: progressBarLoader
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
}
active: !(playerController.player?.canSeek ?? false)
sourceComponent: StyledProgressBar {
wavy: playerController.player?.isPlaying
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length
}
}
}
TrackChangeButton {
iconName: "skip_next"
@@ -56,7 +56,7 @@ Item {
StyledSlider {
id: slider
value: root.node.audio.volume
onValueChanged: root.node.audio.volume = value
onMoved: root.node.audio.volume = value
}
}
}