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