mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23: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: 2025 Micah Stanley <stanleymicah@proton.me>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
/**
|
||||
* This is a simple marquee (flowing) label based on PlasmaComponents Label.
|
||||
*/
|
||||
|
||||
PlasmaComponents.Label {
|
||||
OpacityMask {
|
||||
id: root
|
||||
height: row.height
|
||||
|
||||
// label values
|
||||
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 bool charactersOverflowing: txtMeter.advanceWidth > root.width // true when text is overflowing
|
||||
|
||||
readonly property int interval: 200 // update position every 200 ms
|
||||
readonly property int longDuration: 300
|
||||
readonly property int waitDuration: 900
|
||||
// update animation values and text positions whenever the label overflows or changes
|
||||
onFilteredTextChanged: if (root.charactersOverflowing) { textAnimationLoop.restart() }
|
||||
onCharactersOverflowingChanged: if (charactersOverflowing) { row.scrollPosition = 0 }
|
||||
|
||||
readonly property int charactersOverflow: Math.ceil((txtMeter.advanceWidth - root.width) / (txtMeter.advanceWidth / filteredText.length))
|
||||
property int step: 0
|
||||
Item {
|
||||
id: rowContaner
|
||||
anchors.fill: parent
|
||||
height: row.height
|
||||
opacity: 0 // we display with the opacity gradient below
|
||||
|
||||
TextMetrics {
|
||||
id: txtMeter
|
||||
font: root.font
|
||||
text: filteredText
|
||||
}
|
||||
// use two identical labels for scrolling so we can give the illusion of infinite scrolling
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
Timer {
|
||||
property bool paused: false
|
||||
property real scrollPosition: 0
|
||||
|
||||
interval: root.interval
|
||||
running: visible && charactersOverflow > 0
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (paused) {
|
||||
if (step != 0) {
|
||||
interval = root.longDuration;
|
||||
step = 0;
|
||||
} else {
|
||||
interval = root.interval;
|
||||
paused = false;
|
||||
transform: [
|
||||
Translate {
|
||||
x: row.scrollPosition
|
||||
}
|
||||
} else {
|
||||
step = (step + 1) % filteredText.length;
|
||||
if (step === charactersOverflow) {
|
||||
interval = root.waitDuration;
|
||||
paused = true;
|
||||
]
|
||||
|
||||
spacing: 32
|
||||
|
||||
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: {
|
||||
if (!running) {
|
||||
step = 0;
|
||||
PlasmaComponents.Label {
|
||||
// hide this label when the text is not overflowing so the user never sees both labels
|
||||
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