Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9e693c0a5 | |||
| 85d822346f | |||
| bd85a4fd4f | |||
| 50cfc9e66e | |||
| 88074ccb47 | |||
| 5a48b1b3a9 | |||
| 42268d7266 | |||
| 6d8f237547 | |||
| 45906f07c6 | |||
| c26793dfe2 | |||
| de22c05c33 | |||
| 3d334b9896 | |||
| 7045ecf8e6 | |||
| b88c2ba3c5 | |||
| d62c81762b | |||
| 6102407288 | |||
| 01ddf659d7 | |||
| 4551a63c32 | |||
| 023b869e9c | |||
| a0a675bc99 | |||
| 0ebd29ea02 | |||
| 0798dd3ea2 |
@@ -0,0 +1,19 @@
|
|||||||
|
# AGENTS
|
||||||
|
|
||||||
|
## Commit Style
|
||||||
|
|
||||||
|
- Use Conventional Commits for every commit message.
|
||||||
|
- Required format: `type(scope): subject` or `type: subject`.
|
||||||
|
- Keep `type`, optional `scope`, and subject text lowercase.
|
||||||
|
- Use imperative, concise subjects.
|
||||||
|
- Proper nouns may keep their normal capitalization only when necessary.
|
||||||
|
|
||||||
|
Valid examples:
|
||||||
|
- `feat(ui): add artist text truncation`
|
||||||
|
- `fix: handle empty metadata values`
|
||||||
|
- `chore(release): bump plasmoid version`
|
||||||
|
|
||||||
|
Invalid examples:
|
||||||
|
- `Feat(ui): add artist text truncation` (uppercase type)
|
||||||
|
- `fix(UI): handle empty metadata values` (uppercase scope)
|
||||||
|
- `fix: Handle empty metadata values` (uppercase subject)
|
||||||
@@ -4,6 +4,8 @@ NowPlaying is a KDE Plasma 6 widget inspired by the Cleartext Rainmeter widget.
|
|||||||
|
|
||||||
Now on KDE 6: https://www.pling.com/p/2195583/
|
Now on KDE 6: https://www.pling.com/p/2195583/
|
||||||
|
|
||||||
|
> Too little too late but I plan to update it regularly now if any issues are created - ruinivist
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
You need the basic KDE and Qt 6 tools for Plasma widget development.
|
You need the basic KDE and Qt 6 tools for Plasma widget development.
|
||||||
@@ -90,6 +92,18 @@ Enable the repository pre-commit hook:
|
|||||||
make hooks-install
|
make hooks-install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
The widget appearance can be tuned from the Plasma widget settings, including:
|
||||||
|
|
||||||
|
- custom multiline label text
|
||||||
|
- independent label, title, and artist font sizes
|
||||||
|
- title/artist vertical spacing
|
||||||
|
- NOW/PLAYING label vertical spacing
|
||||||
|
- separator gap from labels
|
||||||
|
- separator gap from track text
|
||||||
|
- separator height percentage
|
||||||
|
|
||||||
## Release Upload
|
## Release Upload
|
||||||
|
|
||||||
Required GitHub repository secrets:
|
Required GitHub repository secrets:
|
||||||
|
|||||||
@@ -13,5 +13,90 @@
|
|||||||
<label>Font family</label>
|
<label>Font family</label>
|
||||||
<default>Noto Sans</default>
|
<default>Noto Sans</default>
|
||||||
</entry>
|
</entry>
|
||||||
|
<entry name="labelFontSize" type="Int">
|
||||||
|
<label>Now Playing label font size</label>
|
||||||
|
<default>16</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="titleFontSize" type="Int">
|
||||||
|
<label>Track title font size</label>
|
||||||
|
<default>28</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="artistFontSize" type="Int">
|
||||||
|
<label>Track artist font size</label>
|
||||||
|
<default>26</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="labelVisibilityMode" type="String">
|
||||||
|
<label>Now Playing label visibility mode</label>
|
||||||
|
<default>auto</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="labelPlacement" type="String">
|
||||||
|
<label>Now Playing label placement</label>
|
||||||
|
<default>left</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="labelText" type="String">
|
||||||
|
<label>Now Playing label text</label>
|
||||||
|
<default><![CDATA[NOW
|
||||||
|
PLAYING]]></default>
|
||||||
|
</entry>
|
||||||
|
<entry name="useLabelArtwork" type="Bool">
|
||||||
|
<label>Show track image when available</label>
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="imageBorderRadius" type="Int">
|
||||||
|
<label>Image border radius</label>
|
||||||
|
<default>8</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="backgroundStyle" type="String">
|
||||||
|
<label>Background style</label>
|
||||||
|
<default>custom</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="backgroundColor" type="String">
|
||||||
|
<label>Background color</label>
|
||||||
|
<default>transparent</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="backgroundRadius" type="Int">
|
||||||
|
<label>Background radius</label>
|
||||||
|
<default>0</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="foregroundColor" type="String">
|
||||||
|
<label>Foreground color</label>
|
||||||
|
<default>white</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="textShadowEnabled" type="Bool">
|
||||||
|
<label>Enable text shadow</label>
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showMediaControls" type="Bool">
|
||||||
|
<label>Show media controls</label>
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="sourcePriority" type="String">
|
||||||
|
<label>Source priority</label>
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
|
<entry name="trackTextVerticalSpacing" type="Int">
|
||||||
|
<label>Track text vertical spacing</label>
|
||||||
|
<default>5</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="labelVerticalSpacing" type="Int">
|
||||||
|
<label>Now Playing label vertical spacing</label>
|
||||||
|
<default>0</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="separatorGapLabel" type="Int">
|
||||||
|
<label>Separator gap from label rail</label>
|
||||||
|
<default>4</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="separatorGapTrack" type="Int">
|
||||||
|
<label>Separator gap from track text</label>
|
||||||
|
<default>4</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="separatorHeight" type="Int">
|
||||||
|
<label>Separator height percent</label>
|
||||||
|
<default>90</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="hideSeparator" type="Bool">
|
||||||
|
<label>Hide separator</label>
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
</group>
|
</group>
|
||||||
</kcfg>
|
</kcfg>
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: labelContent
|
||||||
|
|
||||||
|
required property string artworkSource
|
||||||
|
required property var labelLines
|
||||||
|
required property string fontFamily
|
||||||
|
required property real fontPixelSize
|
||||||
|
required property string textColor
|
||||||
|
required property bool textShadowEnabled
|
||||||
|
required property int verticalSpacing
|
||||||
|
required property int horizontalAlignment
|
||||||
|
required property int borderRadius
|
||||||
|
required property real 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
|
||||||
|
implicitWidth: artworkSize
|
||||||
|
implicitHeight: artworkSize
|
||||||
|
onArtworkSourceChanged: handleArtworkSourceChange()
|
||||||
|
Component.onCompleted: handleArtworkSourceChange()
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: currentArtworkImage
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
opacity: 0
|
||||||
|
source: labelContent.currentArtworkSource
|
||||||
|
sourceSize.width: labelContent.artworkSize
|
||||||
|
sourceSize.height: labelContent.artworkSize
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: incomingArtworkImage
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
opacity: 0
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: artworkMaskShape
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
antialiasing: true
|
||||||
|
color: "white"
|
||||||
|
layer.enabled: true
|
||||||
|
radius: labelContent.borderRadius
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiEffect {
|
||||||
|
id: currentArtworkMask
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
autoPaddingEnabled: false
|
||||||
|
maskEnabled: true
|
||||||
|
maskSource: artworkMaskShape
|
||||||
|
source: currentArtworkImage
|
||||||
|
opacity: labelContent.currentArtworkOpacity
|
||||||
|
visible: labelContent.currentArtworkSource.length > 0 && opacity > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiEffect {
|
||||||
|
id: incomingArtworkMask
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
autoPaddingEnabled: false
|
||||||
|
maskEnabled: true
|
||||||
|
maskSource: artworkMaskShape
|
||||||
|
source: incomingArtworkImage
|
||||||
|
opacity: labelContent.incomingArtworkOpacity
|
||||||
|
visible: labelContent.incomingArtworkSource.length > 0 && opacity > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: labelTextColumn
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+112
-41
@@ -11,82 +11,155 @@ import org.kde.plasma.private.mpris as Mpris
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property int containerRole: Qt.UserRole + 1
|
||||||
property string sourceName: "any"
|
property string sourceName: "any"
|
||||||
|
property string sourcePriority: ""
|
||||||
property var mpris2Model
|
property var mpris2Model
|
||||||
|
property int modelRevision: 0
|
||||||
|
readonly property var normalizedSourcePriority: parseSourcePriority(sourcePriority)
|
||||||
|
readonly property var resolvedPlayer: resolvePlayer()
|
||||||
readonly property bool ready: {
|
readonly property bool ready: {
|
||||||
if (!mpris2Model.currentPlayer)
|
if (!resolvedPlayer)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return mpris2Model.currentPlayer.desktopEntry === sourceName || sourceName === "any";
|
return sourceName === "any" || entriesMatch(resolvedPlayer.desktopEntry, sourceName);
|
||||||
}
|
}
|
||||||
readonly property string artists: ready ? mpris2Model.currentPlayer.artist : ""
|
readonly property string artists: ready ? resolvedPlayer.artist : ""
|
||||||
readonly property string title: ready ? mpris2Model.currentPlayer.track : ""
|
readonly property string title: ready ? resolvedPlayer.track : ""
|
||||||
readonly property int playbackStatus: ready ? mpris2Model.currentPlayer.playbackStatus : Mpris.PlaybackStatus.Unknown
|
readonly property int playbackStatus: ready ? resolvedPlayer.playbackStatus : Mpris.PlaybackStatus.Unknown
|
||||||
readonly property int shuffle: ready ? mpris2Model.currentPlayer.shuffle : Mpris.ShuffleStatus.Unknown
|
readonly property int shuffle: ready ? resolvedPlayer.shuffle : Mpris.ShuffleStatus.Unknown
|
||||||
readonly property string artUrl: ready ? mpris2Model.currentPlayer.artUrl : ""
|
readonly property string artUrl: ready ? resolvedPlayer.artUrl : ""
|
||||||
readonly property int loopStatus: ready ? mpris2Model.currentPlayer.loopStatus : Mpris.LoopStatus.Unknown
|
readonly property int loopStatus: ready ? resolvedPlayer.loopStatus : Mpris.LoopStatus.Unknown
|
||||||
readonly property double songPosition: ready ? mpris2Model.currentPlayer.position : 0
|
readonly property double songPosition: ready ? resolvedPlayer.position : 0
|
||||||
readonly property double songLength: ready ? mpris2Model.currentPlayer.length : 0
|
readonly property double songLength: ready ? resolvedPlayer.length : 0
|
||||||
readonly property real volume: ready ? mpris2Model.currentPlayer.volume : 0
|
readonly property real volume: ready ? resolvedPlayer.volume : 0
|
||||||
readonly property string identity: ready ? mpris2Model.currentPlayer.identity : ""
|
readonly property string identity: ready ? resolvedPlayer.identity : ""
|
||||||
readonly property bool canGoNext: ready ? mpris2Model.currentPlayer.canGoNext : false
|
readonly property bool canGoNext: ready ? resolvedPlayer.canGoNext : false
|
||||||
readonly property bool canGoPrevious: ready ? mpris2Model.currentPlayer.canGoPrevious : false
|
readonly property bool canGoPrevious: ready ? resolvedPlayer.canGoPrevious : false
|
||||||
readonly property bool canPlay: ready ? mpris2Model.currentPlayer.canPlay : false
|
readonly property bool canPlay: ready ? resolvedPlayer.canPlay : false
|
||||||
readonly property bool canPause: ready ? mpris2Model.currentPlayer.canPause : false
|
readonly property bool canPause: ready ? resolvedPlayer.canPause : false
|
||||||
readonly property bool canSeek: ready ? mpris2Model.currentPlayer.canSeek : false
|
readonly property bool canSeek: ready ? resolvedPlayer.canSeek : false
|
||||||
readonly property bool canRaise: ready ? mpris2Model.currentPlayer.canRaise : false
|
readonly property bool canRaise: ready ? resolvedPlayer.canRaise : false
|
||||||
// To know whether Shuffle and Loop can be changed we have to check if the property is defined,
|
// To know whether Shuffle and Loop can be changed we have to check if the property is defined,
|
||||||
// unlike the other commands, LoopStatus and Shuffle do not have specific properties such as
|
// unlike the other commands, LoopStatus and Shuffle do not have specific properties such as
|
||||||
// CanPause, CanSeek, etc.
|
// CanPause, CanSeek, etc.
|
||||||
readonly property bool canChangeShuffle: ready ? mpris2Model.currentPlayer.shuffle !== undefined : false
|
readonly property bool canChangeShuffle: ready ? resolvedPlayer.shuffle !== undefined : false
|
||||||
readonly property bool canChangeLoopStatus: ready ? mpris2Model.currentPlayer.loopStatus !== undefined : false
|
readonly property bool canChangeLoopStatus: ready ? resolvedPlayer.loopStatus !== undefined : false
|
||||||
|
|
||||||
|
function normalizedEntry(value) {
|
||||||
|
return (value || "").toString().trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function entriesMatch(left, right) {
|
||||||
|
const normalizedLeft = normalizedEntry(left);
|
||||||
|
const normalizedRight = normalizedEntry(right);
|
||||||
|
if (!normalizedLeft || !normalizedRight)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return normalizedLeft === normalizedRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSourcePriority(value) {
|
||||||
|
return (value || "").split(",").map((entry) => {
|
||||||
|
return normalizedEntry(entry);
|
||||||
|
}).filter((entry) => {
|
||||||
|
return entry.length > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerAt(row) {
|
||||||
|
if (!mpris2Model || row < 0 || row >= mpris2Model.rowCount())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return mpris2Model.data(mpris2Model.index(row, 0), containerRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPlayerByEntry(targetEntry) {
|
||||||
|
if (!targetEntry || !mpris2Model)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const playerCount = mpris2Model.rowCount();
|
||||||
|
for (let row = 0; row < playerCount; ++row) {
|
||||||
|
const player = playerAt(row);
|
||||||
|
if (player && entriesMatch(player.desktopEntry, targetEntry))
|
||||||
|
return player;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePlayer() {
|
||||||
|
modelRevision;
|
||||||
|
if (!mpris2Model)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for (const targetEntry of normalizedSourcePriority) {
|
||||||
|
const matchedPlayer = findPlayerByEntry(targetEntry);
|
||||||
|
if (matchedPlayer)
|
||||||
|
return matchedPlayer;
|
||||||
|
|
||||||
|
}
|
||||||
|
return mpris2Model.currentPlayer || null;
|
||||||
|
}
|
||||||
|
|
||||||
function togglePlayPause() {
|
function togglePlayPause() {
|
||||||
if (mpris2Model.currentPlayer)
|
if (resolvedPlayer)
|
||||||
mpris2Model.currentPlayer.PlayPause();
|
resolvedPlayer.PlayPause();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPosition(position) {
|
function setPosition(position) {
|
||||||
mpris2Model.currentPlayer.position = position;
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.position = position;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function next() {
|
function next() {
|
||||||
if (mpris2Model.currentPlayer)
|
if (resolvedPlayer)
|
||||||
mpris2Model.currentPlayer.Next();
|
resolvedPlayer.Next();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function previous() {
|
function previous() {
|
||||||
if (mpris2Model.currentPlayer)
|
if (resolvedPlayer)
|
||||||
mpris2Model.currentPlayer.Previous();
|
resolvedPlayer.Previous();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePosition() {
|
function updatePosition() {
|
||||||
if (mpris2Model.currentPlayer)
|
if (resolvedPlayer)
|
||||||
mpris2Model.currentPlayer.updatePosition();
|
resolvedPlayer.updatePosition();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setVolume(volume) {
|
function setVolume(volume) {
|
||||||
mpris2Model.currentPlayer.volume = volume;
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.volume = volume;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeVolume(delta, showOSD) {
|
function changeVolume(delta, showOSD) {
|
||||||
mpris2Model.currentPlayer.changeVolume(delta, showOSD);
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.changeVolume(delta, showOSD);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setShuffle(shuffle) {
|
function setShuffle(shuffle) {
|
||||||
mpris2Model.currentPlayer.shuffle = shuffle;
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.shuffle = shuffle;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLoopStatus(loopStatus) {
|
function setLoopStatus(loopStatus) {
|
||||||
mpris2Model.currentPlayer.loopStatus = loopStatus;
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.loopStatus = loopStatus;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function raise() {
|
function raise() {
|
||||||
mpris2Model.currentPlayer.Raise();
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.Raise();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTrackTime(s) {
|
function formatTrackTime(s) {
|
||||||
@@ -99,13 +172,11 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mpris2Model: Mpris.Mpris2Model {
|
mpris2Model: Mpris.Mpris2Model {
|
||||||
onRowsInserted: (_, rowIndex) => {
|
onRowsInserted: root.modelRevision += 1
|
||||||
const CONTAINER_ROLE = Qt.UserRole + 1;
|
onRowsRemoved: root.modelRevision += 1
|
||||||
const player = this.data(this.index(rowIndex, 0), CONTAINER_ROLE);
|
onModelReset: root.modelRevision += 1
|
||||||
if (player.desktopEntry === root.sourceName)
|
onDataChanged: root.modelRevision += 1
|
||||||
this.currentIndex = rowIndex;
|
onCurrentPlayerChanged: root.modelRevision += 1
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,103 @@ 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
|
||||||
|
|
||||||
|
property string configuredFontFamily: "Noto Sans"
|
||||||
|
property int configuredLabelFontSize: 16
|
||||||
|
property int configuredTitleFontSize: 28
|
||||||
|
property int configuredArtistFontSize: 26
|
||||||
|
property string configuredLabelVisibilityMode: "auto"
|
||||||
|
property string configuredLabelPlacement: "left"
|
||||||
|
property string configuredLabelText: "NOW\nPLAYING"
|
||||||
|
property bool configuredUseLabelArtwork: false
|
||||||
|
property int configuredImageBorderRadius: 8
|
||||||
|
property string configuredBackgroundStyle: "custom"
|
||||||
|
property string configuredBackgroundColor: "transparent"
|
||||||
|
property int configuredBackgroundRadius: 0
|
||||||
|
property string configuredForegroundColor: "white"
|
||||||
|
property bool configuredTextShadowEnabled: false
|
||||||
|
property bool configuredShowMediaControls: true
|
||||||
|
property int configuredTrackTextVerticalSpacing: 5
|
||||||
|
property int configuredLabelVerticalSpacing: 0
|
||||||
|
property int configuredSeparatorGapLabel: 4
|
||||||
|
property int configuredSeparatorGapTrack: 4
|
||||||
|
property int configuredSeparatorHeight: 90
|
||||||
|
property bool configuredHideSeparator: false
|
||||||
readonly property double buttonSize: 16
|
readonly property double buttonSize: 16
|
||||||
|
readonly property string effectiveLabelVisibilityMode: configuredLabelVisibilityMode === "always" || configuredLabelVisibilityMode === "never" ? configuredLabelVisibilityMode : "auto"
|
||||||
|
readonly property bool labelsOnRight: configuredLabelPlacement === "right"
|
||||||
|
readonly property bool hideNowPlayingArea: effectiveLabelVisibilityMode === "never"
|
||||||
|
readonly property bool usesCustomBackground: configuredBackgroundStyle === "custom"
|
||||||
|
readonly property int hideModeHorizontalPadding: 8
|
||||||
|
readonly property int contentInset: 8
|
||||||
|
readonly property int availableContentWidth: Math.max(0, width - contentInset * 2)
|
||||||
|
readonly property int separatorReservedWidth: nowPlayingLabelsVisible && !hideNowPlayingArea ? 1 + effectiveSeparatorGapLabel + effectiveSeparatorGapTrack : 0
|
||||||
|
readonly property int maxLabelRailSize: Math.max(0, availableContentWidth - separatorReservedWidth)
|
||||||
|
readonly property int labelRailSize: Math.max(0, Math.min(height - contentInset * 2, maxLabelRailSize))
|
||||||
|
readonly property bool mediaControlsTargetShown: mediaControlsEnabled && controlsHoverActive && nowPlayingLabelsVisible && !hideNowPlayingArea
|
||||||
|
readonly property int artworkControlsGap: configuredUseLabelArtwork && player.artUrl.length > 0 ? 2 : 0
|
||||||
|
readonly property real labelShrunkScale: labelRailSize > 0 ? Math.max(0, (labelRailSize - buttonSize - artworkControlsGap) / labelRailSize) : 1
|
||||||
|
property real labelContentScale: mediaControlsTargetShown ? labelShrunkScale : 1
|
||||||
|
readonly property real controlsReveal: {
|
||||||
|
const range = 1 - labelShrunkScale;
|
||||||
|
if (range <= 0)
|
||||||
|
return mediaControlsTargetShown ? 1 : 0;
|
||||||
|
|
||||||
|
return Math.max(0, Math.min(1, (1 - labelContentScale) / range));
|
||||||
|
}
|
||||||
|
readonly property real labelContentSize: labelRailSize * labelContentScale
|
||||||
|
readonly property int effectiveImageBorderRadius: Math.max(0, configuredImageBorderRadius)
|
||||||
|
readonly property string effectiveBackgroundColor: configuredBackgroundColor || "transparent"
|
||||||
|
readonly property int effectiveBackgroundRadius: Math.max(0, configuredBackgroundRadius)
|
||||||
|
readonly property string effectiveForegroundColor: configuredForegroundColor || "white"
|
||||||
|
readonly property int effectiveLabelFontSize: Math.max(1, configuredLabelFontSize)
|
||||||
|
readonly property int effectiveTitleFontSize: Math.max(1, configuredTitleFontSize)
|
||||||
|
readonly property int effectiveArtistFontSize: Math.max(1, configuredArtistFontSize)
|
||||||
|
readonly property int effectiveTrackTextVerticalSpacing: configuredTrackTextVerticalSpacing
|
||||||
|
readonly property int effectiveLabelVerticalSpacing: configuredLabelVerticalSpacing
|
||||||
|
readonly property int effectiveSeparatorGapLabel: Math.max(0, configuredSeparatorGapLabel)
|
||||||
|
readonly property int effectiveSeparatorGapTrack: Math.max(0, configuredSeparatorGapTrack)
|
||||||
|
readonly property int effectiveSeparatorHeight: Math.max(0, Math.min(100, configuredSeparatorHeight))
|
||||||
|
readonly property var effectiveLabelLines: (configuredLabelText || "").split(/\r?\n/)
|
||||||
|
readonly property bool mediaControlsEnabled: configuredShowMediaControls
|
||||||
|
readonly property bool hasTrackInfo: player.ready && (((player.title || "").trim().length > 0) || ((player.artists || "").trim().length > 0))
|
||||||
|
readonly property bool rawHoverActive: mediaControlsEnabled && rootHoverHandler.hovered
|
||||||
|
readonly property bool controlsHoverActive: mediaControlsEnabled && (rawHoverActive || hoverExitTimer.running)
|
||||||
|
readonly property bool nowPlayingLabelsVisible: {
|
||||||
|
if (effectiveLabelVisibilityMode === "always")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (effectiveLabelVisibilityMode === "never")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return hasTrackInfo;
|
||||||
|
}
|
||||||
|
|
||||||
focus: true
|
focus: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
Keys.onPressed: (event) => {
|
onRawHoverActiveChanged: {
|
||||||
|
if (!mediaControlsMouseArea.mediaControlsEnabled) {
|
||||||
|
hoverExitTimer.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rawHoverActive)
|
||||||
|
hoverExitTimer.stop();
|
||||||
|
else
|
||||||
|
hoverExitTimer.restart();
|
||||||
|
}
|
||||||
|
onMediaControlsEnabledChanged: {
|
||||||
|
if (!mediaControlsMouseArea.mediaControlsEnabled)
|
||||||
|
hoverExitTimer.stop();
|
||||||
|
}
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
if (!mediaControlsMouseArea.mediaControlsEnabled) {
|
||||||
|
event.accepted = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!event.modifiers) {
|
if (!event.modifiers) {
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
if (event.key === Qt.Key_Space || event.key === Qt.Key_K)
|
if (event.key === Qt.Key_Space || event.key === Qt.Key_K)
|
||||||
@@ -25,77 +112,94 @@ MouseArea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: rootHoverHandler
|
||||||
|
|
||||||
|
enabled: mediaControlsMouseArea.mediaControlsEnabled
|
||||||
|
margin: 60
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: hoverExitTimer
|
||||||
|
|
||||||
|
interval: 300
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on labelContentScale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 250
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: mediaControlsMouseArea.usesCustomBackground
|
||||||
|
color: mediaControlsMouseArea.effectiveBackgroundColor
|
||||||
|
radius: mediaControlsMouseArea.effectiveBackgroundRadius
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
anchors.margins: mediaControlsMouseArea.contentInset
|
||||||
|
layoutDirection: mediaControlsMouseArea.labelsOnRight ? Qt.RightToLeft : Qt.LeftToRight
|
||||||
|
|
||||||
ColumnLayout {
|
Item {
|
||||||
id: leftColumn
|
id: leftColumn
|
||||||
|
|
||||||
|
visible: !mediaControlsMouseArea.hideNowPlayingArea
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
states: [
|
Layout.minimumWidth: visible ? mediaControlsMouseArea.labelRailSize : 0
|
||||||
State {
|
Layout.preferredWidth: visible ? mediaControlsMouseArea.labelRailSize : 0
|
||||||
name: "buttonsVisible"
|
Layout.maximumWidth: visible ? mediaControlsMouseArea.labelRailSize : 0
|
||||||
when: mediaControlsMouseArea.containsMouse
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
target: nowPlayingLabels
|
|
||||||
Layout.bottomMargin: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "buttonsHidden"
|
|
||||||
when: !mediaControlsMouseArea.containsMouse
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
target: nowPlayingLabels
|
|
||||||
Layout.bottomMargin: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillHeight: true
|
id: labelSlot
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
readonly property bool showsArtwork: mediaControlsMouseArea.configuredUseLabelArtwork && player.artUrl.length > 0
|
||||||
id: nowPlayingLabels
|
// Text is rasterized at whole pixel sizes, then lightly scaled between steps for smoother motion.
|
||||||
|
readonly property real textTargetFontSize: Math.max(1, mediaControlsMouseArea.effectiveLabelFontSize * mediaControlsMouseArea.labelContentScale)
|
||||||
|
readonly property int textRasterFontSize: Math.max(1, Math.round(textTargetFontSize))
|
||||||
|
readonly property real textResidualScale: textTargetFontSize / textRasterFontSize
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignRight
|
x: mediaControlsMouseArea.labelsOnRight ? 0 : leftColumn.width - width
|
||||||
spacing: 0
|
y: 0
|
||||||
|
width: mediaControlsMouseArea.labelContentSize
|
||||||
|
height: mediaControlsMouseArea.labelContentSize
|
||||||
|
clip: true
|
||||||
|
visible: mediaControlsMouseArea.nowPlayingLabelsVisible
|
||||||
|
|
||||||
PlasmaComponents.Label {
|
LabelContent {
|
||||||
Layout.alignment: Qt.AlignRight
|
anchors.left: parent.left
|
||||||
text: "NOW"
|
anchors.top: parent.top
|
||||||
lineHeight: 0.8
|
width: labelSlot.showsArtwork ? mediaControlsMouseArea.labelRailSize : mediaControlsMouseArea.labelContentSize / labelSlot.textResidualScale
|
||||||
font.pixelSize: 16
|
height: labelSlot.showsArtwork ? mediaControlsMouseArea.labelRailSize : mediaControlsMouseArea.labelContentSize / labelSlot.textResidualScale
|
||||||
font.bold: true
|
scale: labelSlot.showsArtwork ? mediaControlsMouseArea.labelContentScale : labelSlot.textResidualScale
|
||||||
// qmllint disable missing-property
|
transformOrigin: Item.TopLeft
|
||||||
font.family: plasmoid.configuration.fontFamily
|
artworkSource: mediaControlsMouseArea.configuredUseLabelArtwork ? player.artUrl : ""
|
||||||
|
labelLines: mediaControlsMouseArea.effectiveLabelLines
|
||||||
|
fontFamily: mediaControlsMouseArea.configuredFontFamily
|
||||||
|
fontPixelSize: labelSlot.showsArtwork ? mediaControlsMouseArea.effectiveLabelFontSize : labelSlot.textRasterFontSize
|
||||||
|
textColor: mediaControlsMouseArea.effectiveForegroundColor
|
||||||
|
textShadowEnabled: mediaControlsMouseArea.configuredTextShadowEnabled
|
||||||
|
verticalSpacing: mediaControlsMouseArea.effectiveLabelVerticalSpacing
|
||||||
|
horizontalAlignment: mediaControlsMouseArea.labelsOnRight ? Text.AlignLeft : Text.AlignRight
|
||||||
|
borderRadius: mediaControlsMouseArea.effectiveImageBorderRadius
|
||||||
|
artworkSize: labelSlot.showsArtwork ? mediaControlsMouseArea.labelRailSize : mediaControlsMouseArea.labelContentSize / labelSlot.textResidualScale
|
||||||
}
|
}
|
||||||
|
|
||||||
PlasmaComponents.Label {
|
|
||||||
id: nowPlayingLabel2
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
text: "PLAYING"
|
|
||||||
lineHeight: 0.8
|
|
||||||
font.bold: true
|
|
||||||
font.pixelSize: 16
|
|
||||||
// qmllint disable missing-property
|
|
||||||
font.family: plasmoid.configuration.fontFamily
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: mediaControls
|
id: mediaControls
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
x: mediaControlsMouseArea.labelsOnRight ? 0 : leftColumn.width - width
|
||||||
opacity: mediaControlsMouseArea.containsMouse ? 1 : 0
|
y: mediaControlsMouseArea.labelContentSize + mediaControlsMouseArea.artworkControlsGap
|
||||||
visible: opacity > 0
|
width: implicitWidth
|
||||||
|
height: mediaControlsMouseArea.buttonSize
|
||||||
|
clip: true
|
||||||
|
enabled: mediaControlsMouseArea.mediaControlsTargetShown
|
||||||
|
opacity: mediaControlsMouseArea.controlsReveal
|
||||||
|
visible: mediaControlsMouseArea.mediaControlsTargetShown || opacity > 0
|
||||||
|
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
Layout.preferredWidth: buttonSize
|
Layout.preferredWidth: buttonSize
|
||||||
@@ -109,8 +213,8 @@ MouseArea {
|
|||||||
|
|
||||||
contentItem: Kirigami.Icon {
|
contentItem: Kirigami.Icon {
|
||||||
source: "media-skip-backward"
|
source: "media-skip-backward"
|
||||||
|
color: mediaControlsMouseArea.effectiveForegroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
@@ -127,8 +231,8 @@ MouseArea {
|
|||||||
|
|
||||||
contentItem: Kirigami.Icon {
|
contentItem: Kirigami.Icon {
|
||||||
source: player.playbackStatus === 2 ? "media-playback-pause" : "media-playback-start"
|
source: player.playbackStatus === 2 ? "media-playback-pause" : "media-playback-start"
|
||||||
|
color: mediaControlsMouseArea.effectiveForegroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
@@ -140,72 +244,66 @@ MouseArea {
|
|||||||
|
|
||||||
contentItem: Kirigami.Icon {
|
contentItem: Kirigami.Icon {
|
||||||
source: "media-skip-forward"
|
source: "media-skip-forward"
|
||||||
|
color: mediaControlsMouseArea.effectiveForegroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
PropertyAnimation {
|
|
||||||
duration: 250
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
transitions: Transition {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 250
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
properties: "Layout.bottomMargin"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
id: separator
|
id: separatorContainer
|
||||||
|
|
||||||
|
visible: mediaControlsMouseArea.nowPlayingLabelsVisible && !mediaControlsMouseArea.hideNowPlayingArea
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
width: 1
|
Layout.leftMargin: visible ? mediaControlsMouseArea.effectiveSeparatorGapLabel : 0
|
||||||
|
Layout.rightMargin: visible ? mediaControlsMouseArea.effectiveSeparatorGapTrack : 0
|
||||||
|
Layout.minimumWidth: visible ? 1 : 0
|
||||||
|
Layout.preferredWidth: visible ? 1 : 0
|
||||||
|
Layout.maximumWidth: visible ? 1 : 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 1
|
||||||
|
height: Math.round(parent.height * mediaControlsMouseArea.effectiveSeparatorHeight / 100)
|
||||||
|
color: mediaControlsMouseArea.configuredHideSeparator ? "transparent" : mediaControlsMouseArea.effectiveForegroundColor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: infoColumn
|
id: infoColumn
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.minimumWidth: 0
|
||||||
|
Layout.leftMargin: mediaControlsMouseArea.hideNowPlayingArea ? mediaControlsMouseArea.hideModeHorizontalPadding : 0
|
||||||
|
Layout.rightMargin: mediaControlsMouseArea.hideNowPlayingArea ? mediaControlsMouseArea.hideModeHorizontalPadding : 0
|
||||||
|
spacing: mediaControlsMouseArea.effectiveTrackTextVerticalSpacing
|
||||||
|
|
||||||
PlasmaComponents.Label {
|
ShadowedLabel {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
shadowEnabled: mediaControlsMouseArea.configuredTextShadowEnabled
|
||||||
text: player.title
|
text: player.title
|
||||||
font.pixelSize: 28
|
font.pixelSize: mediaControlsMouseArea.effectiveTitleFontSize
|
||||||
font.bold: true
|
font.bold: true
|
||||||
// qmllint disable missing-property
|
font.family: mediaControlsMouseArea.configuredFontFamily
|
||||||
font.family: plasmoid.configuration.fontFamily
|
color: mediaControlsMouseArea.effectiveForegroundColor
|
||||||
lineHeight: 0.8
|
lineHeight: 0.8
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
horizontalAlignment: mediaControlsMouseArea.labelsOnRight ? Text.AlignRight : Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
PlasmaComponents.Label {
|
ShadowedLabel {
|
||||||
Layout.maximumWidth: 300
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: mediaControlsMouseArea.labelsOnRight ? Qt.AlignRight : Qt.AlignLeft
|
||||||
|
shadowEnabled: mediaControlsMouseArea.configuredTextShadowEnabled
|
||||||
text: player.artists
|
text: player.artists
|
||||||
font.pixelSize: 26
|
font.pixelSize: mediaControlsMouseArea.effectiveArtistFontSize
|
||||||
// qmllint disable missing-property
|
font.family: mediaControlsMouseArea.configuredFontFamily
|
||||||
font.family: plasmoid.configuration.fontFamily
|
color: mediaControlsMouseArea.effectiveForegroundColor
|
||||||
lineHeight: 0.8
|
lineHeight: 0.8
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
horizontalAlignment: mediaControlsMouseArea.labelsOnRight ? Text.AlignRight : Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
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
|
||||||
|
readonly property real contentWidth: width > 0 ? width : foregroundLabel.implicitWidth
|
||||||
|
readonly property real contentHeight: height > 0 ? height : foregroundLabel.implicitHeight
|
||||||
|
|
||||||
|
implicitWidth: foregroundLabel.implicitWidth
|
||||||
|
implicitHeight: foregroundLabel.implicitHeight
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
id: shadowSourceLabel
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
width: shadowedLabel.contentWidth
|
||||||
|
height: shadowedLabel.contentHeight
|
||||||
|
text: foregroundLabel.text
|
||||||
|
font: foregroundLabel.font
|
||||||
|
lineHeight: foregroundLabel.lineHeight
|
||||||
|
elide: foregroundLabel.elide
|
||||||
|
horizontalAlignment: foregroundLabel.horizontalAlignment
|
||||||
|
verticalAlignment: foregroundLabel.verticalAlignment
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiEffect {
|
||||||
|
x: shadowSourceLabel.x
|
||||||
|
y: shadowSourceLabel.y
|
||||||
|
width: shadowSourceLabel.width
|
||||||
|
height: shadowSourceLabel.height
|
||||||
|
autoPaddingEnabled: true
|
||||||
|
shadowBlur: 0.5
|
||||||
|
shadowColor: "#3f4c66"
|
||||||
|
shadowEnabled: shadowedLabel.shadowEnabled
|
||||||
|
shadowHorizontalOffset: 0
|
||||||
|
shadowOpacity: 0.42
|
||||||
|
shadowScale: 1
|
||||||
|
shadowVerticalOffset: 2
|
||||||
|
source: shadowSourceLabel
|
||||||
|
visible: shadowedLabel.shadowEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
id: foregroundLabel
|
||||||
|
|
||||||
|
width: shadowedLabel.contentWidth
|
||||||
|
height: shadowedLabel.contentHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,24 +1,132 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Dialogs as Dialogs
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import org.kde.kcmutils as KCM
|
import org.kde.kcmutils as KCM
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.plasma.private.mpris as Mpris
|
||||||
|
|
||||||
KCM.SimpleKCM {
|
KCM.SimpleKCM {
|
||||||
id: configRoot
|
id: configRoot
|
||||||
|
|
||||||
property alias cfg_opacity: opacitySpinBox.value
|
property alias cfg_opacity: opacitySpinBox.value
|
||||||
property string cfg_fontFamily
|
property string cfg_fontFamily
|
||||||
|
property alias cfg_labelFontSize: labelFontSizeSpinBox.value
|
||||||
|
property alias cfg_titleFontSize: titleFontSizeSpinBox.value
|
||||||
|
property alias cfg_artistFontSize: artistFontSizeSpinBox.value
|
||||||
|
property string cfg_labelVisibilityMode
|
||||||
|
property string cfg_labelPlacement
|
||||||
|
property alias cfg_labelText: labelTextArea.text
|
||||||
|
property alias cfg_useLabelArtwork: useLabelArtworkCheckBox.checked
|
||||||
|
property alias cfg_imageBorderRadius: imageBorderRadiusSpinBox.value
|
||||||
|
property string cfg_backgroundStyle
|
||||||
|
property alias cfg_backgroundColor: backgroundColorField.text
|
||||||
|
property alias cfg_backgroundRadius: backgroundRadiusSpinBox.value
|
||||||
|
property alias cfg_foregroundColor: foregroundColorField.text
|
||||||
|
property alias cfg_textShadowEnabled: textShadowCheckBox.checked
|
||||||
|
property alias cfg_showMediaControls: showMediaControlsCheckBox.checked
|
||||||
|
property alias cfg_sourcePriority: sourcePriorityField.text
|
||||||
|
property alias cfg_trackTextVerticalSpacing: trackTextVerticalSpacingSpinBox.value
|
||||||
|
property alias cfg_labelVerticalSpacing: labelVerticalSpacingSpinBox.value
|
||||||
|
property alias cfg_separatorGapLabel: separatorGapLabelSpinBox.value
|
||||||
|
property alias cfg_separatorGapTrack: separatorGapTrackSpinBox.value
|
||||||
|
property alias cfg_separatorHeight: separatorHeightSpinBox.value
|
||||||
|
property alias cfg_hideSeparator: hideSeparatorCheckBox.checked
|
||||||
|
readonly property int containerRole: Qt.UserRole + 1
|
||||||
readonly property var availableFonts: Qt.fontFamilies()
|
readonly property var availableFonts: Qt.fontFamilies()
|
||||||
|
property var detectedSources: []
|
||||||
|
readonly property var labelVisibilityOptions: [{
|
||||||
|
"text": i18n("Auto hide when idle"),
|
||||||
|
"value": "auto"
|
||||||
|
}, {
|
||||||
|
"text": i18n("Always show"),
|
||||||
|
"value": "always"
|
||||||
|
}, {
|
||||||
|
"text": i18n("Always hide"),
|
||||||
|
"value": "never"
|
||||||
|
}]
|
||||||
|
readonly property var labelPlacementOptions: [{
|
||||||
|
"text": i18n("Left"),
|
||||||
|
"value": "left"
|
||||||
|
}, {
|
||||||
|
"text": i18n("Right"),
|
||||||
|
"value": "right"
|
||||||
|
}]
|
||||||
|
readonly property var backgroundStyleOptions: [{
|
||||||
|
"text": i18n("Default"),
|
||||||
|
"value": "custom"
|
||||||
|
}, {
|
||||||
|
"text": i18n("Solid background"),
|
||||||
|
"value": "default"
|
||||||
|
}]
|
||||||
|
|
||||||
|
function syncComboByValue(comboBox, options, configuredValue, fallbackValue) {
|
||||||
|
const requestedValue = configuredValue || fallbackValue;
|
||||||
|
const requestedIndex = options.findIndex((option) => {
|
||||||
|
return option.value === requestedValue;
|
||||||
|
});
|
||||||
|
const fallbackIndex = options.findIndex((option) => {
|
||||||
|
return option.value === fallbackValue;
|
||||||
|
});
|
||||||
|
const resolvedIndex = requestedIndex >= 0 ? requestedIndex : (fallbackIndex >= 0 ? fallbackIndex : 0);
|
||||||
|
if (comboBox.currentIndex !== resolvedIndex)
|
||||||
|
comboBox.currentIndex = resolvedIndex;
|
||||||
|
|
||||||
|
return options[resolvedIndex].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDesktopEntry(value) {
|
||||||
|
return (value || "").toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshDetectedSources() {
|
||||||
|
const sources = [];
|
||||||
|
const seen = {
|
||||||
|
};
|
||||||
|
const playerCount = detectedSourcesModel.rowCount();
|
||||||
|
for (let row = 0; row < playerCount; ++row) {
|
||||||
|
const player = detectedSourcesModel.data(detectedSourcesModel.index(row, 0), containerRole);
|
||||||
|
const desktopEntry = normalizeDesktopEntry(player ? player.desktopEntry : "");
|
||||||
|
const desktopEntryKey = desktopEntry.toLowerCase();
|
||||||
|
if (!desktopEntry || seen[desktopEntryKey])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
seen[desktopEntryKey] = true;
|
||||||
|
sources.push(desktopEntry);
|
||||||
|
}
|
||||||
|
detectedSources = sources;
|
||||||
|
}
|
||||||
|
|
||||||
onCfg_fontFamilyChanged: {
|
onCfg_fontFamilyChanged: {
|
||||||
const index = availableFonts.indexOf(cfg_fontFamily);
|
const index = availableFonts.indexOf(cfg_fontFamily);
|
||||||
if (index >= 0 && fontFamilyComboBox.currentIndex !== index)
|
if (index >= 0 && fontFamilyComboBox.currentIndex !== index)
|
||||||
fontFamilyComboBox.currentIndex = index;
|
fontFamilyComboBox.currentIndex = index;
|
||||||
|
|
||||||
|
}
|
||||||
|
onCfg_labelVisibilityModeChanged: {
|
||||||
|
const resolvedValue = syncComboByValue(labelVisibilityModeComboBox, labelVisibilityOptions, cfg_labelVisibilityMode, "auto");
|
||||||
|
if (cfg_labelVisibilityMode !== resolvedValue)
|
||||||
|
cfg_labelVisibilityMode = resolvedValue;
|
||||||
|
|
||||||
|
}
|
||||||
|
onCfg_labelPlacementChanged: {
|
||||||
|
const resolvedValue = syncComboByValue(labelPlacementComboBox, labelPlacementOptions, cfg_labelPlacement, "left");
|
||||||
|
if (cfg_labelPlacement !== resolvedValue)
|
||||||
|
cfg_labelPlacement = resolvedValue;
|
||||||
|
|
||||||
|
}
|
||||||
|
onCfg_backgroundStyleChanged: {
|
||||||
|
const resolvedValue = syncComboByValue(backgroundStyleComboBox, backgroundStyleOptions, cfg_backgroundStyle, "custom");
|
||||||
|
if (cfg_backgroundStyle !== resolvedValue)
|
||||||
|
cfg_backgroundStyle = resolvedValue;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.FormLayout {
|
Kirigami.FormLayout {
|
||||||
|
SectionHeader {
|
||||||
|
title: i18n("Global")
|
||||||
|
}
|
||||||
|
|
||||||
QQC2.SpinBox {
|
QQC2.SpinBox {
|
||||||
id: opacitySpinBox
|
id: opacitySpinBox
|
||||||
|
|
||||||
@@ -35,6 +143,278 @@ KCM.SimpleKCM {
|
|||||||
onActivated: configRoot.cfg_fontFamily = currentText
|
onActivated: configRoot.cfg_fontFamily = currentText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QQC2.ComboBox {
|
||||||
|
id: backgroundStyleComboBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Background style:")
|
||||||
|
model: configRoot.backgroundStyleOptions
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "value"
|
||||||
|
onActivated: configRoot.cfg_backgroundStyle = currentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Kirigami.FormData.label: i18n("Background color:")
|
||||||
|
enabled: configRoot.cfg_backgroundStyle === "custom"
|
||||||
|
|
||||||
|
QQC2.TextField {
|
||||||
|
id: backgroundColorField
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: i18n("transparent or #AARRGGBB")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
text: i18n("Pick...")
|
||||||
|
onClicked: {
|
||||||
|
backgroundColorDialog.selectedColor = backgroundColorField.text || "transparent";
|
||||||
|
backgroundColorDialog.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Kirigami.FormData.label: i18n("Foreground color:")
|
||||||
|
|
||||||
|
QQC2.TextField {
|
||||||
|
id: foregroundColorField
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: i18n("white or #RRGGBB")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
text: i18n("Pick...")
|
||||||
|
onClicked: {
|
||||||
|
foregroundColorDialog.selectedColor = foregroundColorField.text || "white";
|
||||||
|
foregroundColorDialog.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: backgroundRadiusSpinBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Background radius:")
|
||||||
|
enabled: configRoot.cfg_backgroundStyle === "custom"
|
||||||
|
from: 0
|
||||||
|
to: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.CheckBox {
|
||||||
|
id: textShadowCheckBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Text shadow:")
|
||||||
|
text: i18n("Enable")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.CheckBox {
|
||||||
|
id: showMediaControlsCheckBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Media controls:")
|
||||||
|
text: i18n("Show media controls")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.TextField {
|
||||||
|
id: sourcePriorityField
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Source priority:")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: i18n("spotify, firefox")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
id: detectedSourcesField
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Current sources being detected:")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Math.max(implicitHeight, Kirigami.Units.gridUnit * 2)
|
||||||
|
textFormat: TextEdit.PlainText
|
||||||
|
wrapMode: TextEdit.WrapAnywhere
|
||||||
|
text: configRoot.detectedSources.length > 0 ? configRoot.detectedSources.join("\n") : i18n("No active MPRIS desktopEntry values detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: separatorHeightSpinBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Separator height (%):")
|
||||||
|
from: 0
|
||||||
|
to: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.CheckBox {
|
||||||
|
id: hideSeparatorCheckBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Separator:")
|
||||||
|
text: i18n("Hide separator")
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionHeader {
|
||||||
|
title: i18n("Label")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ComboBox {
|
||||||
|
id: labelVisibilityModeComboBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Now Playing labels:")
|
||||||
|
model: configRoot.labelVisibilityOptions
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "value"
|
||||||
|
onActivated: configRoot.cfg_labelVisibilityMode = currentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ComboBox {
|
||||||
|
id: labelPlacementComboBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Label placement:")
|
||||||
|
model: configRoot.labelPlacementOptions
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "value"
|
||||||
|
onActivated: configRoot.cfg_labelPlacement = currentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.CheckBox {
|
||||||
|
id: useLabelArtworkCheckBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Label image:")
|
||||||
|
text: i18n("Show track image when available")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: imageBorderRadiusSpinBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Image border radius:")
|
||||||
|
from: 0
|
||||||
|
to: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.TextArea {
|
||||||
|
id: labelTextArea
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Label text:")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||||
|
placeholderText: i18n("Use a line break for multiple lines")
|
||||||
|
wrapMode: TextEdit.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: labelFontSizeSpinBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Label font size:")
|
||||||
|
from: 8
|
||||||
|
to: 72
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: labelVerticalSpacingSpinBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Label line spacing (negatives possible):")
|
||||||
|
from: -200
|
||||||
|
to: 200
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: separatorGapLabelSpinBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Separator gap from label:")
|
||||||
|
from: 0
|
||||||
|
to: 24
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionHeader {
|
||||||
|
title: i18n("Track & Artist")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: titleFontSizeSpinBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Title font size:")
|
||||||
|
from: 8
|
||||||
|
to: 96
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: artistFontSizeSpinBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Artist font size:")
|
||||||
|
from: 8
|
||||||
|
to: 96
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: trackTextVerticalSpacingSpinBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Title/artist spacing (negatives possible):")
|
||||||
|
from: -200
|
||||||
|
to: 200
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: separatorGapTrackSpinBox
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Separator gap from track:")
|
||||||
|
from: 0
|
||||||
|
to: 24
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialogs.ColorDialog {
|
||||||
|
id: backgroundColorDialog
|
||||||
|
|
||||||
|
title: i18n("Select background color")
|
||||||
|
options: Dialogs.ColorDialog.ShowAlphaChannel
|
||||||
|
onAccepted: configRoot.cfg_backgroundColor = selectedColor.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialogs.ColorDialog {
|
||||||
|
id: foregroundColorDialog
|
||||||
|
|
||||||
|
title: i18n("Select foreground color")
|
||||||
|
options: Dialogs.ColorDialog.ShowAlphaChannel
|
||||||
|
onAccepted: configRoot.cfg_foregroundColor = selectedColor.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
Mpris.Mpris2Model {
|
||||||
|
id: detectedSourcesModel
|
||||||
|
|
||||||
|
onRowsInserted: configRoot.refreshDetectedSources()
|
||||||
|
onRowsRemoved: configRoot.refreshDetectedSources()
|
||||||
|
onModelReset: configRoot.refreshDetectedSources()
|
||||||
|
onDataChanged: configRoot.refreshDetectedSources()
|
||||||
|
Component.onCompleted: configRoot.refreshDetectedSources()
|
||||||
|
}
|
||||||
|
|
||||||
|
component SectionHeader: Item {
|
||||||
|
required property string title
|
||||||
|
|
||||||
|
Kirigami.FormData.isSection: true
|
||||||
|
implicitHeight: sectionLayout.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: sectionLayout
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: parent.parent.title
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 1
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
opacity: 0.25
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.plasma.core as PlasmaCore
|
||||||
import org.kde.plasma.plasmoid
|
import org.kde.plasma.plasmoid
|
||||||
|
|
||||||
PlasmoidItem {
|
PlasmoidItem {
|
||||||
@@ -10,28 +11,132 @@ PlasmoidItem {
|
|||||||
// "qmllint" sees `plasmoid` as QObject here, but Plasma injects `configuration` at runtime.
|
// "qmllint" sees `plasmoid` as QObject here, but Plasma injects `configuration` at runtime.
|
||||||
// qmllint disable missing-property
|
// qmllint disable missing-property
|
||||||
property int configuredOpacity: plasmoid.configuration.opacity
|
property int configuredOpacity: plasmoid.configuration.opacity
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property string configuredFontFamily: plasmoid.configuration.fontFamily
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property int configuredLabelFontSize: plasmoid.configuration.labelFontSize
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property int configuredTitleFontSize: plasmoid.configuration.titleFontSize
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property int configuredArtistFontSize: plasmoid.configuration.artistFontSize
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property string configuredLabelVisibilityMode: plasmoid.configuration.labelVisibilityMode
|
||||||
|
// qmllint disable missing-property
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property string configuredLabelPlacement: plasmoid.configuration.labelPlacement
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property string configuredLabelText: plasmoid.configuration.labelText
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property bool configuredUseLabelArtwork: plasmoid.configuration.useLabelArtwork
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property int configuredImageBorderRadius: plasmoid.configuration.imageBorderRadius
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property string configuredBackgroundStyle: plasmoid.configuration.backgroundStyle
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property string configuredBackgroundColor: plasmoid.configuration.backgroundColor
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property int configuredBackgroundRadius: plasmoid.configuration.backgroundRadius
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property string configuredForegroundColor: plasmoid.configuration.foregroundColor
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property bool configuredTextShadowEnabled: plasmoid.configuration.textShadowEnabled
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property bool configuredShowMediaControls: plasmoid.configuration.showMediaControls
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property string configuredSourcePriority: plasmoid.configuration.sourcePriority
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property int configuredTrackTextVerticalSpacing: plasmoid.configuration.trackTextVerticalSpacing
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property int configuredLabelVerticalSpacing: plasmoid.configuration.labelVerticalSpacing
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property int configuredSeparatorGapLabel: plasmoid.configuration.separatorGapLabel
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property int configuredSeparatorGapTrack: plasmoid.configuration.separatorGapTrack
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property int configuredSeparatorHeight: plasmoid.configuration.separatorHeight
|
||||||
|
// qmllint disable missing-property
|
||||||
|
property bool configuredHideSeparator: plasmoid.configuration.hideSeparator
|
||||||
|
readonly property int resolvedBackgroundHints: {
|
||||||
|
if (configuredBackgroundStyle === "default")
|
||||||
|
return PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground;
|
||||||
|
|
||||||
|
if (configuredBackgroundStyle === "none")
|
||||||
|
return PlasmaCore.Types.NoBackground;
|
||||||
|
|
||||||
|
return PlasmaCore.Types.NoBackground;
|
||||||
|
}
|
||||||
|
|
||||||
width: Kirigami.Units.gridUnit * 25
|
width: Kirigami.Units.gridUnit * 25
|
||||||
height: Kirigami.Units.gridUnit * 5
|
height: Kirigami.Units.gridUnit * 5
|
||||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 25
|
Layout.minimumWidth: Kirigami.Units.gridUnit * 25
|
||||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
|
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
|
||||||
Plasmoid.backgroundHints: "NoBackground"
|
Plasmoid.backgroundHints: resolvedBackgroundHints
|
||||||
opacity: configuredOpacity / 100
|
opacity: configuredOpacity / 100
|
||||||
|
|
||||||
Player {
|
Player {
|
||||||
id: player
|
id: player
|
||||||
|
|
||||||
|
sourcePriority: root.configuredSourcePriority
|
||||||
}
|
}
|
||||||
|
|
||||||
fullRepresentation: Representation {
|
fullRepresentation: Representation {
|
||||||
|
width: root.width
|
||||||
|
height: root.height
|
||||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 25
|
Layout.minimumWidth: Kirigami.Units.gridUnit * 25
|
||||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
|
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
|
||||||
Layout.preferredWidth: root.width
|
Layout.preferredWidth: root.width
|
||||||
Layout.preferredHeight: root.height
|
Layout.preferredHeight: root.height
|
||||||
|
configuredFontFamily: root.configuredFontFamily
|
||||||
|
configuredLabelFontSize: root.configuredLabelFontSize
|
||||||
|
configuredTitleFontSize: root.configuredTitleFontSize
|
||||||
|
configuredArtistFontSize: root.configuredArtistFontSize
|
||||||
|
configuredLabelVisibilityMode: root.configuredLabelVisibilityMode
|
||||||
|
configuredLabelPlacement: root.configuredLabelPlacement
|
||||||
|
configuredLabelText: root.configuredLabelText
|
||||||
|
configuredUseLabelArtwork: root.configuredUseLabelArtwork
|
||||||
|
configuredImageBorderRadius: root.configuredImageBorderRadius
|
||||||
|
configuredBackgroundStyle: root.configuredBackgroundStyle
|
||||||
|
configuredBackgroundColor: root.configuredBackgroundColor
|
||||||
|
configuredBackgroundRadius: root.configuredBackgroundRadius
|
||||||
|
configuredForegroundColor: root.configuredForegroundColor
|
||||||
|
configuredTextShadowEnabled: root.configuredTextShadowEnabled
|
||||||
|
configuredShowMediaControls: root.configuredShowMediaControls
|
||||||
|
configuredTrackTextVerticalSpacing: root.configuredTrackTextVerticalSpacing
|
||||||
|
configuredLabelVerticalSpacing: root.configuredLabelVerticalSpacing
|
||||||
|
configuredSeparatorGapLabel: root.configuredSeparatorGapLabel
|
||||||
|
configuredSeparatorGapTrack: root.configuredSeparatorGapTrack
|
||||||
|
configuredSeparatorHeight: root.configuredSeparatorHeight
|
||||||
|
configuredHideSeparator: root.configuredHideSeparator
|
||||||
}
|
}
|
||||||
|
|
||||||
compactRepresentation: Representation {
|
compactRepresentation: Representation {
|
||||||
|
width: root.width
|
||||||
|
height: root.height
|
||||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 25
|
Layout.minimumWidth: Kirigami.Units.gridUnit * 25
|
||||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
|
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
|
||||||
|
Layout.preferredWidth: root.width
|
||||||
|
Layout.preferredHeight: root.height
|
||||||
|
configuredFontFamily: root.configuredFontFamily
|
||||||
|
configuredLabelFontSize: root.configuredLabelFontSize
|
||||||
|
configuredTitleFontSize: root.configuredTitleFontSize
|
||||||
|
configuredArtistFontSize: root.configuredArtistFontSize
|
||||||
|
configuredLabelVisibilityMode: root.configuredLabelVisibilityMode
|
||||||
|
configuredLabelPlacement: root.configuredLabelPlacement
|
||||||
|
configuredLabelText: root.configuredLabelText
|
||||||
|
configuredUseLabelArtwork: root.configuredUseLabelArtwork
|
||||||
|
configuredImageBorderRadius: root.configuredImageBorderRadius
|
||||||
|
configuredBackgroundStyle: root.configuredBackgroundStyle
|
||||||
|
configuredBackgroundColor: root.configuredBackgroundColor
|
||||||
|
configuredBackgroundRadius: root.configuredBackgroundRadius
|
||||||
|
configuredForegroundColor: root.configuredForegroundColor
|
||||||
|
configuredTextShadowEnabled: root.configuredTextShadowEnabled
|
||||||
|
configuredShowMediaControls: root.configuredShowMediaControls
|
||||||
|
configuredTrackTextVerticalSpacing: root.configuredTrackTextVerticalSpacing
|
||||||
|
configuredLabelVerticalSpacing: root.configuredLabelVerticalSpacing
|
||||||
|
configuredSeparatorGapLabel: root.configuredSeparatorGapLabel
|
||||||
|
configuredSeparatorGapTrack: root.configuredSeparatorGapTrack
|
||||||
|
configuredSeparatorHeight: root.configuredSeparatorHeight
|
||||||
|
configuredHideSeparator: root.configuredHideSeparator
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user