mirror of
https://github.com/tomasriveral/nixos.git
synced 2026-08-12 02:28:37 +02:00
ported some quickshell stuff from rivendell-hyprdots
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQml.Models
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Mpris
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
property MprisPlayer trackedPlayer: null
|
||||
property MprisPlayer activePlayer: trackedPlayer ?? Mpris.players.values[0] ?? null
|
||||
signal trackChanged(reverse: bool)
|
||||
|
||||
property bool __reverse: false
|
||||
|
||||
property var activeTrack
|
||||
|
||||
Instantiator {
|
||||
model: Mpris.players
|
||||
|
||||
Connections {
|
||||
required property MprisPlayer modelData
|
||||
target: modelData
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.trackedPlayer == null || modelData.isPlaying) {
|
||||
root.trackedPlayer = modelData;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (root.trackedPlayer == null || !root.trackedPlayer.isPlaying) {
|
||||
for (const player of Mpris.players.values) {
|
||||
if (player.playbackState.isPlaying) {
|
||||
root.trackedPlayer = player;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (trackedPlayer == null && Mpris.players.values.length != 0) {
|
||||
trackedPlayer = Mpris.players.values[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onPlaybackStateChanged() {
|
||||
if (root.trackedPlayer !== modelData)
|
||||
root.trackedPlayer = modelData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: activePlayer
|
||||
|
||||
function onPostTrackChanged() {
|
||||
root.updateTrack();
|
||||
}
|
||||
|
||||
function onTrackArtUrlChanged() {
|
||||
//root.updateTrack();
|
||||
if (root.activePlayer.uniqueId == root.activeTrack.uniqueId && root.activePlayer.trackArtUrl != root.activeTrack.artUrl) {
|
||||
// cantata likes to send cover updates *BEFORE* updating the track info.
|
||||
// as such, art url changes shouldn't be able to break the reverse animation
|
||||
const r = root.__reverse;
|
||||
root.updateTrack();
|
||||
root.__reverse = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onActivePlayerChanged: this.updateTrack()
|
||||
|
||||
function updateTrack() {
|
||||
//console.log(`update: ${this.activePlayer?.trackTitle ?? ""} : ${this.activePlayer?.trackArtists}`)
|
||||
this.activeTrack = {
|
||||
uniqueId: this.activePlayer?.uniqueId ?? 0,
|
||||
artUrl: this.activePlayer?.trackArtUrl ?? "",
|
||||
title: this.activePlayer?.trackTitle || "Unknown Title",
|
||||
artist: this.activePlayer?.trackArtist || "Unknown Artist",
|
||||
album: this.activePlayer?.trackAlbum || "Unknown Album"
|
||||
};
|
||||
|
||||
this.trackChanged(__reverse);
|
||||
this.__reverse = false;
|
||||
}
|
||||
|
||||
property bool isPlaying: this.activePlayer && this.activePlayer.isPlaying
|
||||
property bool canTogglePlaying: this.activePlayer?.canTogglePlaying ?? false
|
||||
function togglePlaying() {
|
||||
if (this.canTogglePlaying)
|
||||
this.activePlayer.togglePlaying();
|
||||
}
|
||||
|
||||
property bool canGoPrevious: this.activePlayer?.canGoPrevious ?? false
|
||||
function previous() {
|
||||
if (this.canGoPrevious) {
|
||||
this.__reverse = true;
|
||||
this.activePlayer.previous();
|
||||
}
|
||||
}
|
||||
|
||||
property bool canGoNext: this.activePlayer?.canGoNext ?? false
|
||||
function next() {
|
||||
if (this.canGoNext) {
|
||||
this.__reverse = false;
|
||||
this.activePlayer.next();
|
||||
}
|
||||
}
|
||||
|
||||
property bool canChangeVolume: this.activePlayer && this.activePlayer.volumeSupported && this.activePlayer.canControl
|
||||
|
||||
property bool loopSupported: this.activePlayer && this.activePlayer.loopSupported && this.activePlayer.canControl
|
||||
property var loopState: this.activePlayer?.loopState ?? MprisLoopState.None
|
||||
function setLoopState(loopState: var) {
|
||||
if (this.loopSupported) {
|
||||
this.activePlayer.loopState = loopState;
|
||||
}
|
||||
}
|
||||
|
||||
property bool shuffleSupported: this.activePlayer && this.activePlayer.shuffleSupported && this.activePlayer.canControl
|
||||
property bool hasShuffle: this.activePlayer?.shuffle ?? false
|
||||
function setShuffle(shuffle: bool) {
|
||||
if (this.shuffleSupported) {
|
||||
this.activePlayer.shuffle = shuffle;
|
||||
}
|
||||
}
|
||||
|
||||
function setActivePlayer(player: MprisPlayer) {
|
||||
const targetPlayer = player ?? Mpris.players[0];
|
||||
console.log(`setactive: ${targetPlayer} from ${activePlayer}`);
|
||||
|
||||
if (targetPlayer && this.activePlayer) {
|
||||
this.__reverse = Mpris.players.indexOf(targetPlayer) < Mpris.players.indexOf(this.activePlayer);
|
||||
} else {
|
||||
// always animate forward if going to null
|
||||
this.__reverse = false;
|
||||
}
|
||||
|
||||
this.trackedPlayer = targetPlayer;
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "mpris"
|
||||
|
||||
function pauseAll(): void {
|
||||
for (const player of Mpris.players.values) {
|
||||
if (player.canPause)
|
||||
player.pause();
|
||||
}
|
||||
}
|
||||
|
||||
function playPause(): void {
|
||||
root.togglePlaying();
|
||||
}
|
||||
function previous(): void {
|
||||
root.previous();
|
||||
}
|
||||
function next(): void {
|
||||
root.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.Mpris
|
||||
import "../.."
|
||||
import ".."
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
required property var window
|
||||
required property var bar
|
||||
|
||||
readonly property var activePlayer: MprisController.activePlayer
|
||||
|
||||
property var isPopupOpen: false
|
||||
|
||||
implicitWidth: column.implicitWidth + 10
|
||||
Layout.fillHeight: true
|
||||
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: isPopupOpen = !isPopupOpen
|
||||
|
||||
Item {
|
||||
id: widget
|
||||
anchors.fill: parent
|
||||
|
||||
scale: root.isPopupOpen ? 0.92 : 1
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 70
|
||||
}
|
||||
}
|
||||
|
||||
implicitHeight: column.implicitHeight + 10
|
||||
|
||||
RowLayout {
|
||||
id: column
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
}
|
||||
|
||||
ClickableIcon {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
image: "../../assets/fastforward.png"
|
||||
mirror: true
|
||||
hoverEnabled: true
|
||||
enabled: MprisController.canGoPrevious
|
||||
onClicked: MprisController.previous()
|
||||
scale: containsMouse ? 0.9 : 1
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 70
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClickableIcon {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
implicitWidth: 26
|
||||
implicitHeight: 26
|
||||
Layout.leftMargin: -4
|
||||
Layout.rightMargin: -4
|
||||
image: `../../assets/${MprisController.isPlaying ? "pause" : "play"}.png`
|
||||
hoverEnabled: true
|
||||
enabled: MprisController.canTogglePlaying
|
||||
onClicked: MprisController.togglePlaying()
|
||||
}
|
||||
|
||||
ClickableIcon {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
image: "../../assets/fastforward.png"
|
||||
hoverEnabled: true
|
||||
enabled: MprisController.canGoNext
|
||||
onClicked: MprisController.next()
|
||||
scale: containsMouse ? 0.9 : 1
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 70
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onIsPopupOpenChanged() {
|
||||
root.window.focusable = root.isPopupOpen;
|
||||
}
|
||||
}
|
||||
LazyLoader {
|
||||
id: popupLoader
|
||||
active: root.isPopupOpen
|
||||
// Leave time for exit anim
|
||||
Behavior on active {
|
||||
enabled: popupLoader.active
|
||||
NumberAnimation {
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
|
||||
PlayersPopup {
|
||||
id: popup
|
||||
visible: true
|
||||
isOpen: root.isPopupOpen
|
||||
|
||||
anchor.window: root.window
|
||||
anchor.rect.x: root.window.width
|
||||
anchor.rect.y: 0
|
||||
|
||||
HyprlandFocusGrab {
|
||||
active: root.isPopupOpen
|
||||
windows: [popup]
|
||||
}
|
||||
contentItem {
|
||||
focus: true
|
||||
Keys.onEscapePressed: root.isPopupOpen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component CenteredText: Item {
|
||||
Layout.fillWidth: true
|
||||
|
||||
property alias text: label.text
|
||||
property alias font: label.font
|
||||
|
||||
Label {
|
||||
id: label
|
||||
visible: text != ""
|
||||
anchors.centerIn: parent
|
||||
elide: Text.ElideRight
|
||||
width: Math.min(parent.width - 20, implicitWidth)
|
||||
font.family: "BigBlueTermPlusNerdFont"
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.Mpris
|
||||
import "../.."
|
||||
import "../../components"
|
||||
import ".."
|
||||
|
||||
PopupWindow {
|
||||
id: root
|
||||
|
||||
property bool isOpen: false
|
||||
|
||||
implicitWidth: 700
|
||||
//implicitHeight: popupContent.implicitHeight + popupContent.anchors.margins * 2
|
||||
implicitHeight: 800
|
||||
|
||||
mask: Region {
|
||||
item: wrapper
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
|
||||
property Scope positionInfo: Scope {
|
||||
id: positionInfo
|
||||
|
||||
property int position: Math.floor(MprisController.activePlayer.position)
|
||||
property int length: Math.floor(MprisController.activePlayer.length)
|
||||
|
||||
FrameAnimation {
|
||||
id: posTracker
|
||||
running: MprisController.activePlayer.isPlaying
|
||||
onTriggered: MprisController.activePlayer.positionChanged()
|
||||
}
|
||||
|
||||
function timeStr(time: int): string {
|
||||
const seconds = time % 60;
|
||||
const minutes = Math.floor(time / 60);
|
||||
|
||||
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||
}
|
||||
}
|
||||
|
||||
Rope {
|
||||
segmentCount: 4
|
||||
segmentLen: 22
|
||||
start: Qt.vector2d(250, -5)
|
||||
end: Qt.vector2d(wrapper.x + 49, wrapper.y)
|
||||
}
|
||||
Rope {
|
||||
segmentCount: 4
|
||||
segmentLen: 22
|
||||
start: Qt.vector2d(wrapper.width + 200 - 50, -5)
|
||||
end: Qt.vector2d(wrapper.width + wrapper.x - 49, wrapper.y)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: wrapper
|
||||
implicitWidth: 500
|
||||
implicitHeight: popupContent.implicitHeight + popupContent.anchors.margins * 2
|
||||
color: "#824524"
|
||||
|
||||
x: 200
|
||||
y: -wrapper.height
|
||||
readonly property var targetX: 200
|
||||
readonly property var targetY: root.isOpen ? 70 : -wrapper.height - 50
|
||||
property var velocityX: 0
|
||||
property var velocityY: 0
|
||||
|
||||
FrameAnimation {
|
||||
running: true
|
||||
function dampingVelocity(currentVelocity, delta) {
|
||||
const spring = 8.0;
|
||||
const damping = 0.3;
|
||||
const springForce = spring * delta;
|
||||
const dampingForce = -damping * currentVelocity;
|
||||
return currentVelocity + (springForce + dampingForce);
|
||||
}
|
||||
onTriggered: {
|
||||
const deltaX = wrapper.targetX - wrapper.x;
|
||||
const deltaY = wrapper.targetY - wrapper.y;
|
||||
if (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1) {
|
||||
wrapper.velocityX = dampingVelocity(wrapper.velocityX, deltaX);
|
||||
wrapper.velocityY = dampingVelocity(wrapper.velocityY, deltaY);
|
||||
wrapper.x += wrapper.velocityX * frameTime;
|
||||
wrapper.y += wrapper.velocityY * frameTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
property var prevMouseX: 0
|
||||
property var prevMouseY: 0
|
||||
anchors.fill: parent
|
||||
onPressed: e => {
|
||||
prevMouseX = e.x;
|
||||
prevMouseY = e.y;
|
||||
}
|
||||
onPositionChanged: e => {
|
||||
wrapper.x += (e.x - prevMouseX) * 0.3;
|
||||
wrapper.y += (e.y - prevMouseY) * 0.3;
|
||||
prevMouseX = e.x;
|
||||
prevMouseY = e.y;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
clip: true
|
||||
|
||||
Image {
|
||||
scale: 10
|
||||
anchors.fill: parent
|
||||
source: "../../assets/wood.png"
|
||||
smooth: false
|
||||
fillMode: Image.Tile
|
||||
opacity: 0.4
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: popupContent
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
|
||||
Connections {
|
||||
target: MprisController
|
||||
|
||||
function onTrackChanged(reverse: bool) {
|
||||
trackStack.updateTrack(reverse, false);
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: playerSelectorContainment
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: playerSelector.implicitHeight
|
||||
implicitWidth: playerSelector.implicitWidth
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: 50
|
||||
implicitHeight: 50
|
||||
color: "#824524"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: playerSelector
|
||||
property Item selectedPlayerDisplay: Mpris.players[0]
|
||||
x: parent.width / 2 - (selectedPlayerDisplay ? selectedPlayerDisplay.x + selectedPlayerDisplay.width / 2 : 0)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 400
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Mpris.players
|
||||
|
||||
MouseArea {
|
||||
required property MprisPlayer modelData
|
||||
readonly property bool selected: modelData == MprisController.activePlayer
|
||||
onSelectedChanged: () => {
|
||||
if (selected) {
|
||||
playerSelector.selectedPlayerDisplay = this;
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: childrenRect.width
|
||||
implicitHeight: childrenRect.height
|
||||
|
||||
onClicked: MprisController.setActivePlayer(modelData)
|
||||
|
||||
Item {
|
||||
width: 50
|
||||
height: 50
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
source: {
|
||||
// So janky xD
|
||||
const identity = modelData.identity.toLowerCase();
|
||||
|
||||
if (identity.includes("chrome") || identity.includes("chromium")) {
|
||||
return Quickshell.iconPath("google-chrome") || Quickshell.iconPath("chromium");
|
||||
}
|
||||
if (identity.includes("firefox")) {
|
||||
return Quickshell.iconPath("firefox");
|
||||
}
|
||||
if (identity.includes("spotify")) {
|
||||
return Quickshell.iconPath("spotify");
|
||||
}
|
||||
if (modelData.identity == "Brave") {
|
||||
return "/opt/brave-bin/product_logo_64.png";
|
||||
}
|
||||
const entry = DesktopEntries.byId(modelData.desktopEntry);
|
||||
if (entry && entry.icon) {
|
||||
return Quickshell.iconPath(entry.icon);
|
||||
}
|
||||
|
||||
return "../../assets/mpd.png";
|
||||
}
|
||||
smooth: false
|
||||
sourceSize.width: 50
|
||||
sourceSize.height: 50
|
||||
cache: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: 16
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: MprisController.activePlayer.identity
|
||||
font.family: "BigBlueTermPlusNerdFont"
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
|
||||
SlideView {
|
||||
id: trackStack
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 100
|
||||
clip: animating || (lastFlicked?.contentX ?? 0) != 0
|
||||
|
||||
property Flickable lastFlicked
|
||||
property bool reverse: false
|
||||
|
||||
Component.onCompleted: updateTrack(false, true)
|
||||
|
||||
function updateTrack(reverse: bool, immediate: bool) {
|
||||
this.reverse = reverse;
|
||||
this.replace(trackComponent, {
|
||||
track: MprisController.activeTrack
|
||||
}, immediate);
|
||||
}
|
||||
|
||||
property var trackComponent: Component {
|
||||
Flickable {
|
||||
id: flickable
|
||||
required property var track
|
||||
readonly property bool svReady: img.status === Image.Ready
|
||||
contentWidth: width + 1
|
||||
onDragStarted: trackStack.lastFlicked = this
|
||||
onDragEnded: {
|
||||
if (Math.abs(contentX) > 75) {
|
||||
if (contentX < 0)
|
||||
MprisController.previous();
|
||||
else if (contentX > 0)
|
||||
MprisController.next();
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 8
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
implicitHeight: 128
|
||||
implicitWidth: 128
|
||||
color: "transparent"
|
||||
|
||||
Image {
|
||||
id: img
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
visible: flickable.track.artUrl != ""
|
||||
source: flickable.track.artUrl
|
||||
cache: false
|
||||
asynchronous: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Label {
|
||||
text: flickable.track.title
|
||||
font.pointSize: albumLabel.font.pointSize + 1
|
||||
font.family: "BigBlueTermPlusNerdFont"
|
||||
color: "white"
|
||||
elide: Text.ElideRight
|
||||
Layout.maximumWidth: Math.min(300, implicitWidth)
|
||||
}
|
||||
|
||||
Label {
|
||||
id: albumLabel
|
||||
text: flickable.track.album
|
||||
opacity: 0.8
|
||||
font.family: "BigBlueTermPlusNerdFont"
|
||||
color: "white"
|
||||
Layout.maximumWidth: Math.min(300, implicitWidth)
|
||||
}
|
||||
|
||||
Label {
|
||||
text: flickable.track.artist
|
||||
opacity: 0.8
|
||||
font.family: "BigBlueTermPlusNerdFont"
|
||||
color: "white"
|
||||
Layout.maximumWidth: Math.min(300, implicitWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real fromPos: trackStack.width * (trackStack.reverse ? -1 : 1)
|
||||
|
||||
enterTransition: PropertyAnimation {
|
||||
property: "x"
|
||||
from: trackStack.fromPos
|
||||
to: 0
|
||||
duration: 350
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
exitTransition: PropertyAnimation {
|
||||
property: "x"
|
||||
to: target.x - trackStack.fromPos
|
||||
duration: 350
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: controlsRow.implicitHeight
|
||||
|
||||
RowLayout {
|
||||
id: controlsRow
|
||||
anchors.centerIn: parent
|
||||
|
||||
ClickableIcon {
|
||||
Layout.leftMargin: 2
|
||||
Layout.rightMargin: 2
|
||||
implicitWidth: 24
|
||||
implicitHeight: 24
|
||||
enabled: MprisController.loopSupported
|
||||
image: {
|
||||
switch (MprisController.loopState) {
|
||||
case MprisLoopState.None:
|
||||
return "../../assets/repeat-off.png";
|
||||
case MprisLoopState.Playlist:
|
||||
return "../../assets/repeat.png";
|
||||
case MprisLoopState.Track:
|
||||
return "../../assets/repeat-once.png";
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
let target = MprisLoopState.None;
|
||||
switch (MprisController.loopState) {
|
||||
case MprisLoopState.None:
|
||||
target = MprisLoopState.Playlist;
|
||||
break;
|
||||
case MprisLoopState.Playlist:
|
||||
target = MprisLoopState.Track;
|
||||
break;
|
||||
case MprisLoopState.Track:
|
||||
target = MprisLoopState.None;
|
||||
break;
|
||||
}
|
||||
MprisController.setLoopState(target);
|
||||
}
|
||||
}
|
||||
|
||||
ClickableIcon {
|
||||
Layout.leftMargin: 2
|
||||
Layout.rightMargin: 2
|
||||
implicitWidth: 32
|
||||
implicitHeight: 32
|
||||
enabled: MprisController.canGoPrevious
|
||||
image: "../../assets/fastforward.png"
|
||||
mirror: true
|
||||
onClicked: MprisController.previous()
|
||||
}
|
||||
|
||||
ClickableIcon {
|
||||
implicitWidth: 42
|
||||
implicitHeight: 42
|
||||
enabled: MprisController.canTogglePlaying
|
||||
image: `../../assets/${MprisController.isPlaying ? "pause" : "play"}.png`
|
||||
onClicked: MprisController.togglePlaying()
|
||||
}
|
||||
|
||||
ClickableIcon {
|
||||
Layout.leftMargin: 2
|
||||
Layout.rightMargin: 2
|
||||
implicitWidth: 32
|
||||
implicitHeight: 32
|
||||
enabled: MprisController.canGoNext
|
||||
image: "../../assets/fastforward.png"
|
||||
onClicked: MprisController.next()
|
||||
}
|
||||
|
||||
ClickableIcon {
|
||||
Layout.leftMargin: 2
|
||||
Layout.rightMargin: 2
|
||||
implicitWidth: 24
|
||||
implicitHeight: 24
|
||||
enabled: MprisController.shuffleSupported
|
||||
image: `../../assets/${MprisController.hasShuffle ? "shuffle" : "noshuffle"}.png`
|
||||
onClicked: MprisController.setShuffle(!MprisController.hasShuffle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.margins: 5
|
||||
|
||||
Label {
|
||||
Layout.preferredWidth: lengthLabel.implicitWidth
|
||||
text: positionInfo.timeStr(positionInfo.position)
|
||||
font.family: "BigBlueTermPlusNerdFont"
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: slider
|
||||
|
||||
property bool bindSlider: true
|
||||
|
||||
property real boundAnimStart: 0
|
||||
property real boundAnimFactor: 1
|
||||
property real lastPosition: 0
|
||||
property real lastLength: 0
|
||||
property real boundPosition: {
|
||||
const ppos = MprisController.activePlayer.position / MprisController.activePlayer.length;
|
||||
const bpos = boundAnimStart;
|
||||
return (ppos * boundAnimFactor) + (bpos * (1.0 - boundAnimFactor));
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
enabled: MprisController.activePlayer.canSeek
|
||||
from: 0
|
||||
to: 1
|
||||
|
||||
background: Rectangle {
|
||||
x: slider.leftPadding
|
||||
y: slider.topPadding + slider.availableHeight / 2 - height / 2
|
||||
implicitHeight: 24
|
||||
width: slider.availableWidth
|
||||
height: implicitHeight
|
||||
color: "#824524"
|
||||
|
||||
Rectangle {
|
||||
anchors.margins: 8
|
||||
x: anchors.leftMargin
|
||||
y: anchors.topMargin
|
||||
width: slider.visualPosition * (parent.width - anchors.leftMargin - anchors.rightMargin)
|
||||
height: parent.height - anchors.topMargin - anchors.bottomMargin
|
||||
color: "#8D804B"
|
||||
}
|
||||
}
|
||||
|
||||
handle: Rectangle {
|
||||
x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width)
|
||||
y: slider.topPadding + slider.availableHeight / 2 - height / 2
|
||||
implicitWidth: 16
|
||||
implicitHeight: 16
|
||||
rotation: 45
|
||||
color: "#8D804B"
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: MprisController.activePlayer
|
||||
|
||||
function onPositionChanged() {
|
||||
slider.lastPosition = MprisController.activePlayer.position;
|
||||
slider.lastLength = MprisController.activePlayer.length;
|
||||
}
|
||||
}
|
||||
|
||||
onPressedChanged: () => {
|
||||
if (!pressed)
|
||||
MprisController.activePlayer.position = value * MprisController.activePlayer.length;
|
||||
bindSlider = !pressed;
|
||||
}
|
||||
|
||||
Binding {
|
||||
when: slider.bindSlider
|
||||
slider.value: slider.boundPosition
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: lengthLabel
|
||||
text: positionInfo.timeStr(positionInfo.length)
|
||||
font.family: "BigBlueTermPlusNerdFont"
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Image {
|
||||
source: "../../assets/rope-tie.png"
|
||||
smooth: false
|
||||
width: 32
|
||||
height: 32
|
||||
x: wrapper.x + 49 - width / 2
|
||||
y: wrapper.y - height / 2
|
||||
}
|
||||
Image {
|
||||
source: "../../assets/rope-tie.png"
|
||||
smooth: false
|
||||
width: 32
|
||||
height: 32
|
||||
x: wrapper.width + wrapper.x - 49 - width / 2
|
||||
y: wrapper.y - height / 2
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user