mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-28 22:53:09 +00:00
MarqueeLabel: Smooth Scrolling
This merge request reworks the MarqueeLabel to add smooth scrolling, giving it a more clean appearance while also improving scrolling when filled when characters of different lengths. Before:  After: 
This commit is contained in:
parent
5511cd73a5
commit
a9ddcf726d
1 changed files with 89 additions and 34 deletions
|
|
@ -1,64 +1,119 @@
|
||||||
// SPDX-FileCopyrightText: 2022 Yari Polla <skilvingr@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Yari Polla <skilvingr@gmail.com>
|
||||||
|
// SPDX-FileCopyrightText: 2025 Micah Stanley <stanleymicah@proton.me>
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Effects
|
||||||
|
|
||||||
import org.kde.kirigami 2.20 as Kirigami
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a simple marquee (flowing) label based on PlasmaComponents Label.
|
* This is a simple marquee (flowing) label based on PlasmaComponents Label.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
PlasmaComponents.Label {
|
OpacityMask {
|
||||||
id: root
|
id: root
|
||||||
|
height: row.height
|
||||||
|
|
||||||
|
// label values
|
||||||
required property string inputText
|
required property string inputText
|
||||||
|
property font font
|
||||||
|
property var textFormat: Text.RichText
|
||||||
|
// properties for the marquee label scroll speed and wait duration
|
||||||
|
readonly property real scrollSpeed: 0.025
|
||||||
|
readonly property int waitDuration: 2000
|
||||||
|
|
||||||
readonly property string filteredText: inputText.replace(/\n/g, ' ') // remove new line characters
|
readonly property string filteredText: inputText.replace(/\n/g, ' ') // remove new line characters
|
||||||
|
readonly property bool charactersOverflowing: txtMeter.advanceWidth > root.width // true when text is overflowing
|
||||||
|
|
||||||
readonly property int interval: 200 // update position every 200 ms
|
// update animation values and text positions whenever the label overflows or changes
|
||||||
readonly property int longDuration: 300
|
onFilteredTextChanged: if (root.charactersOverflowing) { textAnimationLoop.restart() }
|
||||||
readonly property int waitDuration: 900
|
onCharactersOverflowingChanged: if (charactersOverflowing) { row.scrollPosition = 0 }
|
||||||
|
|
||||||
readonly property int charactersOverflow: Math.ceil((txtMeter.advanceWidth - root.width) / (txtMeter.advanceWidth / filteredText.length))
|
Item {
|
||||||
property int step: 0
|
id: rowContaner
|
||||||
|
anchors.fill: parent
|
||||||
|
height: row.height
|
||||||
|
opacity: 0 // we display with the opacity gradient below
|
||||||
|
|
||||||
TextMetrics {
|
// use two identical labels for scrolling so we can give the illusion of infinite scrolling
|
||||||
id: txtMeter
|
RowLayout {
|
||||||
font: root.font
|
id: row
|
||||||
text: filteredText
|
anchors.left: parent.left
|
||||||
}
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
|
||||||
Timer {
|
property real scrollPosition: 0
|
||||||
property bool paused: false
|
|
||||||
|
|
||||||
interval: root.interval
|
transform: [
|
||||||
running: visible && charactersOverflow > 0
|
Translate {
|
||||||
repeat: true
|
x: row.scrollPosition
|
||||||
onTriggered: {
|
|
||||||
if (paused) {
|
|
||||||
if (step != 0) {
|
|
||||||
interval = root.longDuration;
|
|
||||||
step = 0;
|
|
||||||
} else {
|
|
||||||
interval = root.interval;
|
|
||||||
paused = false;
|
|
||||||
}
|
}
|
||||||
} else {
|
]
|
||||||
step = (step + 1) % filteredText.length;
|
|
||||||
if (step === charactersOverflow) {
|
spacing: 32
|
||||||
interval = root.waitDuration;
|
|
||||||
paused = true;
|
PlasmaComponents.Label {
|
||||||
|
id: label
|
||||||
|
font: root.font
|
||||||
|
textFormat: root.textFormat
|
||||||
|
text: filteredText
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
|
||||||
|
TextMetrics {
|
||||||
|
id: txtMeter
|
||||||
|
font: root.font
|
||||||
|
text: filteredText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onRunningChanged: {
|
PlasmaComponents.Label {
|
||||||
if (!running) {
|
// hide this label when the text is not overflowing so the user never sees both labels
|
||||||
step = 0;
|
visible: textAnimationLoop.running
|
||||||
|
font: root.font
|
||||||
|
textFormat: root.textFormat
|
||||||
|
text: filteredText
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
text: filteredText.substring(step, step + filteredText.length - charactersOverflow)
|
// setting the gradient mask source
|
||||||
|
source: rowContaner
|
||||||
|
|
||||||
|
// if the label is overflowing, this animation in a loop smoothly scrolling thought the text
|
||||||
|
SequentialAnimation {
|
||||||
|
id: textAnimationLoop
|
||||||
|
running: root.charactersOverflowing && root.visible
|
||||||
|
onRunningChanged: row.scrollPosition = 0
|
||||||
|
loops: Animation.Infinite
|
||||||
|
PauseAnimation { duration: root.waitDuration }
|
||||||
|
NumberAnimation { target: row; property: "scrollPosition"; from: 0; to: -txtMeter.advanceWidth - row.spacing; duration: (txtMeter.advanceWidth + row.spacing) / root.scrollSpeed }
|
||||||
|
}
|
||||||
|
|
||||||
|
// gradient mask to smoothly fade the ends of the label when it is scrolling
|
||||||
|
maskSource: Rectangle {
|
||||||
|
id: mask
|
||||||
|
width: root.width
|
||||||
|
height: root.height
|
||||||
|
|
||||||
|
property real gradientPct: (Kirigami.Units.gridUnit * 0.35) / root.width
|
||||||
|
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Horizontal
|
||||||
|
|
||||||
|
GradientStop { position: 0; color: row.scrollPosition == 0 || row.scrollPosition < -txtMeter.advanceWidth ? 'white' : 'transparent' } // remove the beginning of the gradient when at the start of the label so the front text is fully visible
|
||||||
|
GradientStop { position: 0 + mask.gradientPct; color: 'white' }
|
||||||
|
GradientStop { position: 1.0 - mask.gradientPct; color: 'white' }
|
||||||
|
GradientStop { position: 1.0; color: 'transparent' }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue