Files
noctalia-shell/Widgets/NScrollText.qml
T

240 lines
5.7 KiB
QML

import QtQuick
import QtQuick.Layouts
import qs.Commons
/*
NScrollText {
NText {
pointSize: Style.fontSizeS
// here any NText properties can be used
}
maxWidth: 200
text: "Some long long long text"
scrollMode: NScrollText.ScrollMode.Always
}
*/
Item {
id: root
required property string text
default property Component delegate: NText {
pointSize: Style.fontSizeS
}
property real maxWidth: Infinity
enum ScrollMode {
Never = 0,
Always = 1,
Hover = 2
}
property int scrollMode: NScrollText.ScrollMode.Never
property bool alwaysMaxWidth: false
property bool forcedHover: false
property int cursorShape: Qt.ArrowCursor
property real waitBeforeScrolling: 1000
property real scrollCycleDuration: Math.max(4000, root.text.length * 120)
property real resettingDuration: 300
// gradient controls
property bool showGradients: true
property real gradientWidth: Math.round(12 * Style.uiScaleRatio)
property color gradientColor: Color.mSurfaceVariant
property real cornerRadius: 0
readonly property bool gradientsEnabled: root.showGradients && Settings.data.bar.capsuleOpacity >= 1.0
readonly property real contentWidth: {
if (!titleText.item)
return 0;
const implicit = titleText.item.implicitWidth;
return implicit > 0 ? implicit : titleText.item.width;
}
readonly property real measuredWidth: scrollContainer.width
clip: true
implicitWidth: alwaysMaxWidth ? maxWidth : Math.min(maxWidth, contentWidth)
implicitHeight: titleText.height
enum ScrollState {
None = 0,
Scrolling = 1,
Resetting = 2
}
property int state: NScrollText.ScrollState.None
onTextChanged: {
if (titleText.item)
titleText.item.text = text;
if (loopingText.item)
loopingText.item.text = text;
// reset state
resetState();
}
onMaxWidthChanged: resetState()
onContentWidthChanged: root.updateState()
onForcedHoverChanged: updateState()
function resetState() {
root.state = NScrollText.ScrollState.None;
scrollContainer.x = 0;
scrollTimer.restart();
root.updateState();
}
Timer {
id: scrollTimer
interval: root.waitBeforeScrolling
onTriggered: {
root.state = NScrollText.ScrollState.Scrolling;
root.updateState();
}
}
MouseArea {
id: hoverArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onEntered: root.updateState()
onExited: root.updateState()
cursorShape: root.cursorShape
}
function ensureReset() {
if (state === NScrollText.ScrollState.Scrolling)
state = NScrollText.ScrollState.Resetting;
}
function updateState() {
if (contentWidth <= root.maxWidth || scrollMode === NScrollText.ScrollMode.Never) {
state = NScrollText.ScrollState.None;
return;
}
if (scrollMode === NScrollText.ScrollMode.Always) {
if (hoverArea.containsMouse) {
ensureReset();
} else {
scrollTimer.restart();
}
} else if (scrollMode === NScrollText.ScrollMode.Hover) {
if (hoverArea.containsMouse || forcedHover)
state = NScrollText.ScrollState.Scrolling;
else
ensureReset();
}
}
RowLayout {
id: scrollContainer
height: parent.height
x: 0
spacing: 50
Loader {
id: titleText
sourceComponent: root.delegate
Layout.fillHeight: true
onLoaded: {
this.item.text = root.text;
// Bind height to container to enable vertical centering of overly high text
this.item.height = Qt.binding(() => titleText.height);
}
}
Loader {
id: loopingText
sourceComponent: root.delegate
Layout.fillHeight: true
visible: root.state !== NScrollText.ScrollState.None
onLoaded: {
this.item.text = root.text;
this.item.height = Qt.binding(() => loopingText.height);
}
}
NumberAnimation on x {
running: root.state === NScrollText.ScrollState.Resetting
to: 0
duration: root.resettingDuration
easing.type: Easing.OutQuad
onFinished: {
root.state = NScrollText.ScrollState.None;
root.updateState();
}
}
NumberAnimation on x {
running: root.state === NScrollText.ScrollState.Scrolling
to: -(titleText.width + scrollContainer.spacing)
duration: root.scrollCycleDuration
loops: Animation.Infinite
easing.type: Easing.Linear
}
}
// Fade Gradients
Rectangle {
id: leftGradient
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: root.gradientWidth
z: 2
visible: root.gradientsEnabled && root.contentWidth > root.maxWidth
radius: root.cornerRadius
opacity: scrollContainer.x < -1 ? 1 : 0
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0.0
color: root.gradientColor
}
GradientStop {
position: 1.0
color: "transparent"
}
}
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
}
Rectangle {
id: rightGradient
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
width: root.gradientWidth
z: 2
visible: root.gradientsEnabled && root.contentWidth > root.maxWidth
radius: root.cornerRadius
opacity: 1 // Always show if overflowing as it loops
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0.0
color: "transparent"
}
GradientStop {
position: 1.0
color: root.gradientColor
}
}
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
}
}