mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
219 lines
5.2 KiB
QML
219 lines
5.2 KiB
QML
import QtQuick
|
||
import QtQuick.Effects
|
||
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
|
||
|
||
// Fade controls (fadeExtent: 0.0–0.5, fraction of width that fades)
|
||
property real fadeExtent: 0.1
|
||
property real fadeCornerRadius: 0
|
||
property bool fadeRoundLeftCorners: true
|
||
|
||
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
|
||
|
||
implicitWidth: alwaysMaxWidth ? maxWidth : Math.min(maxWidth, contentWidth)
|
||
implicitHeight: titleText.height
|
||
|
||
layer.enabled: contentWidth > maxWidth
|
||
layer.effect: MultiEffect {
|
||
maskEnabled: true
|
||
maskThresholdMin: 0.5
|
||
maskSpreadAtMin: 1.0
|
||
maskSource: fadeMask
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
// Transparency Fade Rectangle
|
||
Rectangle {
|
||
id: fadeMask
|
||
width: root.width
|
||
height: root.height
|
||
topLeftRadius: fadeRoundLeftCorners ? fadeCornerRadius : 0
|
||
bottomLeftRadius: fadeRoundLeftCorners ? fadeCornerRadius : 0
|
||
topRightRadius: fadeCornerRadius
|
||
bottomRightRadius: fadeCornerRadius
|
||
gradient: Gradient {
|
||
GradientStop {
|
||
position: 0.0
|
||
color: "transparent"
|
||
}
|
||
GradientStop {
|
||
position: fadeExtent
|
||
color: "white"
|
||
}
|
||
GradientStop {
|
||
position: 1 - fadeExtent
|
||
color: "white"
|
||
}
|
||
GradientStop {
|
||
position: 1.0
|
||
color: "transparent"
|
||
}
|
||
orientation: Gradient.Horizontal
|
||
}
|
||
layer.enabled: true
|
||
layer.smooth: true
|
||
opacity: 0
|
||
}
|
||
}
|