import QtQuick import QtQuick.Layouts import QtQuick.Effects import Quickshell import Quickshell.Io import Quickshell.Services.Mpris import qs.common import qs.common.functions import qs.common.widgets import Qt5Compat.GraphicalEffects Item { // -- fields -- id: playerController required property MprisPlayer player required property real popupRounding required property real contentPadding required property real artRounding property var artUrl: player?.trackArtUrl property string artDownloadLocation: Directories.coverArt property string artFileName: Qt.md5(artUrl) + ".jpg" property string artFilePath: `${artDownloadLocation}/${artFileName}` property color artDominantColor: ColorUtils.mix((colorQuantizer?.colors[0] ?? Appearance.colors.colPrimary), Appearance.colors.colPrimaryContainer, 0.8) || Appearance.m3colors.m3secondaryContainer property bool downloaded: false implicitWidth: widgetWidth implicitHeight: widgetHeight // colors property bool backgroundIsDark: artDominantColor.hslLightness < 0.5 property QtObject blendedColors: QtObject { property color colLayer0: ColorUtils.mix(Appearance.colors.colLayer0, artDominantColor, (backgroundIsDark && Appearance.m3colors.darkmode) ? 0.6 : 0.5) property color colLayer1: ColorUtils.mix(Appearance.colors.colLayer1, artDominantColor, 0.5) property color colOnLayer0: ColorUtils.mix(Appearance.colors.colOnLayer0, artDominantColor, 0.5) property color colOnLayer1: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5) property color colSubtext: ColorUtils.mix(Appearance.colors.colOnlayer1, artDominantColor, 0.5) property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimary, artDominantColor), artDominantColor, 0.5) property color colPrimaryHover: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryHover, artDominantColor), artDominantColor, 0.3) property color colPrimaryActive: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryActive, artDominantColor), artDominantColor, 0.3) property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.15) property color colSecondaryContainerHover: ColorUtils.mix(Appearance.colors.colSecondaryContainerHover, artDominantColor, 0.5) property color colSecondaryContainerActive: ColorUtils.mix(Appearance.colors.colSecondaryContainerActive, artDominantColor, 0.5) property color colOnPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3onPrimary, artDominantColor), artDominantColor, 0.5) property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, artDominantColor, 0.5) } // -- components -- component TrackChangeButton: RippleButton { implicitWidth: 24 implicitHeight: 24 property var iconName colBackground: ColorUtils.transparentize(blendedColors.colSecondaryContainer, 1) } Timer { running: playerController.player?.playbackState == MprisPlaybackState.Playing interval: 1000 repeat: true onTriggered: { playerController.player.positionChanged() } } onArtUrlChanged: { if (playerController.artUrl.length == 0) { playerController.artDominantColor = Appearance.m3colors.m3secondaryContainer; return; } playerController.downloaded = false coverArtDownloader.running = true } Process { id: coverArtDownloader property string targetFile: playerController.artUrl command: ["bash", "-c", `[ -f ${playerController.artFilePath} ] || curl -sSL '${targetFile}' -o '${playerController.artFilePath}'`] onExited: (exitCode, exitStatus) => { playerController.downloaded = true; } } ColorQuantizer { // From Quickshell id: colorQuantizer source: playerController.downloaded ? Qt.resolvedUrl(playerController.artFilePath) : "" depth: 0 rescaleSize: 1 } Rectangle { id: background anchors.fill: parent anchors.margins: Appearance.sizes.elevationMargin color: blendedColors.colLayer0 radius: root.popupRounding layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: background.width height: background.height radius: background.radius } } /* Image { id: blurredArt anchors.fill: parent source: playerController.downloaded ? Qt.resolvedUrl(playerController.artFilePath) : "" sourceSize.width: background.width sourceSize.height: background.height fillMode: Image.PreserveAspectCrop cache: false antialiasing: true asynchronous: true layer.enabled: true layer.effect: MultiEffect { source: blurredArt saturation: 0.2 blurEnabled: true blurMax: 100 blur: 1 } } */ Rectangle { anchors.fill: parent color: ColorUtils.transparentize(playerController.blendedColors.colLayer0, 0.3) radius: playerController.popupRounding } } RowLayout { anchors.fill: parent anchors.margins: playerController.contentPadding spacing: 15 Rectangle { id: artBackground Layout.fillHeight: true implicitWidth: height radius: playerController.artRounding color:ColorUtils.transparentize(blendedColors.colLayer1, 0.5) layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: artBackground.width height: artBackground.height radius: artBackground.radius } } Image { id: mediaArt property int size: parent.height anchors.fill: parent source: playerController.downloaded ? Qt.resolvedUrl(playerController.artFilePath) : "" fillMode: Image.PreserveAspectCrop cache:false antialiasing: true asynchronous: true width: size height: size sourceSize.width: size sourceSize.height: size } } ColumnLayout { Layout.fillHeight: true spacing: 2 StyledText { id: trackTitle Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.large color: blendedColors.colOnLayer0 elide: Text.ElideRight text: playerController.player?.trackTitle || "Untitled" } StyledText { id: trackArtist Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.smaller color:blendedColors.colSubtext elide: Text.ElideRight text: playerController.player?.trackArtist } Item { // spacing Layout.fillHeight: true } } } }