added two new themes, reworked Crypto.qml and Stats.qml, standardized the buttons for the modules, added a new Sidebar widget which contains new 3 modules
This commit is contained in:
355
widgets/sidebar/mpris/MprisWidget.qml
Normal file
355
widgets/sidebar/mpris/MprisWidget.qml
Normal file
@@ -0,0 +1,355 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Mpris
|
||||
import "../../../config" as Cfg
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitHeight: contentCol.implicitHeight
|
||||
|
||||
readonly property var t: Cfg.Config.theme
|
||||
|
||||
property var activePlayer: null
|
||||
|
||||
readonly property bool canChangeVolume: root.activePlayer && root.activePlayer.volumeSupported && root.activePlayer.canControl
|
||||
|
||||
readonly property var _identityMap: ({
|
||||
"firefox": "Firefox",
|
||||
"spotify": "Spotify",
|
||||
"chromium": "Chromium",
|
||||
"google-chrome": "Chrome",
|
||||
"brave": "Brave",
|
||||
"vlc": "VLC",
|
||||
"mpv": "mpv"
|
||||
})
|
||||
|
||||
function selectPlayer() {
|
||||
var players = Mpris.players.values
|
||||
var fallback = null
|
||||
for (var i = 0; i < players.length; i++) {
|
||||
var p = players[i]
|
||||
if (!p || (p.trackTitle === "" && p.identity === "")) continue
|
||||
if (p.playbackState === MprisPlaybackState.Playing) { activePlayer = p; return }
|
||||
if (!fallback) fallback = p
|
||||
}
|
||||
if (activePlayer && activePlayer.playbackState !== MprisPlaybackState.Stopped) return
|
||||
activePlayer = fallback
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
model: Mpris.players
|
||||
Connections {
|
||||
required property MprisPlayer modelData
|
||||
target: modelData
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.activePlayer == null || modelData.playbackState === MprisPlaybackState.Playing)
|
||||
root.selectPlayer()
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (root.activePlayer === modelData) {
|
||||
root.activePlayer = null
|
||||
root.selectPlayer()
|
||||
}
|
||||
}
|
||||
|
||||
function onPlaybackStateChanged() {
|
||||
root.selectPlayer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function formatDuration(s) {
|
||||
if (isNaN(s) || s < 0) return "0:00"
|
||||
var m = Math.floor(s / 60)
|
||||
var sec = Math.floor(s % 60)
|
||||
return m + ":" + (sec < 10 ? "0" : "") + sec
|
||||
}
|
||||
|
||||
function formatIdentity(name) {
|
||||
if (!name) return "Media"
|
||||
var lower = name.toLowerCase()
|
||||
for (var key in root._identityMap) {
|
||||
if (lower.indexOf(key) !== -1) return root._identityMap[key]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
function playbackStatusText() {
|
||||
if (!root.activePlayer) return "No players"
|
||||
switch (root.activePlayer.playbackState) {
|
||||
case MprisPlaybackState.Playing: return "Now playing"
|
||||
case MprisPlaybackState.Paused: return "Paused"
|
||||
default: return "Stopped"
|
||||
}
|
||||
}
|
||||
|
||||
function volumeIcon() {
|
||||
if (!root.activePlayer) return ""
|
||||
var v = root.activePlayer.volume
|
||||
if (v === 0) return ""
|
||||
if (v < 0.33) return ""
|
||||
if (v < 0.66) return ""
|
||||
return ""
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: positionTimer
|
||||
running: root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing
|
||||
interval: 1000
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (root.activePlayer) root.activePlayer.positionChanged()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentCol
|
||||
anchors { left: parent.left; right: parent.right; top: parent.top }
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 32
|
||||
color: "transparent"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
Rectangle {
|
||||
width: 32; height: 32
|
||||
radius: 8
|
||||
color: Qt.alpha(root.t.mprisAccent, 0.12)
|
||||
border.color: Qt.alpha(root.t.mprisAccent, 0.22)
|
||||
border.width: 1
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: ""
|
||||
color: root.t.mprisAccent
|
||||
font.pixelSize: 18
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Text {
|
||||
text: root.activePlayer ? root.formatIdentity(root.activePlayer.identity) : "No media"
|
||||
color: root.t.mprisTextMain
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.DemiBold
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Text {
|
||||
text: root.playbackStatusText()
|
||||
color: root.t.mprisTextDim
|
||||
font.pixelSize: 10
|
||||
opacity: 0.75
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 12 }
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.0; color: Qt.alpha(root.t.mprisAccent, 0.5) }
|
||||
GradientStop { position: 1.0; color: "transparent" }
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: root.activePlayer ? 10 : 0 }
|
||||
|
||||
ColumnLayout {
|
||||
id: trackArea
|
||||
Layout.fillWidth: true
|
||||
visible: root.activePlayer !== null
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Rectangle {
|
||||
width: 64; height: 64
|
||||
radius: 8
|
||||
color: root.t.mprisAlbumBg
|
||||
border.color: root.t.mprisAlbumBorder
|
||||
border.width: 1
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
Image {
|
||||
id: albumArt
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
source: root.activePlayer ? root.activePlayer.trackArtUrl : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
visible: root.activePlayer && root.activePlayer.trackArtUrl !== "" && status === Image.Ready
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: ""
|
||||
color: root.t.mprisAccent
|
||||
font.pixelSize: 28
|
||||
visible: root.activePlayer && (root.activePlayer.trackArtUrl === "" || !albumArt.visible)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
text: root.activePlayer ? (root.activePlayer.trackTitle || "Unknown Title") : ""
|
||||
color: root.t.mprisTextMain
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.activePlayer ? (root.activePlayer.trackArtist || "Unknown Artist") : ""
|
||||
color: root.t.mprisTextDim
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.activePlayer ? (root.activePlayer.trackAlbum || "") : ""
|
||||
color: root.t.mprisTextDim
|
||||
font.pixelSize: 10
|
||||
opacity: 0.7
|
||||
elide: Text.ElideRight
|
||||
visible: root.activePlayer && root.activePlayer.trackAlbum !== ""
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 12 }
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
visible: root.activePlayer && root.activePlayer.positionSupported
|
||||
|
||||
MediaSlider {
|
||||
Layout.fillWidth: true
|
||||
interactive: root.activePlayer && root.activePlayer.canSeek
|
||||
fraction: {
|
||||
var p = root.activePlayer
|
||||
if (!p) return 0
|
||||
var len = p.length
|
||||
if (len > 0) return Math.max(0, Math.min(1, p.position / len))
|
||||
return 0
|
||||
}
|
||||
onSeeked: frac => {
|
||||
var p = root.activePlayer
|
||||
if (p && p.canSeek && p.length > 0) {
|
||||
p.position = frac * p.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
text: root.activePlayer ? root.formatDuration(root.activePlayer.position) : "0:00"
|
||||
color: root.t.mprisTextDim
|
||||
font.pixelSize: 9
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Text {
|
||||
text: root.activePlayer ? root.formatDuration(root.activePlayer.length) : "0:00"
|
||||
color: root.t.mprisTextDim
|
||||
font.pixelSize: 9
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 12 }
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
visible: root.canChangeVolume
|
||||
|
||||
Text {
|
||||
text: root.volumeIcon()
|
||||
color: root.activePlayer && root.activePlayer.volume > 0 ? root.t.mprisAccent : root.t.mprisTextDim
|
||||
font.pixelSize: 16
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
MediaSlider {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
interactive: root.canChangeVolume
|
||||
fraction: {
|
||||
var p = root.activePlayer
|
||||
if (!p) return 1
|
||||
return p.volume
|
||||
}
|
||||
onSeeked: frac => {
|
||||
var p = root.activePlayer
|
||||
if (p && root.canChangeVolume) {
|
||||
p.volume = frac
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: root.canChangeVolume ? 12 : 0 }
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 16
|
||||
|
||||
MediaButton {
|
||||
icon: ""
|
||||
enabled: root.activePlayer && root.activePlayer.canGoPrevious
|
||||
onClicked: root.activePlayer.previous()
|
||||
}
|
||||
|
||||
MediaButton {
|
||||
primary: true
|
||||
icon: root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing ? "" : ""
|
||||
enabled: root.activePlayer && root.activePlayer.canTogglePlaying
|
||||
onClicked: root.activePlayer.togglePlaying()
|
||||
}
|
||||
|
||||
MediaButton {
|
||||
icon: ""
|
||||
enabled: root.activePlayer && root.activePlayer.canGoNext
|
||||
onClicked: root.activePlayer.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 6 }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user