diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 9470bdca..f104b27c 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -301,6 +301,7 @@ Singleton { property var workspaceNameIcons: ({}) property bool waveProgressEnabled: true property bool scrollTitleEnabled: true + property bool mediaAdaptiveWidthEnabled: true property bool audioVisualizerEnabled: true property string audioScrollMode: "volume" property int audioWheelScrollAmount: 5 diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index c0dcda7d..4036e648 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -140,6 +140,7 @@ var SPEC = { workspaceNameIcons: { def: {} }, waveProgressEnabled: { def: true }, scrollTitleEnabled: { def: true }, + mediaAdaptiveWidthEnabled: { def: true }, audioVisualizerEnabled: { def: true }, audioScrollMode: { def: "volume" }, audioWheelScrollAmount: { def: 5 }, diff --git a/quickshell/Modules/DankBar/Widgets/Media.qml b/quickshell/Modules/DankBar/Widgets/Media.qml index 453e2096..6007427e 100644 --- a/quickshell/Modules/DankBar/Widgets/Media.qml +++ b/quickshell/Modules/DankBar/Widgets/Media.qml @@ -19,7 +19,8 @@ BasePill { readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser property bool compactMode: false property var widgetData: null - readonly property int textWidth: { + readonly property bool adaptiveWidthEnabled: SettingsData.mediaAdaptiveWidthEnabled + readonly property int maxTextWidth: { const size = widgetData?.mediaSize !== undefined ? widgetData.mediaSize : SettingsData.mediaSize; switch (size) { case 0: @@ -36,10 +37,7 @@ BasePill { if (isVerticalOrientation) { return widgetThickness - horizontalPadding * 2; } - const controlsWidth = 20 + Theme.spacingXS + 24 + Theme.spacingXS + 20; - const audioVizWidth = 20; - const contentWidth = audioVizWidth + Theme.spacingXS + controlsWidth; - return contentWidth + (textWidth > 0 ? textWidth + Theme.spacingXS : 0); + return 0; } readonly property int currentContentHeight: { if (!isVerticalOrientation) { @@ -119,7 +117,28 @@ BasePill { content: Component { Item { - implicitWidth: root.playerAvailable ? root.currentContentWidth : 0 + id: contentRoot + readonly property real measuredTextWidth: { + if (!root.playerAvailable || root.maxTextWidth <= 0 || !textContainer.visible) + return 0; + // Preserve the fixed-width text slot even if metadata is briefly empty. + if (!root.adaptiveWidthEnabled) + return root.maxTextWidth; + if (textContainer.displayText.length === 0) + return 0; + const rawWidth = mediaText.contentWidth; + if (!isFinite(rawWidth) || rawWidth <= 0) + return 0; + return Math.min(root.maxTextWidth, Math.ceil(rawWidth)); + } + readonly property int horizontalContentWidth: { + const controlsWidth = 20 + Theme.spacingXS + 24 + Theme.spacingXS + 20; + const audioVizWidth = 20; + const baseWidth = audioVizWidth + Theme.spacingXS + controlsWidth; + return baseWidth + (measuredTextWidth > 0 ? measuredTextWidth + Theme.spacingXS : 0); + } + + implicitWidth: root.playerAvailable ? (root.isVerticalOrientation ? root.currentContentWidth : horizontalContentWidth) : 0 implicitHeight: root.playerAvailable ? root.currentContentHeight : 0 opacity: root.playerAvailable ? 1 : 0 @@ -132,8 +151,9 @@ BasePill { Behavior on implicitWidth { NumberAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing + duration: Theme.mediumDuration + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel } } @@ -269,7 +289,7 @@ BasePill { } anchors.verticalCenter: parent.verticalCenter - width: textWidth + width: contentRoot.measuredTextWidth height: root.widgetThickness visible: { const size = widgetData?.mediaSize !== undefined ? widgetData.mediaSize : SettingsData.mediaSize; @@ -278,50 +298,95 @@ BasePill { clip: true color: "transparent" - StyledText { - id: mediaText - property bool needsScrolling: implicitWidth > textContainer.width && SettingsData.scrollTitleEnabled - property real scrollOffset: 0 - - anchors.verticalCenter: parent.verticalCenter - text: textContainer.displayText - font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) - color: Theme.widgetTextColor - wrapMode: Text.NoWrap - x: needsScrolling ? -scrollOffset : 0 - onTextChanged: { - scrollOffset = 0; - scrollAnimation.restart(); + Behavior on width { + NumberAnimation { + duration: Theme.mediumDuration + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel } + } - SequentialAnimation { - id: scrollAnimation - running: mediaText.needsScrolling && textContainer.visible - loops: Animation.Infinite + Item { + id: textClip + anchors.fill: parent + clip: true - PauseAnimation { - duration: 2000 + StyledText { + id: mediaText + property bool needsScrolling: implicitWidth > textContainer.width && SettingsData.scrollTitleEnabled + property real scrollOffset: 0 + property real textShift: 0 + + anchors.verticalCenter: parent.verticalCenter + text: textContainer.displayText + font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) + color: Theme.widgetTextColor + wrapMode: Text.NoWrap + x: (needsScrolling ? -scrollOffset : 0) + textShift + opacity: 1 + + onTextChanged: { + scrollOffset = 0; + textShift = 0; + scrollAnimation.restart(); + textChangeAnimation.restart(); } - NumberAnimation { - target: mediaText - property: "scrollOffset" - from: 0 - to: mediaText.implicitWidth - textContainer.width + 5 - duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60) - easing.type: Easing.Linear + SequentialAnimation { + id: scrollAnimation + running: mediaText.needsScrolling && textContainer.visible + loops: Animation.Infinite + + PauseAnimation { + duration: 2000 + } + + NumberAnimation { + target: mediaText + property: "scrollOffset" + from: 0 + to: mediaText.implicitWidth - textContainer.width + 5 + duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60) + easing.type: Easing.Linear + } + + PauseAnimation { + duration: 2000 + } + + NumberAnimation { + target: mediaText + property: "scrollOffset" + to: 0 + duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60) + easing.type: Easing.Linear + } } - PauseAnimation { - duration: 2000 - } + SequentialAnimation { + id: textChangeAnimation - NumberAnimation { - target: mediaText - property: "scrollOffset" - to: 0 - duration: Math.max(1000, (mediaText.implicitWidth - textContainer.width + 5) * 60) - easing.type: Easing.Linear + ParallelAnimation { + NumberAnimation { + target: mediaText + property: "opacity" + from: 0.7 + to: 1 + duration: Theme.shortDuration + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel + } + + NumberAnimation { + target: mediaText + property: "textShift" + from: 4 + to: 0 + duration: Theme.shortDuration + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel + } + } } } } diff --git a/quickshell/Modules/Settings/MediaPlayerTab.qml b/quickshell/Modules/Settings/MediaPlayerTab.qml index b1a4a5ae..a844a86f 100644 --- a/quickshell/Modules/Settings/MediaPlayerTab.qml +++ b/quickshell/Modules/Settings/MediaPlayerTab.qml @@ -46,6 +46,13 @@ Item { onToggled: checked => SettingsData.set("audioVisualizerEnabled", checked) } + SettingsToggleRow { + text: I18n.tr("Adaptive Media Width") + description: I18n.tr("Shrink the media widget to fit shorter song titles while still respecting the configured maximum size") + checked: SettingsData.mediaAdaptiveWidthEnabled + onToggled: checked => SettingsData.set("mediaAdaptiveWidthEnabled", checked) + } + SettingsDropdownRow { property var scrollOptsInternal: ["volume", "song", "nothing"] property var scrollOptsDisplay: [I18n.tr("Change Volume", "media scroll wheel option"), I18n.tr("Change Song", "media scroll wheel option"), I18n.tr("Nothing", "media scroll wheel option")]