From 1a9f0abcf7a1303de81d210bde2132380cfd28a0 Mon Sep 17 00:00:00 2001 From: Fushan Wen Date: Sun, 3 Jul 2022 00:40:00 +0800 Subject: [PATCH] applets/mediacontroller: move StackView to new file The component will be reused in compact representation. --- .../contents/ui/AlbumArtStackView.qml | 177 ++++++++++++++++ .../contents/ui/ExpandedRepresentation.qml | 196 +++--------------- 2 files changed, 203 insertions(+), 170 deletions(-) create mode 100644 applets/mediacontroller/contents/ui/AlbumArtStackView.qml diff --git a/applets/mediacontroller/contents/ui/AlbumArtStackView.qml b/applets/mediacontroller/contents/ui/AlbumArtStackView.qml new file mode 100644 index 000000000..687ff5b02 --- /dev/null +++ b/applets/mediacontroller/contents/ui/AlbumArtStackView.qml @@ -0,0 +1,177 @@ +/* + SPDX-FileCopyrightText: 2013 Marco Martin + SPDX-FileCopyrightText: 2014 Sebastian Kügler + SPDX-FileCopyrightText: 2014 Kai Uwe Broulik + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras + +Item { + id: container + + /** + * Whether the album art image is available or in loading status + */ + readonly property bool hasImage: albumArt.currentItem instanceof Image + && (albumArt.currentItem.status === Image.Ready + || albumArt.currentItem.status === Image.Loading) + + readonly property bool animating: exitTransition.running || popExitTransition.running + + /** + * Whether the component is used in the compact representation + */ + property bool inCompactRepresentation: false + + /** + * Provides source item for \ShaderEffectSource + */ + readonly property alias albumArt: albumArt + + function loadAlbumArt() { + if (!root.albumArt) { + albumArt.clear(StackView.PopTransition); + return; + } + + const oldImageRatio = albumArt.currentItem instanceof Image ? albumArt.currentItem.sourceSize.width / albumArt.currentItem.sourceSize.height : 1; + const pendingImage = albumArtComponent.createObject(albumArt, { + "source": root.albumArt, + "opacity": 0, + }); + + function replaceWhenLoaded() { + if (pendingImage.status === Image.Loading) { + return; + } + if (pendingImage.status === Image.Null || pendingImage.status === Image.Error) { + pendingImage.destroy(); + + // Load placeholder icon + albumArt.clear(StackView.PopTransition); + + return; + } + + const newImageRatio = pendingImage.sourceSize.width / pendingImage.sourceSize.height; + exitTransitionOpacityAnimator.duration = oldImageRatio === newImageRatio ? 0 : PlasmaCore.Units.longDuration; + + albumArt.replace(pendingImage, {}, StackView.ReplaceTransition); + pendingImage.statusChanged.disconnect(replaceWhenLoaded); + } + + pendingImage.statusChanged.connect(replaceWhenLoaded); + replaceWhenLoaded(); + } + + StackView { + id: albumArt + + anchors.fill: parent + + readonly property string icon: (mpris2Source.currentData && mpris2Source.currentData["Desktop Icon Name"]) || "media-album-cover" + + replaceEnter: Transition { + OpacityAnimator { + from: 0 + to: 1 + duration: PlasmaCore.Units.longDuration + } + } + + replaceExit: Transition { + id: exitTransition + + SequentialAnimation { + PauseAnimation { + duration: PlasmaCore.Units.longDuration + } + + /** + * If the new ratio and the old ratio are different, + * perform a fade-out animation for the old image + * to prevent it from suddenly disappearing. + */ + OpacityAnimator { + id: exitTransitionOpacityAnimator + from: 1 + to: 0 + duration: 0 + } + } + } + + popExit: Transition { + id: popExitTransition + + OpacityAnimator { + from: 1 + to: 0 + duration: PlasmaCore.Units.longDuration + } + } + + Component { + id: albumArtComponent + + Image { // Album Art + horizontalAlignment: Image.AlignRight + verticalAlignment: Image.AlignVCenter + fillMode: Image.PreserveAspectFit + + asynchronous: true + cache: false + + StackView.onRemoved: { + source = ""; // HACK: Reduce memory usage + destroy(); + } + } + } + + Component { + id: fallbackIconItem + + PlasmaCore.IconItem { // Fallback + anchors.margins: PlasmaCore.Units.largeSpacing * 2 + source: albumArt.icon + } + } + + // "No media playing" placeholder message + Component { + id: placeholderMessage + + // Put PlaceholderMessage in Item so PlaceholderMessage will not fill its parent. + Item { + property alias source: message.iconName + + PlasmaExtras.PlaceholderMessage { + id: message + anchors.centerIn: parent + width: parent.width // For text wrap + iconName: albumArt.icon + text: i18n("No media playing") + } + } + } + + } + + Loader { + anchors.fill: parent + visible: active + + active: (inCompactRepresentation || Plasmoid.expanded) && !container.hasImage + asynchronous: true + sourceComponent: root.track ? fallbackIconItem : placeholderMessage + } +} + diff --git a/applets/mediacontroller/contents/ui/ExpandedRepresentation.qml b/applets/mediacontroller/contents/ui/ExpandedRepresentation.qml index 38984ccb9..8cc466e68 100644 --- a/applets/mediacontroller/contents/ui/ExpandedRepresentation.qml +++ b/applets/mediacontroller/contents/ui/ExpandedRepresentation.qml @@ -158,11 +158,11 @@ PlasmaExtras.Representation { property real scaleFactor: 1.0 property ShaderEffectSource source: ShaderEffectSource { id: shaderEffectSource - sourceItem: albumArt + sourceItem: albumArt.albumArt } anchors.centerIn: parent - visible: (exitTransition.running || popExitTransition.running || albumArt.hasImage) && !softwareRendering + visible: (albumArt.animating || albumArt.hasImage) && !softwareRendering layer.enabled: !softwareRendering layer.effect: HueSaturation { @@ -185,20 +185,20 @@ PlasmaExtras.Representation { // use State to avoid unnecessary reevaluation of width and height states: State { name: "albumArtReady" - when: Plasmoid.expanded && backgroundImage.visible && albumArt.currentItem.paintedWidth > 0 + when: Plasmoid.expanded && backgroundImage.visible && shaderEffectSource.sourceItem.currentItem.paintedWidth > 0 PropertyChanges { target: backgroundImage - scaleFactor: Math.max(parent.width / albumArt.currentItem.paintedWidth, parent.height / albumArt.currentItem.paintedHeight) - width: Math.round(albumArt.currentItem.paintedWidth * scaleFactor) - height: Math.round(albumArt.currentItem.paintedHeight * scaleFactor) + scaleFactor: Math.max(parent.width / shaderEffectSource.sourceItem.currentItem.paintedWidth, parent.height / shaderEffectSource.sourceItem.currentItem.paintedHeight) + width: Math.round(shaderEffectSource.sourceItem.currentItem.paintedWidth * scaleFactor) + height: Math.round(shaderEffectSource.sourceItem.currentItem.paintedHeight * scaleFactor) } PropertyChanges { target: shaderEffectSource // HACK: Fix background ratio when DPI > 1 - sourceRect: Qt.rect(albumArt.width - albumArt.currentItem.paintedWidth, - Math.round((albumArt.height - albumArt.currentItem.paintedHeight) / 2), - albumArt.currentItem.paintedWidth, - albumArt.currentItem.paintedHeight) + sourceRect: Qt.rect(shaderEffectSource.sourceItem.width - shaderEffectSource.sourceItem.currentItem.paintedWidth, + Math.round((shaderEffectSource.sourceItem.height - shaderEffectSource.sourceItem.currentItem.paintedHeight) / 2), + shaderEffectSource.sourceItem.currentItem.paintedWidth, + shaderEffectSource.sourceItem.currentItem.paintedHeight) } } } @@ -213,177 +213,33 @@ PlasmaExtras.Representation { spacing: PlasmaCore.Units.largeSpacing - Item { + AlbumArtStackView { + id: albumArt + Layout.fillWidth: true Layout.fillHeight: true Layout.preferredWidth: 50 - QQC2.StackView { - id: albumArt - anchors.fill: parent - - readonly property bool hasImage: currentItem instanceof Image - && (currentItem.status === Image.Ready || currentItem.status === Image.Loading) - - replaceEnter: Transition { - OpacityAnimator { - from: 0 - to: 1 - duration: PlasmaCore.Units.longDuration - } - } - - replaceExit: Transition { - id: exitTransition - - SequentialAnimation { - PauseAnimation { - duration: PlasmaCore.Units.longDuration - } - - /** - * If the new ratio and the old ratio are different, - * perform a fade-out animation for the old image - * to prevent it from suddenly disappearing. - */ - OpacityAnimator { - id: exitTransitionOpacityAnimator - from: 1 - to: 0 - duration: 0 - } - } - } - - popExit: Transition { - id: popExitTransition - - OpacityAnimator { - from: 1 - to: 0 - duration: PlasmaCore.Units.longDuration - } - } - - Connections { - enabled: Plasmoid.expanded - target: root - - function onAlbumArtChanged() { - albumArt.loadAlbumArt(); - } - } - - Connections { - target: plasmoid - - function onExpandedChanged() { - // NOTE: Don't use strict equality - if (!Plasmoid.expanded || (albumArt.currentItem instanceof Image && albumArt.currentItem.source == root.albumArt)) { - return; - } - - albumArt.loadAlbumArt(); - } - } - - function loadAlbumArt() { - if (!root.albumArt) { - albumArt.clear(QQC2.StackView.PopTransition); - return; - } - - const oldImageRatio = albumArt.currentItem instanceof Image ? albumArt.currentItem.sourceSize.width / albumArt.currentItem.sourceSize.height : 1; - const pendingImage = albumArtComponent.createObject(albumArt, { - "source": root.albumArt, - "opacity": 0, - }); - - function replaceWhenLoaded() { - if (pendingImage.status === Image.Loading) { - return; - } - if (pendingImage.status === Image.Null || pendingImage.status === Image.Error) { - pendingImage.destroy(); - - // Also clear the old image - albumArt.clear(QQC2.StackView.PopTransition); + Connections { + enabled: Plasmoid.expanded + target: root - return; - } - - const newImageRatio = pendingImage.sourceSize.width / pendingImage.sourceSize.height; - exitTransitionOpacityAnimator.duration = oldImageRatio === newImageRatio ? 0 : PlasmaCore.Units.longDuration; - - albumArt.replace(pendingImage, {}, QQC2.StackView.ReplaceTransition); - pendingImage.statusChanged.disconnect(replaceWhenLoaded); - } - - pendingImage.statusChanged.connect(replaceWhenLoaded); - replaceWhenLoaded(); - } - - Component { - id: albumArtComponent - - Image { // Album Art - horizontalAlignment: Image.AlignRight - verticalAlignment: Image.AlignVCenter - fillMode: Image.PreserveAspectFit - - asynchronous: true - cache: false - - QQC2.StackView.onRemoved: { - source = ""; // HACK: Reduce memory usage - destroy(); - } - } + function onAlbumArtChanged() { + albumArt.loadAlbumArt(); } } + Connections { + target: Plasmoid.self - Loader { - id: fallbackIconLoader - // When albumArt is shown, the icon is unloaded to reduce memory usage. - readonly property string icon: (mpris2Source.currentData && mpris2Source.currentData["Desktop Icon Name"]) || "media-album-cover" - active: Plasmoid.expanded && !albumArt.hasImage - anchors.fill: parent - - sourceComponent: root.track ? fallbackIconItem : placeholderMessage - - opacity: active ? 1 : 0 - Behavior on opacity { - NumberAnimation { - duration: PlasmaCore.Units.longDuration - } - } - - Component { - id: fallbackIconItem - - PlasmaCore.IconItem { // Fallback - source: icon - anchors { - fill: parent - margins: PlasmaCore.Units.largeSpacing * 2 - } + function onExpandedChanged() { + // NOTE: Don't use strict equality + if (!Plasmoid.expanded + || (albumArt.albumArt.currentItem instanceof Image && albumArt.albumArt.currentItem.source == root.albumArt)) { + return; } - } - - Component { - id: placeholderMessage - Item { // Put PlaceholderMessage in Item so PlaceholderMessage will not fill its parent. - anchors.fill: parent - - PlasmaExtras.PlaceholderMessage { // "No media playing" placeholder message - width: parent.width // For text wrap - anchors.centerIn: parent - iconName: icon - text: i18n("No media playing") - } - } + albumArt.loadAlbumArt(); } } }