media: rebrand to Bento

popouts: instantly set empty input region on exit
This commit is contained in:
bbedward
2026-09-22 13:44:12 -04:00
parent 4eb163538c
commit 17d52d3c88
18 changed files with 133 additions and 93 deletions
+2 -2
View File
@@ -88,7 +88,7 @@ jobs:
## Claude review
<one line verdict>
- <finding, file:line> per inline comment posted this run
Checked: <one line>. Model: claude-opus-5.
Checked: <one line>. Model: claude-opus-5-5.
No findings: "No issues found." plus what was checked.
@@ -102,7 +102,7 @@ jobs:
Review only. Never edit files, commit, push, approve or request changes. Do not build, vet, format or run tests; CI already does that and the tools are not available here. Post each finding once. Keep comments short.
claude_args: |
--model claude-opus-5
--model claude-opus-5-5
--max-turns 120
--allowedTools "Read,Grep,Glob,LS,Task,mcp__github_inline_comment__create_inline_comment,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr comment:*),Bash(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}/comments:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*),Bash(git blame:*)"
--disallowedTools "Edit,Write,MultiEdit,NotebookEdit,WebFetch,WebSearch"
+6 -5
View File
@@ -12,14 +12,15 @@ Singleton {
albumArtBackdrop: true,
albumArtAccent: true,
animatedArt: false,
lyrics: true
lyrics: true,
deviceName: true
})
readonly property string defaultPlayerStyle: "zurvan"
readonly property string defaultPlayerStyle: "bento"
readonly property var playerStyles: [
{
"value": "zurvan",
"text": "Zurvan"
"value": "bento",
"text": "Bento"
},
{
"value": "material",
@@ -28,7 +29,7 @@ Singleton {
]
readonly property var titleFontDefaults: ({
zurvan: "Notable",
bento: "Notable",
material: "ui"
})
+3 -2
View File
@@ -194,8 +194,6 @@ Singleton {
readonly property real mediaCardMargin: Theme.spacingL
readonly property real mediaArtSize: 150
readonly property real mediaArtSizeDash: 336
readonly property real mediaArtLowResRatio: 0.5
readonly property real mediaArtLowResScale: 0.85
readonly property real mediaArtSizeMaterial: 200
readonly property real mediaArtRadius: Theme.cornerRadiusXL
readonly property real mediaArtSoftRadiusRatio: 0.3
@@ -204,12 +202,15 @@ Singleton {
readonly property real mediaPlayWidthRatio: 1.5
readonly property real mediaPlayWidthStep: 0.75
readonly property real mediaTransportPressScale: 1.2
readonly property real mediaDeviceNameWidthRatio: 0.42
readonly property real accentSelectedAlpha: 0.2
readonly property real groupIdleAlpha: 0.1
readonly property int mediaPositionPollInterval: 1000
readonly property int mediaVolumeEchoTimeout: 1500
readonly property int mediaTransitionGraceInterval: 3000
readonly property int mediaPlayerLossGraceInterval: 1500
readonly property real mediaTextScrollSpeed: 40
readonly property int mediaTextScrollStep: 30
readonly property int mediaLyricsRequestTimeout: 16000
readonly property int mediaLyricsLoadingDelay: 300
readonly property real mediaLyricsPositionTolerance: 0.05
+1 -1
View File
@@ -122,7 +122,7 @@ Singleton {
"value": "slanted",
"text": I18n.tr("Slanted", "Album artwork shape")
}
]), choice("titleFont", I18n.tr("Title font"), MediaOptions.defaultTitleFont, Theme.fontChoices), toggle("seekbar", I18n.tr("Show seekbar"), MediaOptions.defaults.seekbar), toggle("waveProgress", I18n.tr("Wave progress bars"), MediaOptions.defaults.waveProgress), toggle("albumArtBackdrop", I18n.tr("Album art backdrop"), MediaOptions.defaults.albumArtBackdrop), toggle("albumArtAccent", I18n.tr("Use album art accent"), MediaOptions.defaults.albumArtAccent), toggle("animatedArt", I18n.tr("Apple Music animated covers"), MediaOptions.defaults.animatedArt, I18n.tr("Sends the playing artist and album name to Apple")), toggle("lyrics", I18n.tr("Lyrics", "Media player lyrics button"), MediaOptions.defaults.lyrics, I18n.tr("Sends the playing track, artist and album name to enabled lyrics providers"))]
]), choice("titleFont", I18n.tr("Title font"), MediaOptions.defaultTitleFont, Theme.fontChoices), toggle("deviceName", I18n.tr("Show device name", "Media player option that puts the audio output device name on its button"), MediaOptions.defaults.deviceName), toggle("seekbar", I18n.tr("Show seekbar"), MediaOptions.defaults.seekbar), toggle("waveProgress", I18n.tr("Wave progress bars"), MediaOptions.defaults.waveProgress), toggle("albumArtBackdrop", I18n.tr("Album art backdrop"), MediaOptions.defaults.albumArtBackdrop), toggle("albumArtAccent", I18n.tr("Use album art accent"), MediaOptions.defaults.albumArtAccent), toggle("animatedArt", I18n.tr("Apple Music animated covers"), MediaOptions.defaults.animatedArt, I18n.tr("Sends the playing artist and album name to Apple")), toggle("lyrics", I18n.tr("Lyrics", "Media player lyrics button"), MediaOptions.defaults.lyrics, I18n.tr("Sends the playing track, artist and album name to enabled lyrics providers"))]
},
{
"id": "wallpaper",
@@ -12,27 +12,25 @@ MediaChromeBase {
readonly property bool compact: width < Theme.smallBreakpoint || halfWidth < headerHeight + controls.implicitHeight
readonly property real halfWidth: Math.max(0, (width - padding * 2 - Theme.spacingXL) / 2)
readonly property real bodyHeight: Math.max(0, height - padding * 2)
// !TODO: expose this variant? playerPaneOpen false gives art beside lyrics, or art alone with hover transport
readonly property bool playerPane: root.player.playerPaneOpen
readonly property bool controlsColumn: playerPane
readonly property bool lyricsColumn: root.player.lyricsOpen && !playerPane
readonly property bool splitPanes: controlsColumn || lyricsColumn
readonly property bool artTransport: !playerPane && !!root.presentation
readonly property real naturalArtSize: Math.max(0, Math.min(artSize, compact ? width / 2 : halfWidth))
readonly property real artworkSize: compact ? naturalArtSize : Math.max(naturalArtSize, Math.min(halfWidth, bodyHeight))
readonly property real naturalArtSize: Math.round(Math.max(0, Math.min(artSize, compact ? width / 2 : halfWidth)))
// Sized from the controls column, never from this item's own height: reading back the height
// the artwork itself sets makes the split settle wherever the current track happened to put it.
readonly property real artworkSize: Math.round(compact ? naturalArtSize : Math.min(halfWidth, Math.max(naturalArtSize, headerHeight + controls.implicitHeight)))
readonly property real columnHeight: compact ? artworkSize : Math.max(artworkSize, paneHeight)
readonly property real paneHeight: Math.max(artworkSize, headerHeight + controls.implicitHeight)
readonly property real paneMinHeight: Math.max(naturalArtSize, headerHeight + controls.implicitHeight)
readonly property real headerHeight: sourceRow.height + Theme.spacingS
readonly property real renderScale: Window.window?.devicePixelRatio ?? Screen.devicePixelRatio
readonly property int artSide: TrackArtService.resolvedArtSide
readonly property bool lowResArt: artSide > 0 && artSide < artworkSize * renderScale * DashMetrics.mediaArtLowResRatio
artSize: DashMetrics.mediaArtSizeDash
surfaceColor: Theme.foregroundColor(Theme.hostSurface)
baseHeight: Math.max(DashMetrics.tabMinHeight, padding * 2 + (compact ? naturalArtSize + Theme.spacingXL + paneMinHeight : Math.max(naturalArtSize, paneMinHeight)))
focusTargets: (viewToggle.visible ? [viewToggle] : []).concat(artTransportLoader.item?.focusTargets ?? [], [playerButton], sourceGroup.focusTargets, transport.focusTargets, seekbar.canSeek && seekBlock.visible ? [seekbar] : [])
focusTargets: (viewToggle.visible ? [viewToggle] : []).concat(artTransportLoader.item?.focusTargets ?? [], [playerButton], sourceGroup.focusTargets, transport.focusTargets, seekbar.canSeek && root.hasSeekbar ? [seekbar] : [])
panelButtons: [playerButton].concat(sourceGroup.panelButtons)
inlineVolume: true
@@ -56,10 +54,8 @@ MediaChromeBase {
Item {
id: art
readonly property real scaledSize: root.artworkSize * (root.lowResArt ? DashMetrics.mediaArtLowResScale : 1)
anchors.centerIn: parent
width: root.compact ? scaledSize : Math.min(root.artworkSize, Math.max(scaledSize, root.headerHeight + controls.implicitHeight))
width: root.artworkSize
height: width
HoverHandler {
@@ -282,6 +278,7 @@ MediaChromeBase {
loop: true
fadeEdges: true
pxPerMs: DashMetrics.mediaTextScrollSpeed / 1000
stepInterval: DashMetrics.mediaTextScrollStep
}
ScrollingText {
@@ -294,6 +291,7 @@ MediaChromeBase {
loop: true
fadeEdges: true
pxPerMs: DashMetrics.mediaTextScrollSpeed / 1000
stepInterval: DashMetrics.mediaTextScrollStep
}
}
}
@@ -327,11 +325,13 @@ MediaChromeBase {
visible: hasControls
}
// Kept in the column for a length-less stream so the pane, and with it the
// artwork it sizes, does not resize when one comes up in the queue.
Item {
id: seekBlock
width: parent.width
height: Theme.buttonHeightXS
visible: root.hasSeekbar
visible: root.seekbarEnabled && !!root.presentation
LayoutMirroring.enabled: false
LayoutMirroring.childrenInherit: true
@@ -339,6 +339,7 @@ MediaChromeBase {
id: elapsed
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
visible: root.hasSeekbar
text: Format.formatDuration(seekbar.value * (root.presentation?.length ?? 0))
reserveText: duration.text.replace(/\d/g, "8")
}
@@ -349,6 +350,7 @@ MediaChromeBase {
anchors.right: duration.left
anchors.margins: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
visible: root.hasSeekbar
player: root.player
}
@@ -356,6 +358,7 @@ MediaChromeBase {
id: duration
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
visible: root.hasSeekbar
text: Format.formatDuration(root.presentation?.length ?? 0)
reserveText: text.replace(/\d/g, "8")
}
@@ -39,7 +39,7 @@ Row {
panelId: "volume"
iconName: root.player.getVolumeIcon()
enabled: root.player.volumeAvailable
tooltipText: I18n.tr("Volume") + ": " + Math.round(root.player.currentVolume * 100) + "%"
tooltipText: (root.player.usePlayerVolume ? I18n.tr("Media volume") : I18n.tr("Volume")) + ": " + Math.round(root.player.currentVolume * 100) + "%"
MediaVolumeWheel {
player: root.player
@@ -50,7 +50,7 @@ Row {
GroupButton {
id: devicesButton
panelId: "devices"
tooltipText: I18n.tr("Devices", "Media player output device picker")
tooltipText: I18n.tr("Devices", "Media player output device picker") + ": " + AudioService.displayName(AudioService.sink)
iconName: AudioService.sinkIcon(AudioService.sink)
MediaSinkWheel {}
@@ -151,7 +151,7 @@ DankBottomSheet {
subtitle: [modelData?.trackTitle, modelData?.trackArtist].filter(Boolean).join(" · ")
selected: modelData === root.player.activePlayer
onActivated: {
MprisController.switchActivePlayer(modelData);
MprisController.setActivePlayer(modelData);
root.player.panel = "";
}
}
@@ -4,6 +4,7 @@ import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.DankDash
import "../../../DankCommon/Common/FocusNavigation.js" as FocusNavigation
Item {
@@ -11,26 +12,13 @@ Item {
required property var player
readonly property var focusTargets: [volumeButton, devicesButton]
readonly property var panelButtons: [volumeButton, devicesButton]
readonly property var focusTargets: [volumeButton, deviceChip, deviceIcon]
readonly property var panelButtons: [volumeButton, deviceChip, deviceIcon]
readonly property string devicesLabel: I18n.tr("Devices", "Media player output device picker")
readonly property string sinkName: AudioService.displayName(AudioService.sink)
readonly property string fittedSinkName: {
const room = devicesButton.maximumLabelWidth;
// advanceWidth() is not reactive, this read re-evaluates once the font resolves
if (labelMetrics.averageCharacterWidth <= 0)
return "";
if (Math.ceil(labelMetrics.advanceWidth(sinkName)) <= room)
return sinkName;
const words = sinkName.split(/\s+/);
while (words.length > 1) {
words.pop();
const shortened = words.join(" ");
if (Math.ceil(labelMetrics.advanceWidth(shortened)) <= room)
return shortened;
}
return "";
}
readonly property string deviceTooltip: devicesLabel + ": " + sinkName
readonly property bool showSinkName: root.player.options?.deviceName ?? MediaOptions.defaults.deviceName
readonly property string volumeLabel: root.player.usePlayerVolume ? I18n.tr("Media volume") : I18n.tr("Volume")
readonly property bool muted: player.usePlayerVolume ? player.currentVolume === 0 : AudioService.sinkSilent
readonly property real volumeRatio: player.maxVolumePercent > 0 ? player.currentVolume * 100 / player.maxVolumePercent : 0
@@ -38,13 +26,6 @@ Item {
Keys.onPressed: event => event.accepted = FocusNavigation.handleHorizontalKey(event, focusTargets, I18n.isRtl)
FontMetrics {
id: labelMetrics
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeMedium
font.weight: Theme.fontWeightMedium
}
DankRingGauge {
id: volumeRing
@@ -72,7 +53,7 @@ Item {
iconName: root.player.getVolumeIcon()
iconColor: root.muted ? Theme.onSurfaceVariant : Theme.onSurface
enabled: root.player.volumeAvailable
tooltipText: I18n.tr("Volume") + ": " + Math.round(root.player.currentVolume * 100) + "%"
tooltipText: root.volumeLabel + ": " + Math.round(root.player.currentVolume * 100) + "%"
onClicked: root.player.togglePanel(panelId)
MediaVolumeWheel {
@@ -83,22 +64,41 @@ Item {
}
DankButton {
id: devicesButton
id: deviceChip
readonly property string panelId: "devices"
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
maximumWidth: Math.max(0, root.width - volumeRing.width - Theme.spacingXS)
text: root.fittedSinkName || root.devicesLabel
visible: root.showSinkName
maximumWidth: Math.round(root.width * DashMetrics.mediaDeviceNameWidthRatio)
text: root.sinkName
iconName: AudioService.sinkIcon(AudioService.sink)
tooltipText: root.fittedSinkName === root.sinkName ? root.devicesLabel : root.sinkName
Accessible.name: root.devicesLabel + ": " + root.sinkName
tooltipText: root.deviceTooltip
Accessible.name: root.deviceTooltip
buttonHeight: Theme.buttonHeightXS
backgroundColor: MediaAccentService.accentSecondaryContainer
textColor: MediaAccentService.onAccentSecondaryContainer
horizontalPadding: Theme.spacingM
onClicked: root.player.togglePanel("devices")
onClicked: root.player.togglePanel(panelId)
MediaSinkWheel {}
}
DankActionButton {
id: deviceIcon
readonly property string panelId: "devices"
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
visible: !root.showSinkName
buttonSize: volumeRing.width
iconName: AudioService.sinkIcon(AudioService.sink)
iconColor: MediaAccentService.onAccentSecondaryContainer
backgroundColor: MediaAccentService.accentSecondaryContainer
tooltipText: root.deviceTooltip
onClicked: root.player.togglePanel(panelId)
MediaSinkWheel {}
}
+30 -4
View File
@@ -45,9 +45,22 @@ Item {
}
readonly property bool volumeAvailable: !!((activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio))
readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
readonly property real reportedVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
readonly property real currentVolume: pendingVolume >= 0 ? pendingVolume : reportedVolume
readonly property real maxVolumePercent: usePlayerVolume ? 100 : AudioService.sinkMaxVolume
// MPRIS volume round-trips through the player, so successive steps would each read a stale
// activePlayer.volume and collapse into one. Drive them from the value we last asked for.
property real pendingVolume: -1
onReportedVolumeChanged: {
if (pendingVolume >= 0 && Math.abs(reportedVolume - pendingVolume) < 0.005)
clearPendingVolume();
}
onUsePlayerVolumeChanged: clearPendingVolume()
onActivePlayerChanged: clearPendingVolume()
implicitWidth: DashMetrics.contentWidthFor(SettingsData.showWeekNumber, DashMetrics.panelColumnsFor(entryId))
implicitHeight: mediaChrome.item?.implicitHeight ?? DashMetrics.tabMinHeight
@@ -93,6 +106,12 @@ Item {
}
}
Timer {
id: pendingVolumeTimer
interval: DashMetrics.mediaVolumeEchoTimeout
onTriggered: root.pendingVolume = -1
}
MediaPresentation {
id: mediaPresentation
player: root
@@ -155,12 +174,19 @@ Item {
return AudioService.sinkVolumeIconName;
}
function clearPendingVolume() {
pendingVolume = -1;
pendingVolumeTimer.stop();
}
function setVolume(ratio) {
if (!volumeAvailable)
return;
const clamped = Math.min(maxVolumePercent / 100, Math.max(0, ratio));
SessionData.suppressOSDTemporarily();
if (usePlayerVolume) {
pendingVolume = clamped;
pendingVolumeTimer.restart();
activePlayer.volume = clamped;
return;
}
@@ -281,12 +307,12 @@ Item {
Loader {
id: mediaChrome
anchors.fill: parent
sourceComponent: root.playerStyle === "material" ? materialChrome : zurvanChrome
sourceComponent: root.playerStyle === "material" ? materialChrome : bentoChrome
}
Component {
id: zurvanChrome
ZurvanChrome {
id: bentoChrome
BentoChrome {
player: root
}
}
@@ -47,6 +47,7 @@ CcSheetDialog {
iconName: "settings"
onClicked: {
root.dismiss();
PopoutService.closeDankDash();
PopoutService.openSettingsWithTab(root.entryId === "media" ? "media_player" : "weather");
}
}
@@ -26,7 +26,7 @@ Card {
readonly property string artUrl: TrackArtService.resolvedArtUrl
readonly property color accent: MediaAccentService.accent
readonly property bool showSeekbar: options.seekbar !== false && !tiny
readonly property bool zurvan: options.playerStyle === "zurvan"
readonly property bool bento: options.playerStyle === "bento"
readonly property bool inlineMetadata: compact && titleLabel.implicitHeight + artistLabel.implicitHeight + trackText.spacing > transport.y - Theme.spacingXS
readonly property var playbackFocusTargets: [playButton, previousButton, nextButton].concat(seekbar.canSeek ? [seekbar] : [])
@@ -167,7 +167,7 @@ Card {
id: transport
readonly property bool stacked: root.narrow && !root.tiny
readonly property bool centered: root.zurvan && !stacked && !root.tiny
readonly property bool centered: root.bento && !stacked && !root.tiny
readonly property bool medium: !root.compact && width >= Theme.buttonHeightM * 4
readonly property real buttonHeight: medium ? Theme.buttonHeightM : Theme.buttonHeightS
readonly property real spacing: root.compact || stacked ? Theme.spacingXS : Theme.spacingS
@@ -194,7 +194,7 @@ Card {
checkable: true
checked: root.playing
iconFilled: false
radius: Theme.buttonRadius(width, height, buttonSize, pressed, !root.zurvan && checked)
radius: Theme.buttonRadius(width, height, buttonSize, pressed, !root.bento && checked)
iconName: root.playing ? "pause" : "play_arrow"
Accessible.name: root.playing ? I18n.tr("Pause") : I18n.tr("Play")
enabled: !!root.activePlayer?.canTogglePlaying
@@ -209,11 +209,11 @@ Card {
anchors.bottom: parent.bottom
size: transport.medium ? "m" : "s"
width: transport.stacked ? (parent.width - transport.spacing) / 2 : (parent.width - playButton.width - transport.spacing * 2) / 2
variant: root.zurvan ? "tonal" : "standard"
round: !root.zurvan
variant: root.bento ? "tonal" : "standard"
round: !root.bento
radius: Theme.buttonRadius(width, height, buttonSize, pressed, round)
containerColor: MediaAccentService.accentSecondaryContainer
contentColor: root.zurvan ? MediaAccentService.onAccentSecondaryContainer : Theme.onSecondaryContainer
contentColor: root.bento ? MediaAccentService.onAccentSecondaryContainer : Theme.onSecondaryContainer
iconName: "skip_previous"
Accessible.name: I18n.tr("Previous")
enabled: !!root.activePlayer?.canGoPrevious || (!!root.activePlayer?.canSeek && root.activePlayer.position > 8)
+4 -1
View File
@@ -279,7 +279,10 @@ Item {
DankButton {
text: I18n.tr("Settings")
iconName: "settings"
onClicked: PopoutService.openSettingsWithTab("weather")
onClicked: {
PopoutService.closeDankDash();
PopoutService.openSettingsWithTab("weather");
}
}
}
}
-7
View File
@@ -367,13 +367,6 @@ Singleton {
_persistIdentity(activePlayer.identity);
}
function switchActivePlayer(player) {
const current = root.activePlayer;
if (current && current !== player && current.canPause)
current.pause();
setActivePlayer(player);
}
function setActivePlayer(player: MprisPlayer): void {
activePlayer = player;
if (player)
+3 -4
View File
@@ -1424,8 +1424,6 @@ Item {
screen: root.surfaceScreen
visible: false
color: "transparent"
readonly property bool closeVisualActive: root.shouldBeVisible || root.isClosing
onVisibleChanged: {
if (!visible) {
root._surfaceFrameReady = false;
@@ -1506,13 +1504,14 @@ Item {
height: root.maskHeight
}
// Empty input region to avoid the animating surface taking pointer events
Item {
id: contentMaskRect
visible: false
x: contentContainer.x - contentContainer.horizontalConnectorExtent - root.inputMargin
y: contentContainer.y - contentContainer.verticalConnectorExtent - root.inputMargin
width: root.connected || contentWindow.closeVisualActive ? root.renderedAlignedWidth + contentContainer.horizontalConnectorExtent * 2 + root.inputMargin * 2 : 0
height: root.connected || contentWindow.closeVisualActive ? root.renderedAlignedHeight + contentContainer.verticalConnectorExtent * 2 + root.inputMargin * 2 : 0
width: root.shouldBeVisible ? root.renderedAlignedWidth + contentContainer.horizontalConnectorExtent * 2 + root.inputMargin * 2 : 0
height: root.shouldBeVisible ? root.renderedAlignedHeight + contentContainer.verticalConnectorExtent * 2 + root.inputMargin * 2 : 0
}
Loader {
+16 -4
View File
@@ -16,6 +16,7 @@ Item {
property real holdStartMs: 2000
property real holdEndMs: 2000
property real pxPerMs: 1 / 60
property int stepInterval: 60
property real overscroll: 5
property bool animateTextChange: false
property bool loop: false
@@ -34,6 +35,7 @@ Item {
property int scrollDirection: 1
property real scrollHoldMs: holdStartMs
property real textShift: 0
property real lastStepMs: 0
function resetScroll() {
scrollOffset = 0;
@@ -67,8 +69,11 @@ Item {
}
onScrollActiveChanged: {
if (!scrollActive)
if (!scrollActive) {
resetScroll();
return;
}
lastStepMs = Date.now();
}
clip: true
@@ -86,10 +91,13 @@ Item {
wrapMode: Text.NoWrap
width: root.needsScrolling ? implicitWidth : Math.min(implicitWidth, root.width)
elide: SettingsData.scrollTitleEnabled ? Text.ElideNone : Text.ElideRight
// Whole-pixel x plus NativeRendering's glyph snapping quantises the offset twice, which
// turns even motion into a 1px/2px stutter. Both go while the text is actually scrolling.
renderType: root.needsScrolling ? Text.QtRendering : resolvedRenderType
x: {
if (root.needsScrolling) {
const offset = Math.min(root.scrollOffset, root.maxScrollOffset);
return Math.round((root.rightToLeft ? root.width - width + offset : -offset) + root.textShift);
return (root.rightToLeft ? root.width - width + offset : -offset) + root.textShift;
}
switch (root.horizontalAlignment) {
case Text.AlignHCenter:
@@ -120,6 +128,7 @@ Item {
font: label.font
color: label.color
wrapMode: Text.NoWrap
renderType: Text.QtRendering
Accessible.ignored: true
}
}
@@ -157,13 +166,16 @@ Item {
// Timer stepping, not NumberAnimation: a running animation commits frames every vsync (#2863).
// When cava frames are already driving renders, scroll steps ride those ticks instead (#2863).
Timer {
interval: 60
interval: root.stepInterval
repeat: true
running: root.scrollActive
onTriggered: {
const now = Date.now();
const elapsed = Math.min(now - root.lastStepMs, root.stepInterval * 4);
root.lastStepMs = now;
if (cavaTickWatch.running)
return;
root.stepScroll(60);
root.stepScroll(elapsed);
}
}
+3 -2
View File
@@ -34,6 +34,7 @@ ShellRoot {
DC.Log.backend = Log;
DC.Host.session = SessionService;
DC.Host.cache = CacheData;
void IconThemeService.ready;
if (entrypoint.runGreeter)
return;
// Build the polkit agent here, outside incubation: first-touching it from a Connections target during DMSShell's async load crashed QQmlConnections::connectSignalsToMethods.
@@ -58,7 +59,7 @@ ShellRoot {
Loader {
id: shellCoreLoader
active: !entrypoint.runGreeter && IconThemeService.ready
active: !entrypoint.runGreeter
asynchronous: true
source: "ShellCore.qml"
onLoaded: dmsShellLoader.setSource("DMSShell.qml", {
@@ -73,7 +74,7 @@ ShellRoot {
Loader {
id: dmsGreeterLoader
active: entrypoint.runGreeter && IconThemeService.ready
active: entrypoint.runGreeter
asynchronous: false
source: "DMSGreeter.qml"
}
+5 -5
View File
@@ -202,7 +202,7 @@ ShellRoot {
SettingsData.reduceMotion = true;
SettingsData.dashOptions = {
media: {
playerStyle: "zurvan",
playerStyle: "bento",
lyrics: true
}
};
@@ -237,7 +237,7 @@ ShellRoot {
check(media.lyrics.lines.length === 5 && media.lyrics.lines[2].x.includes("backing vocal"), "same-time lyrics stay together");
let overlay = media.lyricsFocusTarget;
check(overlay && overlay.activeFocus, "lyrics receives keyboard focus");
check(overlay.width === artwork.width && overlay.height === artwork.height, "Zurvan lyrics stay within the artwork shape");
check(overlay.width === artwork.width && overlay.height === artwork.height, "Bento lyrics stay within the artwork shape");
const openCorners = artCorners(artwork);
check(openCorners === corners, "opening lyrics leaves pixels outside the artwork untouched: " + corners + " -> " + openCorners);
const line = find(overlay, item => item.text === root.longLine && item.truncated !== undefined);
@@ -248,7 +248,7 @@ ShellRoot {
check(line.lineCount === wrappedLineCount, "lyric highlighting preserves line breaks");
source.position = 10;
input.wait(30);
capture("zurvan");
capture("bento");
input.keyClick(Qt.Key_PageDown);
input.wait(30);
check(!overlay.following && input.findChild(overlay, "followPlayback")?.visible, "browsing pauses automatic following");
@@ -413,7 +413,7 @@ ShellRoot {
check(media.lyrics.state === "ready" && !media.lyrics.synced, "embedded lyrics remain available with all providers off");
SettingsData.dashOptions = {
media: {
playerStyle: "zurvan",
playerStyle: "bento",
lyrics: true,
artStyle: "circle"
}
@@ -438,7 +438,7 @@ ShellRoot {
SettingsData.reduceMotion = false;
SettingsData.dashOptions = {
media: {
playerStyle: "zurvan",
playerStyle: "bento",
lyrics: true
}
};
+4 -4
View File
@@ -199,11 +199,11 @@ ShellRoot {
viewport.width = 780;
SettingsData.dashOptions = {
media: {
playerStyle: "zurvan"
playerStyle: "bento"
}
};
input.wait(30);
check(!!find(media, item => item.loop === true), "switching back restores Zurvan");
check(!!find(media, item => item.loop === true), "switching back restores Bento");
media.lyricsOpen = false;
media.playerPaneOpen = false;
input.wait(30);
@@ -215,7 +215,7 @@ ShellRoot {
input.wait(30);
SettingsData.dashOptions = {
media: {
playerStyle: "zurvan",
playerStyle: "bento",
lyrics: false
}
};
@@ -225,7 +225,7 @@ ShellRoot {
check(!!lyricsToggle && !lyricsToggle.visible, "lyrics option gates the button");
SettingsData.dashOptions = {
media: {
playerStyle: "zurvan",
playerStyle: "bento",
lyrics: true
}
};