222 lines
4.2 KiB
QML
222 lines
4.2 KiB
QML
import QtQuick
|
|
import qs.Config
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property alias anim: marqueeAnim
|
|
property bool animate: false
|
|
property color color: DynamicColors.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 > root.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
|
|
|
|
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: true
|
|
implicitHeight: elideText.implicitHeight
|
|
|
|
Behavior on leftFadeStrength {
|
|
Anim {
|
|
}
|
|
}
|
|
Behavior on rightFadeStrength {
|
|
Anim {
|
|
}
|
|
}
|
|
|
|
onTextChanged: resetMarquee()
|
|
onVisibleChanged: if (!visible)
|
|
resetMarquee()
|
|
onWidthChanged: resetMarquee()
|
|
|
|
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: root.width
|
|
}
|
|
|
|
Item {
|
|
id: marqueeViewport
|
|
|
|
anchors.fill: parent
|
|
clip: true
|
|
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
|
|
text: elideText.text
|
|
}
|
|
|
|
CustomText {
|
|
id: t2
|
|
|
|
animate: root.animate
|
|
animateProp: "opacity"
|
|
color: root.color
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|