forked from Shinonome/dots-hyprland
adjust media control colors
This commit is contained in:
@@ -14,6 +14,57 @@ Singleton {
|
|||||||
property QtObject sizes
|
property QtObject sizes
|
||||||
property string syntaxHighlightingTheme
|
property string syntaxHighlightingTheme
|
||||||
|
|
||||||
|
function colorWithHueOf(color1, color2) {
|
||||||
|
// Convert both colors to HSV
|
||||||
|
var c1 = Qt.color(color1);
|
||||||
|
var c2 = Qt.color(color2);
|
||||||
|
|
||||||
|
// Helper to convert RGB to HSV
|
||||||
|
function rgb2hsv(c) {
|
||||||
|
var r = c.r, g = c.g, b = c.b;
|
||||||
|
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||||
|
var h, s, v = max;
|
||||||
|
var d = max - min;
|
||||||
|
s = max === 0 ? 0 : d / max;
|
||||||
|
if (max === min) {
|
||||||
|
h = 0;
|
||||||
|
} else {
|
||||||
|
switch (max) {
|
||||||
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||||
|
case g: h = (b - r) / d + 2; break;
|
||||||
|
case b: h = (r - g) / d + 4; break;
|
||||||
|
}
|
||||||
|
h /= 6;
|
||||||
|
}
|
||||||
|
return {h: h, s: s, v: v, a: c.a};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to convert HSV to RGB
|
||||||
|
function hsv2rgb(h, s, v, a) {
|
||||||
|
var r, g, b;
|
||||||
|
var i = Math.floor(h * 6);
|
||||||
|
var f = h * 6 - i;
|
||||||
|
var p = v * (1 - s);
|
||||||
|
var q = v * (1 - f * s);
|
||||||
|
var t = v * (1 - (1 - f) * s);
|
||||||
|
switch(i % 6){
|
||||||
|
case 0: r = v, g = t, b = p; break;
|
||||||
|
case 1: r = q, g = v, b = p; break;
|
||||||
|
case 2: r = p, g = v, b = t; break;
|
||||||
|
case 3: r = p, g = q, b = v; break;
|
||||||
|
case 4: r = t, g = p, b = v; break;
|
||||||
|
case 5: r = v, g = p, b = q; break;
|
||||||
|
}
|
||||||
|
return Qt.rgba(r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hsv1 = rgb2hsv(c1);
|
||||||
|
var hsv2 = rgb2hsv(c2);
|
||||||
|
|
||||||
|
// Use hue from color2, saturation/value/alpha from color1
|
||||||
|
return hsv2rgb(hsv2.h, hsv1.s, hsv1.v, hsv1.a);
|
||||||
|
}
|
||||||
|
|
||||||
function mix(color1, color2, percentage) {
|
function mix(color1, color2, percentage) {
|
||||||
var c1 = Qt.color(color1);
|
var c1 = Qt.color(color1);
|
||||||
var c2 = Qt.color(color2);
|
var c2 = Qt.color(color2);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ ProgressBar {
|
|||||||
property real valueBarWidth: 120
|
property real valueBarWidth: 120
|
||||||
property real valueBarHeight: 4
|
property real valueBarHeight: 4
|
||||||
property real valueBarGap: 4
|
property real valueBarGap: 4
|
||||||
|
property color highlightColor: Appearance.m3colors.m3primary
|
||||||
|
property color trackColor: Appearance.m3colors.m3secondaryContainer
|
||||||
|
|
||||||
Behavior on value {
|
Behavior on value {
|
||||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||||
@@ -34,21 +36,21 @@ ProgressBar {
|
|||||||
width: root.visualPosition * parent.width
|
width: root.visualPosition * parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
color: Appearance.m3colors.m3primary
|
color: root.highlightColor
|
||||||
}
|
}
|
||||||
Rectangle { // Right remaining part fill
|
Rectangle { // Right remaining part fill
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
width: (1 - root.visualPosition) * parent.width - valueBarGap
|
width: (1 - root.visualPosition) * parent.width - valueBarGap
|
||||||
height: parent.height
|
height: parent.height
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
color: Appearance.m3colors.m3secondaryContainer
|
color: root.trackColor
|
||||||
}
|
}
|
||||||
Rectangle { // Stop point
|
Rectangle { // Stop point
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
width: valueBarGap
|
width: valueBarGap
|
||||||
height: valueBarGap
|
height: valueBarGap
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
color: Appearance.m3colors.m3primary
|
color: root.highlightColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,17 +27,17 @@ Scope {
|
|||||||
property real artRounding: Appearance.rounding.verysmall
|
property real artRounding: Appearance.rounding.verysmall
|
||||||
property string baseCoverArtDir: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/coverart`)
|
property string baseCoverArtDir: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/coverart`)
|
||||||
|
|
||||||
// property bool hasPlasmaIntegration: true
|
property bool hasPlasmaIntegration: false
|
||||||
function isRealPlayer(player) {
|
function isRealPlayer(player) {
|
||||||
// return true
|
// return true
|
||||||
return (
|
return (
|
||||||
// Remove unecessary native buses from browsers if there's plasma integration
|
// Remove unecessary native buses from browsers if there's plasma integration
|
||||||
// !(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.firefox')) &&
|
!(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.firefox')) &&
|
||||||
// !(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.chromium')) &&
|
!(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.chromium')) &&
|
||||||
// playerctld just copies other buses and we don't need duplicates
|
// playerctld just copies other buses and we don't need duplicates
|
||||||
!player.dbusName?.startsWith('org.mpris.MediaPlayer2.playerctld') &&
|
!player.dbusName?.startsWith('org.mpris.MediaPlayer2.playerctld') &&
|
||||||
// Non-instance mpd bus
|
// Non-instance mpd bus
|
||||||
!(player.dbusName?.endsWith('.mpd') && !player.busName.endsWith('MediaPlayer2.mpd'))
|
!(player.dbusName?.endsWith('.mpd') && !player.dbusName.endsWith('MediaPlayer2.mpd'))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,9 +82,8 @@ Scope {
|
|||||||
spacing: -Appearance.sizes.elevationMargin // Shadow overlap okay
|
spacing: -Appearance.sizes.elevationMargin // Shadow overlap okay
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: {
|
model: ScriptModel {
|
||||||
// console.log(JSON.stringify(Mpris.players, null, 2))
|
values: Mpris.players.values.filter(player => isRealPlayer(player))
|
||||||
return Mpris.players.values.filter(player => isRealPlayer(player))
|
|
||||||
}
|
}
|
||||||
delegate: PlayerControl {
|
delegate: PlayerControl {
|
||||||
required property MprisPlayer modelData
|
required property MprisPlayer modelData
|
||||||
|
|||||||
@@ -18,39 +18,51 @@ Item { // Player instance
|
|||||||
required property MprisPlayer player
|
required property MprisPlayer player
|
||||||
// property var artUrl: player?.metadata["xesam:url"] || player?.metadata["mpris:artUrl"] || player?.trackArtUrl
|
// property var artUrl: player?.metadata["xesam:url"] || player?.metadata["mpris:artUrl"] || player?.trackArtUrl
|
||||||
property var artUrl: player?.trackArtUrl
|
property var artUrl: player?.trackArtUrl
|
||||||
property string localArt
|
property color artDominantColor: Appearance.m3colors.m3primaryFixed
|
||||||
property color artDominantColor: "#00000000"
|
|
||||||
|
|
||||||
implicitWidth: widgetWidth
|
implicitWidth: widgetWidth
|
||||||
implicitHeight: widgetHeight
|
implicitHeight: widgetHeight
|
||||||
|
|
||||||
|
Timer { // Force update for prevision
|
||||||
|
running: playerController.player?.playbackState == MprisPlaybackState.Playing
|
||||||
|
interval: 1000
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
playerController.player.positionChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onArtUrlChanged: {
|
onArtUrlChanged: {
|
||||||
|
if (playerController.artUrl.length == 0) return;
|
||||||
|
colorQuantizer.targetFile = playerController.artUrl // Yes this binding break is intentional
|
||||||
colorQuantizer.running = true
|
colorQuantizer.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
Process { // Average Color Runner
|
Process { // Average Color Runner
|
||||||
id: colorQuantizer
|
id: colorQuantizer
|
||||||
command: [ "sh", "-c", `magick ${playerController.player.trackArtUrl} -scale 1x1\\! -format '%[fx:int(255*r+.5)],%[fx:int(255*g+.5)],%[fx:int(255*b+.5)]' info: | sed 's/,/\\n/g' | xargs -L 1 printf '%02x' ; echo` ]
|
property string targetFile: playerController.artUrl
|
||||||
|
command: [ "sh", "-c", `magick '${targetFile}' -scale 1x1\\! -format '%[fx:int(255*r+.5)],%[fx:int(255*g+.5)],%[fx:int(255*b+.5)]' info: | sed 's/,/\\n/g' | xargs -L 1 printf '%02x' ; echo` ]
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
onRead: data => {
|
onRead: data => {
|
||||||
|
// console.log("Color quantizer output:", data)
|
||||||
playerController.artDominantColor = "#" + data
|
playerController.artDominantColor = "#" + data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property QtObject blendedColors: QtObject {
|
property QtObject blendedColors: QtObject {
|
||||||
property color colLayer0: Appearance.mix(Appearance.colors.colLayer0, artDominantColor, 0.7)
|
property color colLayer0: Appearance.mix(Appearance.colors.colLayer0, artDominantColor, 0.6)
|
||||||
property color colLayer1: Appearance.mix(Appearance.colors.colLayer1, artDominantColor, 0.5)
|
property color colLayer1: Appearance.mix(Appearance.colors.colLayer1, artDominantColor, 0.5)
|
||||||
property color colOnLayer0: Appearance.mix(Appearance.colors.colOnLayer0, artDominantColor, 0.7)
|
property color colOnLayer0: Appearance.mix(Appearance.colors.colOnLayer0, artDominantColor, 0.7)
|
||||||
property color colOnLayer1: Appearance.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5)
|
property color colOnLayer1: Appearance.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5)
|
||||||
property color colSubtext: Appearance.mix(Appearance.colors.colSubtext, artDominantColor, 0.5)
|
property color colSubtext: Appearance.mix(Appearance.colors.colSubtext, artDominantColor, 0.5)
|
||||||
property color colPrimary: Appearance.mix(Appearance.m3colors.m3primary, artDominantColor, 0.3)
|
property color colPrimary: Appearance.mix(Appearance.colorWithHueOf(Appearance.m3colors.m3primary, artDominantColor), artDominantColor, 0.5)
|
||||||
property color colPrimaryHover: Appearance.mix(Appearance.colors.colPrimaryHover, artDominantColor, 0.3)
|
property color colPrimaryHover: Appearance.mix(Appearance.colorWithHueOf(Appearance.colors.colPrimaryHover, artDominantColor), artDominantColor, 0.3)
|
||||||
property color colPrimaryActive: Appearance.mix(Appearance.colors.colPrimaryActive, artDominantColor, 0.3)
|
property color colPrimaryActive: Appearance.mix(Appearance.colorWithHueOf(Appearance.colors.colPrimaryActive, artDominantColor), artDominantColor, 0.3)
|
||||||
property color colSecondaryContainer: Appearance.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.5)
|
property color colSecondaryContainer: Appearance.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.3)
|
||||||
property color colSecondaryContainerHover: Appearance.mix(Appearance.colors.colSecondaryContainerHover, artDominantColor, 0.3)
|
property color colSecondaryContainerHover: Appearance.mix(Appearance.colors.colSecondaryContainerHover, artDominantColor, 0.3)
|
||||||
property color colSecondaryContainerActive: Appearance.mix(Appearance.colors.colSecondaryContainerActive, artDominantColor, 0.3)
|
property color colSecondaryContainerActive: Appearance.mix(Appearance.colors.colSecondaryContainerActive, artDominantColor, 0.3)
|
||||||
property color colOnPrimary: Appearance.mix(Appearance.colors.colOnPrimary, artDominantColor, 0.5)
|
property color colOnPrimary: Appearance.mix(Appearance.colorWithHueOf(Appearance.m3colors.m3onPrimary, artDominantColor), artDominantColor, 0.5)
|
||||||
property color colOnSecondaryContainer: Appearance.mix(Appearance.m3colors.m3onSecondaryContainer, artDominantColor, 0.2)
|
property color colOnSecondaryContainer: Appearance.mix(Appearance.m3colors.m3onSecondaryContainer, artDominantColor, 0.2)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -62,6 +74,24 @@ Item { // Player instance
|
|||||||
color: blendedColors.colLayer0
|
color: blendedColors.colLayer0
|
||||||
radius: root.popupRounding
|
radius: root.popupRounding
|
||||||
|
|
||||||
|
LinearGradient {
|
||||||
|
anchors.fill: parent
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: background.width
|
||||||
|
height: background.height
|
||||||
|
radius: root.popupRounding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start: Qt.point(0, 0)
|
||||||
|
end: Qt.point(background.width, background.height)
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: Appearance.transparentize(artDominantColor, 0.6) }
|
||||||
|
GradientStop { position: 0.4; color: Appearance.transparentize(artDominantColor, 0.8) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: root.contentPadding
|
anchors.margins: root.contentPadding
|
||||||
@@ -128,35 +158,31 @@ Item { // Player instance
|
|||||||
StyledText {
|
StyledText {
|
||||||
id: trackTime
|
id: trackTime
|
||||||
anchors.bottom: slider.top
|
anchors.bottom: slider.top
|
||||||
anchors.bottomMargin: -4
|
anchors.bottomMargin: 5
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
font.pixelSize: Appearance.font.pixelSize.small
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
color: blendedColors.colSubtext
|
color: blendedColors.colSubtext
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
text: `${StringUtils.friendlyTimeForSeconds(playerController.player?.position)} / ${StringUtils.friendlyTimeForSeconds(playerController.player?.length)}`
|
text: `${StringUtils.friendlyTimeForSeconds(playerController.player?.position)} / ${StringUtils.friendlyTimeForSeconds(playerController.player?.length)}`
|
||||||
}
|
}
|
||||||
StyledSlider {
|
StyledProgressBar {
|
||||||
id: slider
|
id: slider
|
||||||
anchors {
|
anchors {
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
left: parent.left
|
left: parent.left
|
||||||
right: parent.right
|
right: parent.right
|
||||||
bottomMargin: -8
|
bottomMargin: 5
|
||||||
}
|
}
|
||||||
highlightColor: blendedColors.colPrimary
|
highlightColor: blendedColors.colPrimary
|
||||||
trackColor: blendedColors.colSecondaryContainer
|
trackColor: blendedColors.colSecondaryContainer
|
||||||
handleColor: blendedColors.colOnSecondaryContainer
|
|
||||||
scale: 0.6
|
|
||||||
value: playerController.player?.position / playerController.player?.length
|
value: playerController.player?.position / playerController.player?.length
|
||||||
onMoved: playerController.player.position = value * playerController.player.length
|
|
||||||
tooltipContent: StringUtils.friendlyTimeForSeconds(playerController.player?.position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
id: playPauseButton
|
id: playPauseButton
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.bottom: slider.top
|
anchors.bottom: slider.top
|
||||||
anchors.bottomMargin: -1
|
anchors.bottomMargin: 20
|
||||||
implicitWidth: 44
|
implicitWidth: 44
|
||||||
implicitHeight: 44
|
implicitHeight: 44
|
||||||
onClicked: playerController.player.togglePlaying();
|
onClicked: playerController.player.togglePlaying();
|
||||||
@@ -182,7 +208,7 @@ Item { // Player instance
|
|||||||
iconSize: Appearance.font.pixelSize.huge
|
iconSize: Appearance.font.pixelSize.huge
|
||||||
fill: 1
|
fill: 1
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
color: playerController.player?.isPlaying ? Appearance.m3colors.m3onPrimary : Appearance.m3colors.m3onSecondaryContainer
|
color: playerController.player?.isPlaying ? blendedColors.colOnPrimary : blendedColors.colOnSecondaryContainer
|
||||||
text: playerController.player?.isPlaying ? "pause" : "play_arrow"
|
text: playerController.player?.isPlaying ? "pause" : "play_arrow"
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
|
|||||||
Reference in New Issue
Block a user