Warning, /plasma/plasma-workspace/applets/mediacontroller/package/contents/ui/AlbumArtStackView.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org> 0003 SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org> 0004 SPDX-FileCopyrightText: 2014 Kai Uwe Broulik <kde@privat.broulik.de> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 import QtQuick 2.15 0010 import QtQuick.Controls 2.15 0011 import QtQuick.Window 2.15 0012 0013 import org.kde.plasma.plasmoid 2.0 0014 import org.kde.plasma.components 3.0 as PC3 0015 import org.kde.plasma.extras 2.0 as PlasmaExtras 0016 import org.kde.plasma.private.mpris as Mpris 0017 import org.kde.kirigami 2.20 as Kirigami 0018 0019 Item { 0020 id: container 0021 0022 /** 0023 * Whether the album art image is available or in loading status 0024 */ 0025 readonly property bool hasImage: (pendingImage !== null && (pendingImage.status === Image.Ready || pendingImage.status === Image.Loading)) 0026 || (albumArt.currentItem instanceof Image && albumArt.currentItem.status === Image.Ready) 0027 0028 readonly property bool animating: exitTransition.running || popExitTransition.running 0029 0030 /** 0031 * Whether the component is used in the compact representation 0032 */ 0033 property bool inCompactRepresentation: false 0034 0035 /** 0036 * Provides source item for \ShaderEffectSource 0037 */ 0038 readonly property alias albumArt: albumArt 0039 0040 property Image pendingImage: null 0041 0042 function loadAlbumArt() { 0043 if (pendingImage !== null) { 0044 pendingImage.destroy(); 0045 pendingImage = null; 0046 } 0047 0048 if (!root.albumArt) { 0049 albumArt.clear(StackView.PopTransition); 0050 return; 0051 } 0052 0053 const oldImageRatio = albumArt.currentItem instanceof Image ? albumArt.currentItem.sourceSize.width / albumArt.currentItem.sourceSize.height : 1; 0054 pendingImage = albumArtComponent.createObject(albumArt, { 0055 "source": root.albumArt, 0056 "sourceSize": Qt.size(container.width * Screen.devicePixelRatio, container.height * Screen.devicePixelRatio), 0057 "opacity": 0, 0058 }); 0059 0060 function replaceWhenLoaded() { 0061 // There can be a potential race: when the previous player is gone but the pending image is just ready in time, 0062 // pendingImage.destroy() -> QQuickImage::deleteLater(), so in the event queue statusChanged may be emitted 0063 // before pendingImage is deleted, but pendingImage is already set to null when the previous player is gone. 0064 if (pendingImage === null || pendingImage.status === Image.Loading) { 0065 return; 0066 } 0067 0068 if (pendingImage.status === Image.Ready) { 0069 const newImageRatio = pendingImage.sourceSize.width / pendingImage.sourceSize.height; 0070 exitTransitionOpacityAnimator.duration = oldImageRatio === newImageRatio ? 0 : Kirigami.Units.longDuration; 0071 } else { 0072 // Load placeholder icon, but keep the invalid image to avoid flashing application icons 0073 exitTransitionOpacityAnimator.duration = 0; 0074 } 0075 0076 pendingImage.statusChanged.disconnect(replaceWhenLoaded); 0077 albumArt.replace(pendingImage, {}, StackView.ReplaceTransition); 0078 pendingImage = null; 0079 } 0080 0081 pendingImage.statusChanged.connect(replaceWhenLoaded); 0082 replaceWhenLoaded(); 0083 } 0084 0085 Connections { 0086 enabled: root.expanded // BUG 477866 0087 target: container 0088 function onWidthChanged() { 0089 geometryChangeTimer.restart(); 0090 } 0091 function onHeightChanged() { 0092 geometryChangeTimer.restart(); 0093 } 0094 } 0095 0096 // Reload album art when size of container changes 0097 Timer { 0098 id: geometryChangeTimer 0099 interval: 250 0100 onTriggered: container.loadAlbumArt(); 0101 } 0102 0103 StackView { 0104 id: albumArt 0105 0106 anchors.fill: parent 0107 0108 readonly property string icon: root.iconName || "media-album-cover" 0109 0110 replaceEnter: Transition { 0111 OpacityAnimator { 0112 from: 0 0113 to: 1 0114 duration: Kirigami.Units.longDuration 0115 } 0116 } 0117 0118 replaceExit: Transition { 0119 id: exitTransition 0120 0121 SequentialAnimation { 0122 PauseAnimation { 0123 duration: Kirigami.Units.longDuration 0124 } 0125 0126 /** 0127 * If the new ratio and the old ratio are different, 0128 * perform a fade-out animation for the old image 0129 * to prevent it from suddenly disappearing. 0130 */ 0131 OpacityAnimator { 0132 id: exitTransitionOpacityAnimator 0133 from: 1 0134 to: 0 0135 duration: 0 0136 } 0137 } 0138 } 0139 0140 popExit: Transition { 0141 id: popExitTransition 0142 0143 OpacityAnimator { 0144 from: 1 0145 to: 0 0146 duration: Kirigami.Units.longDuration 0147 } 0148 } 0149 0150 Component { 0151 id: albumArtComponent 0152 0153 Image { // Album Art 0154 horizontalAlignment: Image.AlignHCenter 0155 verticalAlignment: Image.AlignVCenter 0156 fillMode: container.inCompactRepresentation ? Image.PreserveAspectCrop : Image.PreserveAspectFit 0157 0158 asynchronous: true 0159 cache: false 0160 0161 // onRemoved only fires when all transitions end. If a user switches songs quickly this adds up 0162 // Given it's such a heavy item, try to cleanup as early as possible 0163 StackView.onDeactivated: destroy() 0164 StackView.onRemoved: destroy() 0165 } 0166 } 0167 0168 Component { 0169 id: fallbackIconItem 0170 0171 Kirigami.Icon { // Fallback 0172 id: fallbackIcon 0173 0174 anchors.margins: Kirigami.Units.gridUnit * 2 0175 opacity: 0 0176 source: albumArt.icon 0177 0178 NumberAnimation { 0179 duration: Kirigami.Units.longDuration 0180 easing.type: Easing.OutCubic 0181 property: "opacity" 0182 running: true 0183 target: fallbackIcon 0184 to: 1 0185 } 0186 } 0187 } 0188 0189 // "No media playing" placeholder message 0190 Component { 0191 id: placeholderMessage 0192 0193 // Put PlaceholderMessage in Item so PlaceholderMessage will not fill its parent. 0194 Item { 0195 property alias source: message.iconName 0196 0197 PlasmaExtras.PlaceholderMessage { 0198 id: message 0199 anchors.centerIn: parent 0200 width: parent.width // For text wrap 0201 iconName: albumArt.icon 0202 text: root.playbackStatus > Mpris.PlaybackStatus.Stopped ? i18n("No title") : i18n("No media playing") 0203 } 0204 } 0205 } 0206 0207 Component { 0208 id: busyComponent 0209 0210 Item { 0211 PC3.BusyIndicator { 0212 id: busyIndicator 0213 anchors.centerIn: parent 0214 running: false 0215 } 0216 0217 SequentialAnimation { 0218 running: true 0219 0220 PauseAnimation { 0221 duration: Kirigami.Units.longDuration 0222 } 0223 PropertyAction { 0224 property: "running" 0225 target: busyIndicator 0226 value: true 0227 } 0228 } 0229 } 0230 0231 } 0232 } 0233 0234 Loader { 0235 anchors.fill: parent 0236 visible: active 0237 0238 readonly property bool isLoadingImage: pendingImage !== null && pendingImage.status === Image.Loading 0239 0240 active: (inCompactRepresentation || root.expanded) && (!container.hasImage || isLoadingImage) 0241 asynchronous: true 0242 sourceComponent: root.track ? (isLoadingImage ? busyComponent : fallbackIconItem) : placeholderMessage 0243 } 0244 } 0245