mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
optional wavy media playback bars
This commit is contained in:
@@ -50,6 +50,7 @@ Singleton {
|
|||||||
property int maxWorkspaceIcons: 3
|
property int maxWorkspaceIcons: 3
|
||||||
property bool workspacesPerMonitor: true
|
property bool workspacesPerMonitor: true
|
||||||
property var workspaceNameIcons: ({})
|
property var workspaceNameIcons: ({})
|
||||||
|
property bool waveProgressEnabled: true
|
||||||
property bool clockCompactMode: false
|
property bool clockCompactMode: false
|
||||||
property bool focusedWindowCompactMode: false
|
property bool focusedWindowCompactMode: false
|
||||||
property bool runningAppsCompactMode: true
|
property bool runningAppsCompactMode: true
|
||||||
@@ -205,6 +206,7 @@ Singleton {
|
|||||||
maxWorkspaceIcons = settings.maxWorkspaceIcons !== undefined ? settings.maxWorkspaceIcons : 3
|
maxWorkspaceIcons = settings.maxWorkspaceIcons !== undefined ? settings.maxWorkspaceIcons : 3
|
||||||
workspaceNameIcons = settings.workspaceNameIcons !== undefined ? settings.workspaceNameIcons : ({})
|
workspaceNameIcons = settings.workspaceNameIcons !== undefined ? settings.workspaceNameIcons : ({})
|
||||||
workspacesPerMonitor = settings.workspacesPerMonitor !== undefined ? settings.workspacesPerMonitor : true
|
workspacesPerMonitor = settings.workspacesPerMonitor !== undefined ? settings.workspacesPerMonitor : true
|
||||||
|
waveProgressEnabled = settings.waveProgressEnabled !== undefined ? settings.waveProgressEnabled : true
|
||||||
clockCompactMode = settings.clockCompactMode !== undefined ? settings.clockCompactMode : false
|
clockCompactMode = settings.clockCompactMode !== undefined ? settings.clockCompactMode : false
|
||||||
focusedWindowCompactMode = settings.focusedWindowCompactMode !== undefined ? settings.focusedWindowCompactMode : false
|
focusedWindowCompactMode = settings.focusedWindowCompactMode !== undefined ? settings.focusedWindowCompactMode : false
|
||||||
runningAppsCompactMode = settings.runningAppsCompactMode !== undefined ? settings.runningAppsCompactMode : true
|
runningAppsCompactMode = settings.runningAppsCompactMode !== undefined ? settings.runningAppsCompactMode : true
|
||||||
@@ -320,6 +322,7 @@ Singleton {
|
|||||||
"maxWorkspaceIcons": maxWorkspaceIcons,
|
"maxWorkspaceIcons": maxWorkspaceIcons,
|
||||||
"workspacesPerMonitor": workspacesPerMonitor,
|
"workspacesPerMonitor": workspacesPerMonitor,
|
||||||
"workspaceNameIcons": workspaceNameIcons,
|
"workspaceNameIcons": workspaceNameIcons,
|
||||||
|
"waveProgressEnabled": waveProgressEnabled,
|
||||||
"clockCompactMode": clockCompactMode,
|
"clockCompactMode": clockCompactMode,
|
||||||
"focusedWindowCompactMode": focusedWindowCompactMode,
|
"focusedWindowCompactMode": focusedWindowCompactMode,
|
||||||
"runningAppsCompactMode": runningAppsCompactMode,
|
"runningAppsCompactMode": runningAppsCompactMode,
|
||||||
@@ -391,6 +394,11 @@ Singleton {
|
|||||||
saveSettings()
|
saveSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setWaveProgressEnabled(enabled) {
|
||||||
|
waveProgressEnabled = enabled
|
||||||
|
saveSettings()
|
||||||
|
}
|
||||||
|
|
||||||
function setWorkspaceNameIcon(workspaceName, iconData) {
|
function setWorkspaceNameIcon(workspaceName, iconData) {
|
||||||
var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons))
|
var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons))
|
||||||
iconMap[workspaceName] = iconData
|
iconMap[workspaceName] = iconData
|
||||||
|
|||||||
@@ -42,11 +42,13 @@ Item {
|
|||||||
Timer {
|
Timer {
|
||||||
id: positionTimer
|
id: positionTimer
|
||||||
interval: 300
|
interval: 300
|
||||||
running: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing && !progressSliderArea.isSeeking
|
running: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing && !isSeeking
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: activePlayer && activePlayer.positionSupported && activePlayer.positionChanged()
|
onTriggered: activePlayer && activePlayer.positionSupported && activePlayer.positionChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool isSeeking: false
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: cleanupTimer
|
id: cleanupTimer
|
||||||
interval: 2000
|
interval: 2000
|
||||||
@@ -369,106 +371,173 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Progress Bar
|
Loader {
|
||||||
Item {
|
width: parent.width + 4
|
||||||
id: progressSlider
|
|
||||||
width: parent.width
|
|
||||||
height: 20
|
height: 20
|
||||||
|
x: -2
|
||||||
visible: activePlayer?.length > 0
|
visible: activePlayer?.length > 0
|
||||||
|
sourceComponent: SettingsData.waveProgressEnabled ? waveProgressComponent : flatProgressComponent
|
||||||
|
|
||||||
property real value: ratio
|
Component {
|
||||||
property real lineWidth: 2.5
|
id: waveProgressComponent
|
||||||
property color trackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.40)
|
|
||||||
property color fillColor: Theme.primary
|
|
||||||
property color playheadColor: Theme.primary
|
|
||||||
readonly property real midY: height / 2
|
|
||||||
|
|
||||||
// Background track
|
M3WaveProgress {
|
||||||
Rectangle {
|
value: ratio
|
||||||
width: parent.width
|
isPlaying: activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||||
height: progressSlider.lineWidth
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
color: progressSlider.trackColor
|
|
||||||
radius: height / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filled portion
|
MouseArea {
|
||||||
Rectangle {
|
id: progressSliderArea
|
||||||
width: Math.max(0, Math.min(parent.width, parent.width * progressSlider.value))
|
anchors.fill: parent
|
||||||
height: progressSlider.lineWidth
|
hoverEnabled: true
|
||||||
anchors.left: parent.left
|
cursorShape: Qt.PointingHandCursor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
enabled: activePlayer ? (activePlayer.canSeek && activePlayer.length > 0) : false
|
||||||
color: progressSlider.fillColor
|
|
||||||
radius: height / 2
|
|
||||||
Behavior on width { NumberAnimation { duration: 80 } }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Playhead
|
property real pendingSeekPosition: -1
|
||||||
Rectangle {
|
|
||||||
id: playhead
|
|
||||||
width: 2.5
|
|
||||||
height: Math.max(progressSlider.lineWidth + 8, 12)
|
|
||||||
radius: width / 2
|
|
||||||
color: progressSlider.playheadColor
|
|
||||||
x: Math.max(0, Math.min(progressSlider.width, progressSlider.width * progressSlider.value)) - width / 2
|
|
||||||
y: progressSlider.midY - height / 2
|
|
||||||
z: 3
|
|
||||||
Behavior on x { NumberAnimation { duration: 80 } }
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
Timer {
|
||||||
id: progressSliderArea
|
id: seekDebounceTimer
|
||||||
anchors.fill: parent
|
interval: 150
|
||||||
hoverEnabled: true
|
onTriggered: {
|
||||||
cursorShape: Qt.PointingHandCursor
|
if (progressSliderArea.pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
||||||
enabled: activePlayer ? (activePlayer.canSeek && activePlayer.length > 0) : false
|
const clamped = Math.min(progressSliderArea.pendingSeekPosition, activePlayer.length * 0.99)
|
||||||
|
activePlayer.position = clamped
|
||||||
|
progressSliderArea.pendingSeekPosition = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
property bool isSeeking: false
|
onPressed: (mouse) => {
|
||||||
property real pendingSeekPosition: -1
|
root.isSeeking = true
|
||||||
|
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
Timer {
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
id: seekDebounceTimer
|
pendingSeekPosition = r * activePlayer.length
|
||||||
interval: 150
|
displayPosition = pendingSeekPosition
|
||||||
onTriggered: {
|
seekDebounceTimer.restart()
|
||||||
if (progressSliderArea.pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
}
|
||||||
const clamped = Math.min(progressSliderArea.pendingSeekPosition, activePlayer.length * 0.99)
|
}
|
||||||
activePlayer.position = clamped
|
onReleased: {
|
||||||
progressSliderArea.pendingSeekPosition = -1
|
root.isSeeking = false
|
||||||
|
seekDebounceTimer.stop()
|
||||||
|
if (pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
||||||
|
const clamped = Math.min(pendingSeekPosition, activePlayer.length * 0.99)
|
||||||
|
activePlayer.position = clamped
|
||||||
|
pendingSeekPosition = -1
|
||||||
|
}
|
||||||
|
displayPosition = Qt.binding(() => currentPosition)
|
||||||
|
}
|
||||||
|
onPositionChanged: (mouse) => {
|
||||||
|
if (pressed && root.isSeeking && activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
|
pendingSeekPosition = r * activePlayer.length
|
||||||
|
displayPosition = pendingSeekPosition
|
||||||
|
seekDebounceTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
|
activePlayer.position = r * activePlayer.length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onPressed: (mouse) => {
|
Component {
|
||||||
isSeeking = true
|
id: flatProgressComponent
|
||||||
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / progressSlider.width))
|
Item {
|
||||||
pendingSeekPosition = r * activePlayer.length
|
property real value: ratio
|
||||||
displayPosition = pendingSeekPosition
|
property real lineWidth: 2.5
|
||||||
seekDebounceTimer.restart()
|
property color trackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.40)
|
||||||
|
property color fillColor: Theme.primary
|
||||||
|
property color playheadColor: Theme.primary
|
||||||
|
readonly property real midY: height / 2
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.lineWidth
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: parent.trackColor
|
||||||
|
radius: height / 2
|
||||||
}
|
}
|
||||||
}
|
|
||||||
onReleased: {
|
Rectangle {
|
||||||
isSeeking = false
|
width: Math.max(0, Math.min(parent.width, parent.width * parent.value))
|
||||||
seekDebounceTimer.stop()
|
height: parent.lineWidth
|
||||||
if (pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
anchors.left: parent.left
|
||||||
const clamped = Math.min(pendingSeekPosition, activePlayer.length * 0.99)
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
activePlayer.position = clamped
|
color: parent.fillColor
|
||||||
pendingSeekPosition = -1
|
radius: height / 2
|
||||||
|
Behavior on width { NumberAnimation { duration: 80 } }
|
||||||
}
|
}
|
||||||
displayPosition = Qt.binding(() => currentPosition)
|
|
||||||
}
|
Rectangle {
|
||||||
onPositionChanged: (mouse) => {
|
id: playhead
|
||||||
if (pressed && isSeeking && activePlayer?.length > 0 && activePlayer?.canSeek) {
|
width: 2.5
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / progressSlider.width))
|
height: Math.max(parent.lineWidth + 8, 12)
|
||||||
pendingSeekPosition = r * activePlayer.length
|
radius: width / 2
|
||||||
displayPosition = pendingSeekPosition
|
color: parent.playheadColor
|
||||||
seekDebounceTimer.restart()
|
x: Math.max(0, Math.min(parent.width, parent.width * parent.value)) - width / 2
|
||||||
|
y: parent.midY - height / 2
|
||||||
|
z: 3
|
||||||
|
Behavior on x { NumberAnimation { duration: 80 } }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
onClicked: (mouse) => {
|
MouseArea {
|
||||||
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
id: progressSliderArea
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / progressSlider.width))
|
anchors.fill: parent
|
||||||
activePlayer.position = r * activePlayer.length
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
enabled: activePlayer ? (activePlayer.canSeek && activePlayer.length > 0) : false
|
||||||
|
|
||||||
|
property real pendingSeekPosition: -1
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: seekDebounceTimer
|
||||||
|
interval: 150
|
||||||
|
onTriggered: {
|
||||||
|
if (progressSliderArea.pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
||||||
|
const clamped = Math.min(progressSliderArea.pendingSeekPosition, activePlayer.length * 0.99)
|
||||||
|
activePlayer.position = clamped
|
||||||
|
progressSliderArea.pendingSeekPosition = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressed: (mouse) => {
|
||||||
|
root.isSeeking = true
|
||||||
|
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
|
pendingSeekPosition = r * activePlayer.length
|
||||||
|
displayPosition = pendingSeekPosition
|
||||||
|
seekDebounceTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
root.isSeeking = false
|
||||||
|
seekDebounceTimer.stop()
|
||||||
|
if (pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
||||||
|
const clamped = Math.min(pendingSeekPosition, activePlayer.length * 0.99)
|
||||||
|
activePlayer.position = clamped
|
||||||
|
pendingSeekPosition = -1
|
||||||
|
}
|
||||||
|
displayPosition = Qt.binding(() => currentPosition)
|
||||||
|
}
|
||||||
|
onPositionChanged: (mouse) => {
|
||||||
|
if (pressed && root.isSeeking && activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
|
pendingSeekPosition = r * activePlayer.length
|
||||||
|
displayPosition = pendingSeekPosition
|
||||||
|
seekDebounceTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
|
activePlayer.position = r * activePlayer.length
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ Card {
|
|||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 300
|
interval: 300
|
||||||
running: activePlayer?.playbackState === MprisPlaybackState.Playing && !progressMouseArea.isSeeking
|
running: activePlayer?.playbackState === MprisPlaybackState.Playing && !isSeeking
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: activePlayer?.positionSupported && activePlayer.positionChanged()
|
onTriggered: activePlayer?.positionSupported && activePlayer.positionChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool isSeeking: false
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
@@ -280,101 +282,172 @@ Card {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Loader {
|
||||||
id: progressSlider
|
width: parent.width + 4
|
||||||
width: parent.width
|
|
||||||
height: 20
|
height: 20
|
||||||
|
x: -2
|
||||||
visible: activePlayer?.length > 0
|
visible: activePlayer?.length > 0
|
||||||
|
sourceComponent: SettingsData.waveProgressEnabled ? waveProgressComponent : flatProgressComponent
|
||||||
|
|
||||||
property real value: ratio
|
Component {
|
||||||
property real lineWidth: 2.5
|
id: waveProgressComponent
|
||||||
property color trackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.40)
|
|
||||||
property color fillColor: Theme.primary
|
|
||||||
property color playheadColor: Theme.primary
|
|
||||||
|
|
||||||
Rectangle {
|
M3WaveProgress {
|
||||||
width: parent.width
|
value: ratio
|
||||||
height: progressSlider.lineWidth
|
isPlaying: activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
color: progressSlider.trackColor
|
|
||||||
radius: height / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
MouseArea {
|
||||||
width: Math.max(0, Math.min(parent.width, parent.width * progressSlider.value))
|
id: progressMouseArea
|
||||||
height: progressSlider.lineWidth
|
anchors.fill: parent
|
||||||
anchors.left: parent.left
|
hoverEnabled: true
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
cursorShape: Qt.PointingHandCursor
|
||||||
color: progressSlider.fillColor
|
enabled: activePlayer ? (activePlayer.canSeek && activePlayer.length > 0) : false
|
||||||
radius: height / 2
|
|
||||||
Behavior on width { NumberAnimation { duration: 80 } }
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
property real pendingSeekPosition: -1
|
||||||
id: playhead
|
|
||||||
width: 2.5
|
|
||||||
height: Math.max(progressSlider.lineWidth + 8, 12)
|
|
||||||
radius: width / 2
|
|
||||||
color: progressSlider.playheadColor
|
|
||||||
x: Math.max(0, Math.min(progressSlider.width, progressSlider.width * progressSlider.value)) - width / 2
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
z: 3
|
|
||||||
Behavior on x { NumberAnimation { duration: 80 } }
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
Timer {
|
||||||
id: progressMouseArea
|
id: seekDebounceTimer
|
||||||
anchors.fill: parent
|
interval: 150
|
||||||
hoverEnabled: true
|
onTriggered: {
|
||||||
cursorShape: Qt.PointingHandCursor
|
if (progressMouseArea.pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
||||||
enabled: activePlayer ? (activePlayer.canSeek && activePlayer.length > 0) : false
|
const clamped = Math.min(progressMouseArea.pendingSeekPosition, activePlayer.length * 0.99)
|
||||||
|
activePlayer.position = clamped
|
||||||
|
progressMouseArea.pendingSeekPosition = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
property bool isSeeking: false
|
onPressed: (mouse) => {
|
||||||
property real pendingSeekPosition: -1
|
root.isSeeking = true
|
||||||
|
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
Timer {
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
id: seekDebounceTimer
|
pendingSeekPosition = r * activePlayer.length
|
||||||
interval: 150
|
displayPosition = pendingSeekPosition
|
||||||
onTriggered: {
|
seekDebounceTimer.restart()
|
||||||
if (progressMouseArea.pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
}
|
||||||
const clamped = Math.min(progressMouseArea.pendingSeekPosition, activePlayer.length * 0.99)
|
}
|
||||||
activePlayer.position = clamped
|
onReleased: {
|
||||||
progressMouseArea.pendingSeekPosition = -1
|
root.isSeeking = false
|
||||||
|
seekDebounceTimer.stop()
|
||||||
|
if (pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
||||||
|
const clamped = Math.min(pendingSeekPosition, activePlayer.length * 0.99)
|
||||||
|
activePlayer.position = clamped
|
||||||
|
pendingSeekPosition = -1
|
||||||
|
}
|
||||||
|
displayPosition = Qt.binding(() => currentPosition)
|
||||||
|
}
|
||||||
|
onPositionChanged: (mouse) => {
|
||||||
|
if (pressed && root.isSeeking && activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
|
pendingSeekPosition = r * activePlayer.length
|
||||||
|
displayPosition = pendingSeekPosition
|
||||||
|
seekDebounceTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
|
activePlayer.position = r * activePlayer.length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onPressed: (mouse) => {
|
Component {
|
||||||
isSeeking = true
|
id: flatProgressComponent
|
||||||
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / progressSlider.width))
|
Item {
|
||||||
pendingSeekPosition = r * activePlayer.length
|
property real value: ratio
|
||||||
displayPosition = pendingSeekPosition
|
property real lineWidth: 2.5
|
||||||
seekDebounceTimer.restart()
|
property color trackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.40)
|
||||||
|
property color fillColor: Theme.primary
|
||||||
|
property color playheadColor: Theme.primary
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.lineWidth
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: parent.trackColor
|
||||||
|
radius: height / 2
|
||||||
}
|
}
|
||||||
}
|
|
||||||
onReleased: {
|
Rectangle {
|
||||||
isSeeking = false
|
width: Math.max(0, Math.min(parent.width, parent.width * parent.value))
|
||||||
seekDebounceTimer.stop()
|
height: parent.lineWidth
|
||||||
if (pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
anchors.left: parent.left
|
||||||
const clamped = Math.min(pendingSeekPosition, activePlayer.length * 0.99)
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
activePlayer.position = clamped
|
color: parent.fillColor
|
||||||
pendingSeekPosition = -1
|
radius: height / 2
|
||||||
|
Behavior on width { NumberAnimation { duration: 80 } }
|
||||||
}
|
}
|
||||||
displayPosition = Qt.binding(() => currentPosition)
|
|
||||||
}
|
Rectangle {
|
||||||
onPositionChanged: (mouse) => {
|
id: playhead
|
||||||
if (pressed && isSeeking && activePlayer?.length > 0 && activePlayer?.canSeek) {
|
width: 2.5
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / progressSlider.width))
|
height: Math.max(parent.lineWidth + 8, 12)
|
||||||
pendingSeekPosition = r * activePlayer.length
|
radius: width / 2
|
||||||
displayPosition = pendingSeekPosition
|
color: parent.playheadColor
|
||||||
seekDebounceTimer.restart()
|
x: Math.max(0, Math.min(parent.width, parent.width * parent.value)) - width / 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
z: 3
|
||||||
|
Behavior on x { NumberAnimation { duration: 80 } }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
onClicked: (mouse) => {
|
MouseArea {
|
||||||
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
id: progressMouseArea
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / progressSlider.width))
|
anchors.fill: parent
|
||||||
activePlayer.position = r * activePlayer.length
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
enabled: activePlayer ? (activePlayer.canSeek && activePlayer.length > 0) : false
|
||||||
|
|
||||||
|
property real pendingSeekPosition: -1
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: seekDebounceTimer
|
||||||
|
interval: 150
|
||||||
|
onTriggered: {
|
||||||
|
if (progressMouseArea.pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
||||||
|
const clamped = Math.min(progressMouseArea.pendingSeekPosition, activePlayer.length * 0.99)
|
||||||
|
activePlayer.position = clamped
|
||||||
|
progressMouseArea.pendingSeekPosition = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressed: (mouse) => {
|
||||||
|
root.isSeeking = true
|
||||||
|
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
|
pendingSeekPosition = r * activePlayer.length
|
||||||
|
displayPosition = pendingSeekPosition
|
||||||
|
seekDebounceTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
root.isSeeking = false
|
||||||
|
seekDebounceTimer.stop()
|
||||||
|
if (pendingSeekPosition >= 0 && activePlayer?.canSeek && activePlayer?.length > 0) {
|
||||||
|
const clamped = Math.min(pendingSeekPosition, activePlayer.length * 0.99)
|
||||||
|
activePlayer.position = clamped
|
||||||
|
pendingSeekPosition = -1
|
||||||
|
}
|
||||||
|
displayPosition = Qt.binding(() => currentPosition)
|
||||||
|
}
|
||||||
|
onPositionChanged: (mouse) => {
|
||||||
|
if (pressed && root.isSeeking && activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
|
pendingSeekPosition = r * activePlayer.length
|
||||||
|
displayPosition = pendingSeekPosition
|
||||||
|
seekDebounceTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
if (activePlayer?.length > 0 && activePlayer?.canSeek) {
|
||||||
|
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
||||||
|
activePlayer.position = r * activePlayer.length
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -297,6 +297,55 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: mediaSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
|
||||||
|
Theme.surfaceVariant.b, 0.3)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mediaSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "music_note"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Media Player Settings"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: "Wave Progress Bars"
|
||||||
|
description: "Use animated wave progress bars for media playback"
|
||||||
|
checked: SettingsData.waveProgressEnabled
|
||||||
|
onToggled: checked => {
|
||||||
|
return SettingsData.setWaveProgressEnabled(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StyledRect {
|
StyledRect {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: runningAppsSection.implicitHeight + Theme.spacingL * 2
|
height: runningAppsSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
|||||||
228
Widgets/M3WaveProgress.qml
Normal file
228
Widgets/M3WaveProgress.qml
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Shapes
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real value: 0
|
||||||
|
property real lineWidth: 2
|
||||||
|
property real wavelength: 20
|
||||||
|
property real amp: 1.6
|
||||||
|
property real phase: 0.0
|
||||||
|
property bool isPlaying: false
|
||||||
|
property real currentAmp: 1.6
|
||||||
|
property int samples: Math.max(48, Math.round(width / 3))
|
||||||
|
property color trackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.40)
|
||||||
|
property color fillColor: Theme.primary
|
||||||
|
property color playheadColor: Theme.primary
|
||||||
|
|
||||||
|
property real dpr: (root.window ? root.window.devicePixelRatio : 1)
|
||||||
|
function snap(v) { return Math.round(v * dpr) / dpr }
|
||||||
|
|
||||||
|
readonly property real playX: snap(root.width * root.value)
|
||||||
|
readonly property real midY: snap(height / 2)
|
||||||
|
readonly property real capPad: Math.ceil(lineWidth / 2)
|
||||||
|
|
||||||
|
function yWave(x) {
|
||||||
|
return midY + currentAmp * Math.sin((x / wavelength) * 2 * Math.PI + phase)
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on currentAmp {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 300
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onIsPlayingChanged: {
|
||||||
|
currentAmp = isPlaying ? amp : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
id: flatTrack
|
||||||
|
anchors.fill: parent
|
||||||
|
antialiasing: true
|
||||||
|
asynchronous: false
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
layer.enabled: true
|
||||||
|
layer.smooth: true
|
||||||
|
layer.samples: 8
|
||||||
|
layer.textureSize: Qt.size(Math.ceil(width * dpr * 2), Math.ceil(height * dpr * 2))
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
id: flatPath
|
||||||
|
strokeColor: root.trackColor
|
||||||
|
strokeWidth: snap(root.lineWidth)
|
||||||
|
capStyle: ShapePath.RoundCap
|
||||||
|
joinStyle: ShapePath.RoundJoin
|
||||||
|
fillColor: "transparent"
|
||||||
|
|
||||||
|
PathMove {
|
||||||
|
id: flatStart
|
||||||
|
x: 0
|
||||||
|
y: root.midY
|
||||||
|
}
|
||||||
|
|
||||||
|
PathLine {
|
||||||
|
id: flatEnd
|
||||||
|
x: root.width
|
||||||
|
y: root.midY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: waveContainer
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
width: Math.max(0, Math.min(parent.width, root.playX))
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
id: waveShape
|
||||||
|
anchors.fill: parent
|
||||||
|
antialiasing: true
|
||||||
|
asynchronous: false
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
layer.enabled: true
|
||||||
|
layer.smooth: true
|
||||||
|
layer.samples: 8
|
||||||
|
layer.textureSize: Qt.size(Math.ceil(width * dpr * 2), Math.ceil(height * dpr * 2))
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
id: wavePath
|
||||||
|
strokeColor: root.fillColor
|
||||||
|
strokeWidth: snap(root.lineWidth)
|
||||||
|
capStyle: ShapePath.RoundCap
|
||||||
|
joinStyle: ShapePath.RoundJoin
|
||||||
|
fillColor: "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property var cubics: []
|
||||||
|
property real startY: root.midY + root.currentAmp * Math.sin(root.phase)
|
||||||
|
property real endY: root.midY + root.currentAmp * Math.sin((root.playX / root.wavelength) * 2 * Math.PI + root.phase)
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: moveComp
|
||||||
|
PathMove {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: cubicComp
|
||||||
|
PathCubic {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildWave() {
|
||||||
|
wavePath.pathElements = []
|
||||||
|
cubics = []
|
||||||
|
wavePath.pathElements.push(moveComp.createObject(wavePath))
|
||||||
|
for (let i = 0; i < samples - 1; ++i) {
|
||||||
|
const cubic = cubicComp.createObject(wavePath)
|
||||||
|
wavePath.pathElements.push(cubic)
|
||||||
|
cubics.push(cubic)
|
||||||
|
}
|
||||||
|
updateWave()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWave() {
|
||||||
|
if (cubics.length === 0) return
|
||||||
|
const step = root.width / (samples - 1)
|
||||||
|
const startX = snap(root.lineWidth / 2)
|
||||||
|
const r = root.lineWidth / 2
|
||||||
|
const aaBias = 0.25 / dpr
|
||||||
|
|
||||||
|
function y(x) { return yWave(x) }
|
||||||
|
function dy(x) {
|
||||||
|
return currentAmp * (2 * Math.PI / wavelength) * Math.cos((x / wavelength) * 2 * Math.PI + phase)
|
||||||
|
}
|
||||||
|
|
||||||
|
const m = wavePath.pathElements[0]
|
||||||
|
m.x = startX
|
||||||
|
m.y = y(startX)
|
||||||
|
|
||||||
|
for (let i = 0; i < cubics.length; ++i) {
|
||||||
|
const x0 = startX + i * step
|
||||||
|
const x1 = startX + (i + 1) * step
|
||||||
|
|
||||||
|
// Stop exactly at playX
|
||||||
|
if (x0 >= root.playX) {
|
||||||
|
// This segment is entirely past the playhead - collapse it
|
||||||
|
const seg = cubics[i]
|
||||||
|
seg.control1X = seg.control2X = seg.x = root.playX
|
||||||
|
const py = y(root.playX)
|
||||||
|
seg.control1Y = seg.control2Y = seg.y = py
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const xe = Math.min(x1, root.playX - r - aaBias)
|
||||||
|
const p0x = x0, p0y = y(x0)
|
||||||
|
const p1x = xe, p1y = y(xe)
|
||||||
|
const dx = xe - x0
|
||||||
|
|
||||||
|
if (dx <= 0) {
|
||||||
|
// Zero-length segment
|
||||||
|
const seg = cubics[i]
|
||||||
|
seg.control1X = seg.control2X = seg.x = root.playX
|
||||||
|
const py = y(root.playX)
|
||||||
|
seg.control1Y = seg.control2Y = seg.y = py
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const c1x = p0x + dx / 4
|
||||||
|
const c1y = p0y + (dx * dy(x0)) / 4
|
||||||
|
const c2x = p1x - dx / 4
|
||||||
|
const c2y = p1y - (dx * dy(xe)) / 4
|
||||||
|
|
||||||
|
const seg = cubics[i]
|
||||||
|
seg.control1X = c1x
|
||||||
|
seg.control1Y = c1y
|
||||||
|
seg.control2X = c2x
|
||||||
|
seg.control2Y = c2y
|
||||||
|
seg.x = p1x
|
||||||
|
seg.y = p1y
|
||||||
|
}
|
||||||
|
|
||||||
|
flatStart.x = 0
|
||||||
|
flatStart.y = midY
|
||||||
|
flatEnd.x = width
|
||||||
|
flatEnd.y = midY
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: playhead
|
||||||
|
width: 3.5
|
||||||
|
height: Math.max(root.lineWidth + 12, 16)
|
||||||
|
radius: width / 2
|
||||||
|
color: root.playheadColor
|
||||||
|
x: root.playX - width / 2
|
||||||
|
y: root.midY - height / 2
|
||||||
|
z: 3
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameAnimation {
|
||||||
|
running: root.visible && (root.isPlaying || root.currentAmp > 0)
|
||||||
|
onTriggered: {
|
||||||
|
if (root.isPlaying) {
|
||||||
|
root.phase += 0.03 * frameTime * 60
|
||||||
|
}
|
||||||
|
root.updateWave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
currentAmp = isPlaying ? amp : 0
|
||||||
|
buildWave()
|
||||||
|
}
|
||||||
|
onWidthChanged: buildWave()
|
||||||
|
onSamplesChanged: buildWave()
|
||||||
|
onCurrentAmpChanged: updateWave()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user