From c9f1a80dc2b65aa30709d422855b5e394e656265 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 19 May 2025 00:09:08 +0200 Subject: [PATCH] adjust media control colors --- .../quickshell/modules/common/Appearance.qml | 51 +++++++++++++++ .../common/widgets/StyledProgressBar.qml | 8 ++- .../modules/mediaControls/MediaControls.qml | 13 ++-- .../modules/mediaControls/PlayerControl.qml | 62 +++++++++++++------ 4 files changed, 106 insertions(+), 28 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 6e950b172..be23fc2d9 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -14,6 +14,57 @@ Singleton { property QtObject sizes 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) { var c1 = Qt.color(color1); var c2 = Qt.color(color2); diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index 0a27fca99..7173af88b 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -13,6 +13,8 @@ ProgressBar { property real valueBarWidth: 120 property real valueBarHeight: 4 property real valueBarGap: 4 + property color highlightColor: Appearance.m3colors.m3primary + property color trackColor: Appearance.m3colors.m3secondaryContainer Behavior on value { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) @@ -34,21 +36,21 @@ ProgressBar { width: root.visualPosition * parent.width height: parent.height radius: Appearance.rounding.full - color: Appearance.m3colors.m3primary + color: root.highlightColor } Rectangle { // Right remaining part fill anchors.right: parent.right width: (1 - root.visualPosition) * parent.width - valueBarGap height: parent.height radius: Appearance.rounding.full - color: Appearance.m3colors.m3secondaryContainer + color: root.trackColor } Rectangle { // Stop point anchors.right: parent.right width: valueBarGap height: valueBarGap radius: Appearance.rounding.full - color: Appearance.m3colors.m3primary + color: root.highlightColor } } } \ No newline at end of file diff --git a/.config/quickshell/modules/mediaControls/MediaControls.qml b/.config/quickshell/modules/mediaControls/MediaControls.qml index 6d640b4dd..c5b3b87a2 100644 --- a/.config/quickshell/modules/mediaControls/MediaControls.qml +++ b/.config/quickshell/modules/mediaControls/MediaControls.qml @@ -27,17 +27,17 @@ Scope { property real artRounding: Appearance.rounding.verysmall property string baseCoverArtDir: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/coverart`) - // property bool hasPlasmaIntegration: true + property bool hasPlasmaIntegration: false function isRealPlayer(player) { // return true return ( // Remove unecessary native buses from browsers if there's plasma integration - // !(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.firefox')) && - // !(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.chromium')) && + !(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.firefox')) && + !(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.chromium')) && // playerctld just copies other buses and we don't need duplicates !player.dbusName?.startsWith('org.mpris.MediaPlayer2.playerctld') && // 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 Repeater { - model: { - // console.log(JSON.stringify(Mpris.players, null, 2)) - return Mpris.players.values.filter(player => isRealPlayer(player)) + model: ScriptModel { + values: Mpris.players.values.filter(player => isRealPlayer(player)) } delegate: PlayerControl { required property MprisPlayer modelData diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index 00475a786..37e417ffd 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -18,39 +18,51 @@ Item { // Player instance required property MprisPlayer player // property var artUrl: player?.metadata["xesam:url"] || player?.metadata["mpris:artUrl"] || player?.trackArtUrl property var artUrl: player?.trackArtUrl - property string localArt - property color artDominantColor: "#00000000" + property color artDominantColor: Appearance.m3colors.m3primaryFixed implicitWidth: widgetWidth implicitHeight: widgetHeight + Timer { // Force update for prevision + running: playerController.player?.playbackState == MprisPlaybackState.Playing + interval: 1000 + repeat: true + onTriggered: { + playerController.player.positionChanged() + } + } + onArtUrlChanged: { + if (playerController.artUrl.length == 0) return; + colorQuantizer.targetFile = playerController.artUrl // Yes this binding break is intentional colorQuantizer.running = true } Process { // Average Color Runner 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 { onRead: data => { + // console.log("Color quantizer output:", data) playerController.artDominantColor = "#" + data } } } 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 colOnLayer0: Appearance.mix(Appearance.colors.colOnLayer0, artDominantColor, 0.7) property color colOnLayer1: Appearance.mix(Appearance.colors.colOnLayer1, 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 colPrimaryHover: Appearance.mix(Appearance.colors.colPrimaryHover, artDominantColor, 0.3) - property color colPrimaryActive: Appearance.mix(Appearance.colors.colPrimaryActive, artDominantColor, 0.3) - property color colSecondaryContainer: Appearance.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.5) + property color colPrimary: Appearance.mix(Appearance.colorWithHueOf(Appearance.m3colors.m3primary, artDominantColor), artDominantColor, 0.5) + property color colPrimaryHover: Appearance.mix(Appearance.colorWithHueOf(Appearance.colors.colPrimaryHover, artDominantColor), 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.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 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) } @@ -62,6 +74,24 @@ Item { // Player instance color: blendedColors.colLayer0 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 { anchors.fill: parent anchors.margins: root.contentPadding @@ -128,35 +158,31 @@ Item { // Player instance StyledText { id: trackTime anchors.bottom: slider.top - anchors.bottomMargin: -4 + anchors.bottomMargin: 5 anchors.left: parent.left font.pixelSize: Appearance.font.pixelSize.small color: blendedColors.colSubtext elide: Text.ElideRight text: `${StringUtils.friendlyTimeForSeconds(playerController.player?.position)} / ${StringUtils.friendlyTimeForSeconds(playerController.player?.length)}` } - StyledSlider { + StyledProgressBar { id: slider anchors { bottom: parent.bottom left: parent.left right: parent.right - bottomMargin: -8 + bottomMargin: 5 } highlightColor: blendedColors.colPrimary trackColor: blendedColors.colSecondaryContainer - handleColor: blendedColors.colOnSecondaryContainer - scale: 0.6 value: playerController.player?.position / playerController.player?.length - onMoved: playerController.player.position = value * playerController.player.length - tooltipContent: StringUtils.friendlyTimeForSeconds(playerController.player?.position) } Button { id: playPauseButton anchors.right: parent.right anchors.bottom: slider.top - anchors.bottomMargin: -1 + anchors.bottomMargin: 20 implicitWidth: 44 implicitHeight: 44 onClicked: playerController.player.togglePlaying(); @@ -182,7 +208,7 @@ Item { // Player instance iconSize: Appearance.font.pixelSize.huge fill: 1 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" Behavior on color {