import QtQuick import ZShell.Config import qs.Services Item { id: root property alias anim: marqueeAnim property bool animate: false property color color: Colors.palette.m3onSurface property int fadeStrengthAnimMs: 180 property real fadeStrengthIdle: 0.0 property real fadeStrengthMoving: 1.0 property alias font: elideText.font property int gap: 40 property alias horizontalAlignment: elideText.horizontalAlignment property bool leftFadeEnabled: false property real leftFadeStrength: overflowing && leftFadeEnabled ? fadeStrengthMoving : fadeStrengthIdle property int leftFadeWidth: 28 property bool marqueeEnabled: true readonly property bool overflowing: metrics.width > content.width property int pauseMs: 1200 property real pixelsPerSecond: 40 property real rightFadeStrength: overflowing ? fadeStrengthMoving : fadeStrengthIdle property int rightFadeWidth: 28 property bool sliding: false property alias text: elideText.text property bool vertical: false function durationForDistance(px): int { return Math.max(1, Math.round(Math.abs(px) / root.pixelsPerSecond * 1000)); } function resetMarquee() { marqueeAnim.stop(); strip.x = 0; root.sliding = false; root.leftFadeEnabled = false; if (root.marqueeEnabled && root.overflowing && root.visible) { marqueeAnim.restart(); } } clip: false implicitHeight: vertical ? elideText.implicitWidth : elideText.implicitHeight implicitWidth: vertical ? elideText.implicitHeight : elideText.implicitWidth Behavior on leftFadeStrength { Anim { } } Behavior on rightFadeStrength { Anim { } } onHeightChanged: resetMarquee() onTextChanged: resetMarquee() onVerticalChanged: resetMarquee() onVisibleChanged: if (!visible) resetMarquee() onWidthChanged: resetMarquee() Item { id: content anchors.centerIn: parent height: root.vertical ? root.width : root.height width: root.vertical ? root.height : root.width transform: Rotation { angle: root.vertical ? -90 : 0 origin.x: content.width / 2 origin.y: content.height / 2 } TextMetrics { id: metrics font: elideText.font text: elideText.text } CustomText { id: elideText anchors.verticalCenter: parent.verticalCenter animate: root.animate animateProp: "scale,opacity" color: root.color elide: Text.ElideNone visible: !root.overflowing width: content.width } Item { id: marqueeViewport anchors.fill: parent clip: false layer.enabled: true visible: root.overflowing layer.effect: OpacityMask { maskSource: rightFadeMask } Item { id: strip anchors.verticalCenter: parent.verticalCenter height: t1.implicitHeight width: t1.width + root.gap + t2.width x: 0 CustomText { id: t1 animate: root.animate animateProp: "opacity" color: root.color font.pointSize: elideText.font.pointSize text: elideText.text } CustomText { id: t2 animate: root.animate animateProp: "opacity" color: root.color font.pointSize: elideText.font.pointSize text: t1.text x: t1.width + root.gap } } SequentialAnimation { id: marqueeAnim running: false onFinished: pauseTimer.restart() ScriptAction { script: { root.sliding = true; root.leftFadeEnabled = true; } } Anim { duration: root.durationForDistance(t1.width) easing.bezierCurve: Easing.Linear easing.type: Easing.Linear from: 0 property: "x" target: strip to: -t1.width } ScriptAction { script: { root.leftFadeEnabled = false; } } Anim { duration: root.durationForDistance(root.gap) easing.bezierCurve: Easing.Linear easing.type: Easing.Linear from: -t1.width property: "x" target: strip to: -(t1.width + root.gap) } ScriptAction { script: { root.sliding = false; strip.x = 0; } } } Timer { id: pauseTimer interval: root.pauseMs repeat: false running: true onTriggered: { if (root.marqueeEnabled) marqueeAnim.start(); } } } Rectangle { id: rightFadeMask readonly property real fadeStartPos: { const w = Math.max(1, width); return Math.max(0, Math.min(1, (w - root.rightFadeWidth) / w)); } readonly property real leftFadeEndPos: { const w = Math.max(1, width); return Math.max(0, Math.min(1, root.leftFadeWidth / w)); } anchors.fill: marqueeViewport layer.enabled: true visible: false gradient: Gradient { orientation: Gradient.Horizontal GradientStop { color: Qt.rgba(1, 1, 1, 1.0 - root.leftFadeStrength) position: 0.0 } GradientStop { color: Qt.rgba(1, 1, 1, 1.0) position: rightFadeMask.leftFadeEndPos } GradientStop { color: Qt.rgba(1, 1, 1, 1.0) position: rightFadeMask.fadeStartPos } GradientStop { color: Qt.rgba(1, 1, 1, 1.0 - root.rightFadeStrength) position: 1.0 } } } } }