Warning, /multimedia/kasts/src/qml/Desktop/DesktopPlayerControls.qml is written in an unsupported language. File is not indexed.

0001 /**
0002  * SPDX-FileCopyrightText: 2023 Bart De Vries <bart@mogwai.be>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 import QtQuick
0008 import QtQuick.Controls as Controls
0009 import QtQuick.Layouts
0010 import QtQml.Models
0011 
0012 import org.kde.kirigami as Kirigami
0013 import org.kde.kmediasession
0014 
0015 import org.kde.kasts
0016 import org.kde.kasts.settings
0017 
0018 import ".."
0019 
0020 FocusScope {
0021     id: desktopPlayerControls
0022     implicitHeight: playerControlToolBar.implicitHeight + Kirigami.Units.largeSpacing * 2
0023 
0024     property alias chapterModel: chapterModel
0025     /*
0026      * Emmited when User uses the Item as a handle to resize the layout.
0027      * y: difference to previous position
0028      * offset: cursor offset (y coordinate relative to this Item, where dragging
0029      * begun)
0030      */
0031     signal handlePositionChanged(int y, int offset)
0032 
0033     Rectangle {
0034         id: toolbarBackground
0035         anchors.fill: parent
0036 
0037         opacity: 0.7
0038 
0039         //set background color
0040         Kirigami.Theme.inherit: false
0041         Kirigami.Theme.colorSet: Kirigami.Theme.Header
0042         color: Kirigami.Theme.backgroundColor
0043 
0044         MouseArea {
0045           anchors.fill: parent
0046           property int dragStartOffset: 0
0047 
0048           cursorShape: Qt.SizeVerCursor
0049 
0050           onPressed: (mouse) => {
0051             dragStartOffset = mouse.y
0052           }
0053 
0054           onPositionChanged: (mouse) => {
0055             desktopPlayerControls.handlePositionChanged(mouse.y, dragStartOffset)
0056           }
0057 
0058           drag.axis: Drag.YAxis
0059           drag.threshold: 1
0060         }
0061     }
0062 
0063     RowLayout {
0064         id: playerControlToolBar
0065 
0066         anchors.fill: parent
0067         anchors.topMargin: Kirigami.Units.largeSpacing
0068         anchors.bottomMargin: Kirigami.Units.largeSpacing
0069         anchors.rightMargin: Kirigami.Units.smallSpacing
0070         anchors.leftMargin: Kirigami.Units.smallSpacing
0071 
0072         property int audioSliderNiceMinimumWidth: 300
0073         property int audioSliderAbsoluteMinimumWidth: 200
0074 
0075         // size of volumeButton serves as size of extra buttons too
0076         // this is chosen because the volumeButton is always visible
0077         property bool tooNarrowExtra: playerControlToolBar.width - (audioButtons.width + 4 * volumeButton.width + (chapterAction.visible ? chapterTextMetric.width : 0) + extraButtonsTextMetric.width) < audioSliderNiceMinimumWidth + 40
0078 
0079         property bool tooNarrowChapter: playerControlToolBar.width - (audioButtons.width + 4 * volumeButton.width + (chapterAction.visible ? chapterTextMetric.width : 0)) < audioSliderNiceMinimumWidth +  20
0080 
0081         property bool tooNarrowOverflow: playerControlToolBar.width - (audioButtons.width + 4 * volumeButton.width) < audioSliderNiceMinimumWidth
0082 
0083         property bool tooNarrowAudioLabels: playerControlToolBar.width - (audioButtons.width + 2 * volumeButton.width) < audioSliderAbsoluteMinimumWidth
0084 
0085         clip: true
0086 
0087         Loader {
0088             Layout.fillHeight: true
0089             Layout.preferredWidth: height
0090             active: headerBar.handlePosition === 0
0091             visible: active
0092             sourceComponent: imageComponent
0093         }
0094 
0095         Component {
0096             id: imageComponent
0097             ImageWithFallback {
0098                 id: frontImage
0099                 imageSource: headerMetaData.image
0100                 absoluteRadius: Kirigami.Units.smallSpacing
0101                 visible: headerBar.handlePosition === 0
0102                 MouseArea {
0103                     anchors.fill: parent
0104                     cursorShape: Qt.PointingHandCursor
0105                     onClicked: {
0106                         headerBar.openFullScreenImage();
0107                     }
0108                 }
0109             }
0110         }
0111 
0112         RowLayout {
0113             id: audioButtons
0114             Controls.ToolButton {
0115                 icon.name: "media-seek-backward"
0116                 onClicked: AudioManager.skipBackward()
0117                 enabled: AudioManager.canSkipBackward
0118 
0119                 Controls.ToolTip.visible: hovered
0120                 Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
0121                 Controls.ToolTip.text: i18n("Seek backward")
0122             }
0123             Controls.ToolButton {
0124                 id: playButton
0125                 icon.name: AudioManager.playbackState === KMediaSession.PlayingState ? "media-playback-pause" : "media-playback-start"
0126                 onClicked: AudioManager.playbackState === KMediaSession.PlayingState ? AudioManager.pause() : AudioManager.play()
0127                 enabled: AudioManager.canPlay
0128 
0129                 Controls.ToolTip.visible: hovered
0130                 Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
0131                 Controls.ToolTip.text: AudioManager.playbackState === KMediaSession.PlayingState ? i18n("Pause") : i18n("Play")
0132             }
0133             Controls.ToolButton {
0134                 icon.name: "media-seek-forward"
0135                 onClicked: AudioManager.skipForward()
0136                 enabled: AudioManager.canSkipForward
0137 
0138                 Controls.ToolTip.visible: hovered
0139                 Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
0140                 Controls.ToolTip.text: i18n("Seek forward")
0141             }
0142             Controls.ToolButton {
0143                 icon.name: "media-skip-forward"
0144                 onClicked: AudioManager.next()
0145                 enabled: AudioManager.canGoNext
0146 
0147                 Controls.ToolTip.visible: hovered
0148                 Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
0149                 Controls.ToolTip.text: i18n("Skip forward")
0150             }
0151             Controls.ToolButton {
0152                 id: playbackRateButton
0153                 text: AudioManager.playbackRate.toFixed(2) + "x"
0154                 checkable: true
0155                 checked: playbackRateMenu.visible
0156                 onClicked: {
0157                     if (playbackRateMenu.visible) {
0158                         playbackRateMenu.dismiss();
0159                     } else {
0160                         playbackRateMenu.popup(playbackRateButton, 0, playbackRateButton.height);
0161                     }
0162                 }
0163                 padding: 0
0164                 implicitWidth: playButton.width * 1.5
0165                 implicitHeight: playButton.height
0166 
0167                 Controls.ToolTip.visible: hovered
0168                 Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
0169                 Controls.ToolTip.text: i18n("Playback rate:") + " " + AudioManager.playbackRate.toFixed(2) + "x"
0170             }
0171         }
0172 
0173         Controls.Label {
0174             id: currentPositionLabel
0175             text: AudioManager.formattedPosition
0176             visible: !playerControlToolBar.tooNarrowAudioLabels
0177             Layout.alignment: Qt.AlignVCenter
0178         }
0179 
0180         ChapterSlider {
0181             id: durationSlider
0182             model: chapterModel
0183             enabled: AudioManager.entry && AudioManager.PlaybackState != AudioManager.StoppedState && AudioManager.canPlay
0184             Layout.fillWidth: true
0185             Layout.alignment: Qt.AlignVCenter
0186         }
0187 
0188         Item {
0189             id: durationLabel
0190             visible: !playerControlToolBar.tooNarrowAudioLabels
0191             Layout.preferredHeight: endLabel.implicitHeight
0192             Layout.preferredWidth: endLabel.implicitWidth
0193             Layout.rightMargin: Kirigami.Units.largeSpacing
0194             Layout.alignment: Qt.AlignVCenter
0195 
0196             Controls.Label {
0197                 id: endLabel
0198                 anchors.right: parent.right
0199                 anchors.verticalCenter: parent.verticalCenter
0200                 text: (SettingsManager.toggleRemainingTime) ?
0201                         "-" + AudioManager.formattedLeftDuration
0202                         : AudioManager.formattedDuration
0203             }
0204 
0205             MouseArea {
0206                 anchors.fill: parent
0207                 hoverEnabled: true
0208                 onClicked: {
0209                     SettingsManager.toggleRemainingTime = !SettingsManager.toggleRemainingTime;
0210                     SettingsManager.save();
0211                 }
0212             }
0213         }
0214 
0215         RowLayout {
0216             id: extraButtons
0217             visible: !playerControlToolBar.tooNarrowOverflow
0218             Controls.ToolButton {
0219                 id: chapterButton
0220                 action: chapterAction
0221                 display: playerControlToolBar.tooNarrowChapter ? Controls.AbstractButton.IconOnly : Controls.AbstractButton.TextBesideIcon
0222                 visible: chapterAction.visible && !playerControlToolBar.tooNarrowOverflow
0223 
0224                 Controls.ToolTip.visible: hovered
0225                 Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
0226                 Controls.ToolTip.text: i18nc("@action:button", "Show chapter list")
0227             }
0228 
0229             Controls.ToolButton {
0230                 id: infoButton
0231                 action: infoAction
0232                 display: playerControlToolBar.tooNarrowExtra ? Controls.AbstractButton.IconOnly : Controls.AbstractButton.TextBesideIcon
0233                 visible: infoAction.visible && !playerControlToolBar.tooNarrowOverflow
0234 
0235                 Controls.ToolTip.visible: hovered
0236                 Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
0237                 Controls.ToolTip.text: i18nc("@action:button", "Show episode info")
0238             }
0239 
0240             Controls.ToolButton {
0241                 id: sleepButton
0242                 action: sleepAction
0243                 display: playerControlToolBar.tooNarrowExtra ? Controls.AbstractButton.IconOnly : Controls.AbstractButton.TextBesideIcon
0244                 visible:  !playerControlToolBar.tooNarrowOverflow
0245 
0246                 Controls.ToolTip.visible: hovered
0247                 Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
0248                 Controls.ToolTip.text: i18nc("@action:button", "Open sleep timer settings")
0249             }
0250         }
0251 
0252         RowLayout {
0253             id: volumeControls
0254             Controls.ToolButton {
0255                 id: volumeButton
0256                 icon.name: AudioManager.muted ? "player-volume-muted" : "player-volume"
0257                 enabled: AudioManager.PlaybackState != AudioManager.StoppedState && AudioManager.canPlay
0258                 checked: volumePopup.visible
0259 
0260                 Controls.ToolTip.visible: hovered
0261                 Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
0262                 Controls.ToolTip.text: i18nc("@action:button", "Open volume settings")
0263 
0264                 onClicked: {
0265                     if (volumePopup.visible) {
0266                         volumePopup.close();
0267                     } else {
0268                         volumePopup.open();
0269                     }
0270                 }
0271 
0272                 Controls.Popup {
0273                     id: volumePopup
0274                     x: -padding
0275                     y: volumeButton.height
0276 
0277                     focus: true
0278                     padding: Kirigami.Units.smallSpacing
0279                     contentWidth: volumeButtonVertical.implicitWidth
0280 
0281                     contentItem: ColumnLayout {
0282                         VolumeSlider {
0283                             id: volumeSlider
0284                         }
0285 
0286                         Controls.ToolButton {
0287                             id: volumeButtonVertical
0288                             enabled: AudioManager.PlaybackState != AudioManager.StoppedState && AudioManager.canPlay
0289                             icon.name: AudioManager.muted ? "player-volume-muted" : "player-volume"
0290                             onClicked: AudioManager.muted = !AudioManager.muted
0291 
0292                             Controls.ToolTip.visible: hovered
0293                             Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
0294                             Controls.ToolTip.text: i18nc("@action:button", "Toggle mute")
0295 
0296                         }
0297                     }
0298                 }
0299             }
0300         }
0301 
0302         Controls.ToolButton {
0303             id: overflowButton
0304             icon.name: "overflow-menu"
0305             display: Controls.AbstractButton.IconOnly
0306             visible: playerControlToolBar.tooNarrowOverflow
0307             checked: overflowMenu.visible
0308 
0309             Controls.ToolTip.visible: hovered
0310             Controls.ToolTip.delay: Kirigami.Units.toolTipDelay
0311             Controls.ToolTip.text: i18nc("@action:button", "Show more")
0312 
0313             onClicked: {
0314                 if (overflowMenu.visible) {
0315                     overflowMenu.dismiss();
0316                 } else {
0317                     overflowMenu.popup(0, overflowButton.height)
0318                 }
0319             }
0320 
0321             Controls.Menu {
0322                 id: overflowMenu
0323                 contentData: extraActions
0324                 onVisibleChanged: {
0325                     if (visible) {
0326                         for (var i in contentData) {
0327                             overflowMenu.contentData[i].visible = overflowMenu.contentData[i].action.visible;
0328                             overflowMenu.contentData[i].height =
0329                             (overflowMenu.contentData[i].action.visible) ? overflowMenu.contentData[i].implicitHeight : 0 // workaround for qqc2-breeze-style
0330                         }
0331                     }
0332                 }
0333             }
0334         }
0335     }
0336 
0337     // Actions which will be used to create buttons on toolbar or in overflow menu
0338     Kirigami.Action {
0339         id: chapterAction
0340         property bool visible: AudioManager.entry && chapterList.count !== 0
0341         text: i18nc("@action:button", "Chapters")
0342         icon.name: "view-media-playlist"
0343         onTriggered: chapterOverlay.open();
0344     }
0345 
0346     Kirigami.Action {
0347         id: infoAction
0348         property bool visible: AudioManager.entry
0349         text: i18nc("@action:button", "Show Info")
0350         icon.name: "documentinfo"
0351         onTriggered: entryDetailsOverlay.open();
0352     }
0353 
0354     Kirigami.Action {
0355         id: sleepAction
0356         checkable: true
0357         checked: AudioManager.remainingSleepTime > 0
0358         property bool visible: true
0359         text: i18nc("@action:button", "Sleep Timer")
0360         icon.name: "clock"
0361         onTriggered: {
0362             toggle(); // only set the on/off state based on sleep timer state
0363             sleepTimerDialog.open();
0364         }
0365     }
0366 
0367     property var extraActions: [ chapterAction,
0368                                  infoAction,
0369                                  sleepAction ]
0370 
0371     TextMetrics {
0372         id: chapterTextMetric
0373         text: chapterAction.text
0374     }
0375 
0376     TextMetrics {
0377         id: extraButtonsTextMetric
0378         text: infoAction.text + sleepAction.text
0379     }
0380 
0381     ChapterModel {
0382         id: chapterModel
0383         entry: AudioManager.entry ? AudioManager.entry : null
0384         duration: AudioManager.duration
0385     }
0386 
0387     Kirigami.Dialog {
0388         id: chapterOverlay
0389 
0390         showCloseButton: false
0391 
0392         title: i18n("Chapters")
0393 
0394         ListView {
0395             id: chapterList
0396 
0397             currentIndex: -1
0398 
0399             implicitWidth: Kirigami.Units.gridUnit * 30
0400             implicitHeight: Kirigami.Units.gridUnit * 25
0401 
0402             model: chapterModel
0403             delegate: ChapterListDelegate {
0404                 id: chapterDelegate
0405                 width: chapterList.width
0406                 entry: AudioManager.entry ? AudioManager.entry : null
0407                 overlay: chapterOverlay
0408             }
0409         }
0410     }
0411 
0412     Kirigami.Dialog {
0413         id: entryDetailsOverlay
0414         preferredWidth: Kirigami.Units.gridUnit * 30
0415 
0416         showCloseButton: false
0417 
0418         title: AudioManager.entry ? AudioManager.entry.title : i18n("No Track Title")
0419         padding: Kirigami.Units.largeSpacing
0420 
0421         Controls.Label {
0422             id: text
0423             text: AudioManager.entry ? AudioManager.entry.adjustedContent(width, font.pixelSize) : i18n("No track loaded")
0424             verticalAlignment: Text.AlignTop
0425             baseUrl: AudioManager.entry ? AudioManager.entry.baseUrl : ""
0426             textFormat: Text.RichText
0427             wrapMode: Text.WordWrap
0428             onLinkHovered: {
0429                 cursorShape: Qt.PointingHandCursor;
0430             }
0431             onLinkActivated: (link) => {
0432                 if (link.split("://")[0] === "timestamp") {
0433                     if (AudioManager.entry && AudioManager.entry.enclosure) {
0434                         AudioManager.seek(link.split("://")[1]);
0435                     }
0436                 } else {
0437                     Qt.openUrlExternally(link);
0438                 }
0439             }
0440         }
0441     }
0442 
0443     PlaybackRateMenu {
0444         id: playbackRateMenu
0445         parentButton: playbackRateButton
0446     }
0447 }