refactor(ui): extract label content component
This commit is contained in:
@@ -0,0 +1,281 @@
|
|||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: labelContent
|
||||||
|
|
||||||
|
required property string artworkSource
|
||||||
|
required property var labelLines
|
||||||
|
required property string fontFamily
|
||||||
|
required property int fontPixelSize
|
||||||
|
required property string textColor
|
||||||
|
required property bool textShadowEnabled
|
||||||
|
required property int verticalSpacing
|
||||||
|
required property int horizontalAlignment
|
||||||
|
required property int borderRadius
|
||||||
|
required property int artworkSize
|
||||||
|
property string currentArtworkSource: ""
|
||||||
|
property string incomingArtworkSource: ""
|
||||||
|
property real currentArtworkOpacity: 0
|
||||||
|
property real incomingArtworkOpacity: 0
|
||||||
|
property bool crossfadeActive: false
|
||||||
|
readonly property bool artworkVisible: currentArtworkOpacity > 0 || incomingArtworkOpacity > 0
|
||||||
|
readonly property bool holdsArtworkSlot: currentArtworkSource.length > 0 || artworkVisible
|
||||||
|
readonly property real textOpacity: crossfadeActive || (currentArtworkSource.length > 0 && incomingArtworkSource.length > 0) ? 0 : 1 - Math.max(currentArtworkOpacity, incomingArtworkOpacity)
|
||||||
|
|
||||||
|
function resetIncomingArtwork() {
|
||||||
|
incomingArtworkSource = "";
|
||||||
|
incomingArtworkOpacity = 0;
|
||||||
|
crossfadeActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishIncomingArtwork() {
|
||||||
|
currentArtworkSource = incomingArtworkSource;
|
||||||
|
currentArtworkOpacity = incomingArtworkOpacity;
|
||||||
|
resetIncomingArtwork();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCurrentArtwork() {
|
||||||
|
currentArtworkSource = "";
|
||||||
|
currentArtworkOpacity = 0;
|
||||||
|
resetIncomingArtwork();
|
||||||
|
}
|
||||||
|
|
||||||
|
function startFadeOutToText() {
|
||||||
|
crossfadeAnimation.stop();
|
||||||
|
fadeInIncomingArtwork.stop();
|
||||||
|
resetIncomingArtwork();
|
||||||
|
if (currentArtworkSource.length === 0 && currentArtworkOpacity === 0) {
|
||||||
|
clearCurrentArtwork();
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
fadeOutCurrentArtwork.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleArtworkSourceChange() {
|
||||||
|
const nextSource = artworkSource;
|
||||||
|
if (nextSource.length === 0) {
|
||||||
|
startFadeOutToText();
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if (nextSource === currentArtworkSource) {
|
||||||
|
fadeOutCurrentArtwork.stop();
|
||||||
|
currentArtworkOpacity = currentArtworkSource.length > 0 ? 1 : 0;
|
||||||
|
resetIncomingArtwork();
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if (nextSource === incomingArtworkSource)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
fadeOutCurrentArtwork.stop();
|
||||||
|
if (currentArtworkSource.length > 0)
|
||||||
|
currentArtworkOpacity = 1;
|
||||||
|
|
||||||
|
crossfadeAnimation.stop();
|
||||||
|
fadeInIncomingArtwork.stop();
|
||||||
|
incomingArtworkSource = nextSource;
|
||||||
|
incomingArtworkOpacity = 0;
|
||||||
|
crossfadeActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startIncomingTransition() {
|
||||||
|
if (incomingArtworkSource.length === 0 || incomingArtworkSource !== artworkSource)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
fadeOutCurrentArtwork.stop();
|
||||||
|
incomingArtworkOpacity = 0;
|
||||||
|
if (currentArtworkSource.length > 0 && currentArtworkOpacity > 0) {
|
||||||
|
crossfadeActive = true;
|
||||||
|
currentArtworkOpacity = 1;
|
||||||
|
crossfadeAnimation.restart();
|
||||||
|
} else {
|
||||||
|
crossfadeActive = false;
|
||||||
|
currentArtworkOpacity = 0;
|
||||||
|
fadeInIncomingArtwork.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleIncomingArtworkFailure() {
|
||||||
|
if (incomingArtworkSource.length === 0)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
resetIncomingArtwork();
|
||||||
|
startFadeOutToText();
|
||||||
|
}
|
||||||
|
|
||||||
|
clip: holdsArtworkSlot
|
||||||
|
implicitHeight: holdsArtworkSlot ? artworkSize : labelTextColumn.implicitHeight
|
||||||
|
onArtworkSourceChanged: handleArtworkSourceChange()
|
||||||
|
Component.onCompleted: handleArtworkSourceChange()
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: currentArtworkImage
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
visible: false
|
||||||
|
source: labelContent.currentArtworkSource
|
||||||
|
sourceSize.width: labelContent.artworkSize
|
||||||
|
sourceSize.height: labelContent.artworkSize
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: incomingArtworkImage
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
visible: false
|
||||||
|
source: labelContent.incomingArtworkSource
|
||||||
|
sourceSize.width: labelContent.artworkSize
|
||||||
|
sourceSize.height: labelContent.artworkSize
|
||||||
|
onSourceChanged: labelContent.incomingArtworkOpacity = 0
|
||||||
|
onStatusChanged: {
|
||||||
|
if (source.toString().length === 0)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
if (status === Image.Ready)
|
||||||
|
labelContent.startIncomingTransition();
|
||||||
|
else if (status === Image.Error)
|
||||||
|
labelContent.handleIncomingArtworkFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpacityMask {
|
||||||
|
id: currentArtworkMask
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
cached: true
|
||||||
|
source: currentArtworkImage
|
||||||
|
opacity: labelContent.currentArtworkOpacity
|
||||||
|
visible: labelContent.currentArtworkSource.length > 0 && opacity > 0
|
||||||
|
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: labelContent.width
|
||||||
|
height: labelContent.height
|
||||||
|
radius: labelContent.borderRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
OpacityMask {
|
||||||
|
id: incomingArtworkMask
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
cached: true
|
||||||
|
source: incomingArtworkImage
|
||||||
|
opacity: labelContent.incomingArtworkOpacity
|
||||||
|
visible: labelContent.incomingArtworkSource.length > 0 && opacity > 0
|
||||||
|
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: labelContent.width
|
||||||
|
height: labelContent.height
|
||||||
|
radius: labelContent.borderRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: labelTextColumn
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: labelContent.verticalSpacing
|
||||||
|
opacity: labelContent.textOpacity
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: labelContent.labelLines
|
||||||
|
|
||||||
|
delegate: ShadowedLabel {
|
||||||
|
required property string modelData
|
||||||
|
|
||||||
|
width: labelTextColumn.width
|
||||||
|
shadowEnabled: labelContent.textShadowEnabled
|
||||||
|
text: modelData
|
||||||
|
lineHeight: 0.8
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: labelContent.fontPixelSize
|
||||||
|
font.family: labelContent.fontFamily
|
||||||
|
color: labelContent.textColor
|
||||||
|
horizontalAlignment: labelContent.horizontalAlignment
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 250
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: crossfadeAnimation
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
NumberAnimation {
|
||||||
|
target: labelContent
|
||||||
|
property: "currentArtworkOpacity"
|
||||||
|
to: 0
|
||||||
|
duration: 250
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: labelContent
|
||||||
|
property: "incomingArtworkOpacity"
|
||||||
|
to: 1
|
||||||
|
duration: 250
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptAction {
|
||||||
|
script: labelContent.finishIncomingArtwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: fadeInIncomingArtwork
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: labelContent
|
||||||
|
property: "incomingArtworkOpacity"
|
||||||
|
to: 1
|
||||||
|
duration: 250
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptAction {
|
||||||
|
script: labelContent.finishIncomingArtwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: fadeOutCurrentArtwork
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: labelContent
|
||||||
|
property: "currentArtworkOpacity"
|
||||||
|
to: 0
|
||||||
|
duration: 250
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptAction {
|
||||||
|
script: labelContent.clearCurrentArtwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import Qt5Compat.GraphicalEffects
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.plasma.components as PlasmaComponents
|
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: mediaControlsMouseArea
|
id: mediaControlsMouseArea
|
||||||
@@ -166,72 +164,19 @@ MouseArea {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: mediaControlsMouseArea.nowPlayingLabelsVisible
|
visible: mediaControlsMouseArea.nowPlayingLabelsVisible
|
||||||
|
|
||||||
Item {
|
LabelContent {
|
||||||
id: labelContent
|
|
||||||
|
|
||||||
readonly property bool artworkReady: mediaControlsMouseArea.configuredUseLabelArtwork && artworkImage.status === Image.Ready && artworkImage.source.toString().length > 0
|
|
||||||
|
|
||||||
Layout.alignment: mediaControlsMouseArea.labelsOnRight ? Qt.AlignLeft : Qt.AlignRight
|
Layout.alignment: mediaControlsMouseArea.labelsOnRight ? Qt.AlignLeft : Qt.AlignRight
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
clip: artworkReady
|
artworkSource: mediaControlsMouseArea.configuredUseLabelArtwork ? player.artUrl : ""
|
||||||
implicitHeight: artworkReady ? mediaControlsMouseArea.labelRailWidth : labelTextColumn.implicitHeight
|
labelLines: mediaControlsMouseArea.effectiveLabelLines
|
||||||
|
fontFamily: mediaControlsMouseArea.configuredFontFamily
|
||||||
Image {
|
fontPixelSize: mediaControlsMouseArea.effectiveLabelFontSize
|
||||||
id: artworkImage
|
textColor: mediaControlsMouseArea.effectiveForegroundColor
|
||||||
|
textShadowEnabled: mediaControlsMouseArea.configuredTextShadowEnabled
|
||||||
anchors.fill: parent
|
verticalSpacing: mediaControlsMouseArea.effectiveLabelVerticalSpacing
|
||||||
asynchronous: true
|
horizontalAlignment: mediaControlsMouseArea.labelsOnRight ? Text.AlignLeft : Text.AlignRight
|
||||||
cache: true
|
borderRadius: mediaControlsMouseArea.effectiveImageBorderRadius
|
||||||
fillMode: Image.PreserveAspectCrop
|
artworkSize: mediaControlsMouseArea.labelRailWidth
|
||||||
visible: false
|
|
||||||
source: mediaControlsMouseArea.configuredUseLabelArtwork ? player.artUrl : ""
|
|
||||||
sourceSize.width: mediaControlsMouseArea.labelRailWidth
|
|
||||||
sourceSize.height: mediaControlsMouseArea.labelRailWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
OpacityMask {
|
|
||||||
anchors.fill: parent
|
|
||||||
cached: true
|
|
||||||
source: artworkImage
|
|
||||||
visible: labelContent.artworkReady
|
|
||||||
|
|
||||||
maskSource: Rectangle {
|
|
||||||
width: labelContent.width
|
|
||||||
height: labelContent.height
|
|
||||||
radius: mediaControlsMouseArea.effectiveImageBorderRadius
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: labelTextColumn
|
|
||||||
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
spacing: mediaControlsMouseArea.effectiveLabelVerticalSpacing
|
|
||||||
visible: !labelContent.artworkReady
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: mediaControlsMouseArea.effectiveLabelLines
|
|
||||||
|
|
||||||
delegate: ShadowedLabel {
|
|
||||||
required property string modelData
|
|
||||||
|
|
||||||
width: labelTextColumn.width
|
|
||||||
shadowEnabled: mediaControlsMouseArea.configuredTextShadowEnabled
|
|
||||||
text: modelData
|
|
||||||
lineHeight: 0.8
|
|
||||||
font.bold: true
|
|
||||||
font.pixelSize: mediaControlsMouseArea.effectiveLabelFontSize
|
|
||||||
font.family: mediaControlsMouseArea.configuredFontFamily
|
|
||||||
color: mediaControlsMouseArea.effectiveForegroundColor
|
|
||||||
horizontalAlignment: mediaControlsMouseArea.labelsOnRight ? Text.AlignLeft : Text.AlignRight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -380,44 +325,4 @@ MouseArea {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
component ShadowedLabel: Item {
|
|
||||||
id: shadowedLabel
|
|
||||||
|
|
||||||
property alias text: foregroundLabel.text
|
|
||||||
property alias font: foregroundLabel.font
|
|
||||||
property alias lineHeight: foregroundLabel.lineHeight
|
|
||||||
property alias elide: foregroundLabel.elide
|
|
||||||
property alias horizontalAlignment: foregroundLabel.horizontalAlignment
|
|
||||||
property alias verticalAlignment: foregroundLabel.verticalAlignment
|
|
||||||
property alias color: foregroundLabel.color
|
|
||||||
property bool shadowEnabled: false
|
|
||||||
|
|
||||||
implicitWidth: foregroundLabel.implicitWidth + (shadowEnabled ? 1 : 0)
|
|
||||||
implicitHeight: foregroundLabel.implicitHeight + (shadowEnabled ? 1 : 0)
|
|
||||||
|
|
||||||
PlasmaComponents.Label {
|
|
||||||
id: shadowLabel
|
|
||||||
|
|
||||||
visible: shadowedLabel.shadowEnabled
|
|
||||||
x: 1
|
|
||||||
y: 1
|
|
||||||
width: foregroundLabel.width
|
|
||||||
height: foregroundLabel.height
|
|
||||||
text: foregroundLabel.text
|
|
||||||
font: foregroundLabel.font
|
|
||||||
lineHeight: foregroundLabel.lineHeight
|
|
||||||
elide: foregroundLabel.elide
|
|
||||||
horizontalAlignment: foregroundLabel.horizontalAlignment
|
|
||||||
verticalAlignment: foregroundLabel.verticalAlignment
|
|
||||||
color: "#99000000"
|
|
||||||
}
|
|
||||||
|
|
||||||
PlasmaComponents.Label {
|
|
||||||
id: foregroundLabel
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import QtQuick
|
||||||
|
import org.kde.plasma.components as PlasmaComponents
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: shadowedLabel
|
||||||
|
|
||||||
|
property alias text: foregroundLabel.text
|
||||||
|
property alias font: foregroundLabel.font
|
||||||
|
property alias lineHeight: foregroundLabel.lineHeight
|
||||||
|
property alias elide: foregroundLabel.elide
|
||||||
|
property alias horizontalAlignment: foregroundLabel.horizontalAlignment
|
||||||
|
property alias verticalAlignment: foregroundLabel.verticalAlignment
|
||||||
|
property alias color: foregroundLabel.color
|
||||||
|
property bool shadowEnabled: false
|
||||||
|
|
||||||
|
implicitWidth: foregroundLabel.implicitWidth + (shadowEnabled ? 1 : 0)
|
||||||
|
implicitHeight: foregroundLabel.implicitHeight + (shadowEnabled ? 1 : 0)
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
id: shadowLabel
|
||||||
|
|
||||||
|
visible: shadowedLabel.shadowEnabled
|
||||||
|
x: 1
|
||||||
|
y: 1
|
||||||
|
width: foregroundLabel.width
|
||||||
|
height: foregroundLabel.height
|
||||||
|
text: foregroundLabel.text
|
||||||
|
font: foregroundLabel.font
|
||||||
|
lineHeight: foregroundLabel.lineHeight
|
||||||
|
elide: foregroundLabel.elide
|
||||||
|
horizontalAlignment: foregroundLabel.horizontalAlignment
|
||||||
|
verticalAlignment: foregroundLabel.verticalAlignment
|
||||||
|
color: "#99000000"
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
id: foregroundLabel
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user