This commit is contained in:
Ly-sec
2026-01-03 14:59:16 +01:00
21 changed files with 404 additions and 277 deletions
+18 -141
View File
@@ -88,7 +88,7 @@ Item {
}
// Text width (use the measured width)
contentWidth += fullTitleMetrics.contentWidth;
contentWidth += titleContainer.measuredWidth;
// Additional small margin for text
contentWidth += Style.marginXXS * 2;
@@ -156,15 +156,6 @@ Item {
}
}
// Hidden text element to measure full title width
NText {
id: fullTitleMetrics
visible: false
text: windowTitle
pointSize: Style.barFontSize
applyUiScale: false
}
NPopupContextMenu {
id: contextMenu
@@ -248,145 +239,31 @@ Item {
}
}
// Title container with scrolling
Item {
NScrollText {
id: titleContainer
Layout.preferredWidth: {
text: windowTitle
maxWidth: {
// Calculate available width based on other elements
var iconWidth = (showIcon && windowIcon.visible ? (iconSize + Style.marginS) : 0);
var totalMargins = Style.marginXXS * 2;
var availableWidth = mainContainer.width - iconWidth - totalMargins;
return Math.max(20, availableWidth);
}
Layout.maximumWidth: Layout.preferredWidth
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: titleText.height
clip: true
property bool isScrolling: false
property bool isResetting: false
property real textWidth: fullTitleMetrics.contentWidth
property real containerWidth: width
property bool needsScrolling: textWidth > containerWidth
// Timer for "always" mode with delay
Timer {
id: scrollStartTimer
interval: 1000
repeat: false
onTriggered: {
if (scrollingMode === "always" && titleContainer.needsScrolling) {
titleContainer.isScrolling = true;
titleContainer.isResetting = false;
}
}
scrollMode: {
if (scrollingMode === "always")
return NScrollText.ScrollMode.Always;
if (scrollingMode === "hover")
return NScrollText.ScrollMode.Hover;
return NScrollText.ScrollMode.Never;
}
// Update scrolling state based on mode
property var updateScrollingState: function () {
if (scrollingMode === "never") {
isScrolling = false;
isResetting = false;
} else if (scrollingMode === "always") {
if (needsScrolling) {
if (mouseArea.containsMouse) {
isScrolling = false;
isResetting = true;
} else {
scrollStartTimer.restart();
}
} else {
scrollStartTimer.stop();
isScrolling = false;
isResetting = false;
}
} else if (scrollingMode === "hover") {
if (mouseArea.containsMouse && needsScrolling) {
isScrolling = true;
isResetting = false;
} else {
isScrolling = false;
if (needsScrolling) {
isResetting = true;
}
}
}
}
onWidthChanged: updateScrollingState()
Component.onCompleted: updateScrollingState()
// React to hover changes
Connections {
target: mouseArea
function onContainsMouseChanged() {
titleContainer.updateScrollingState();
}
}
// Scrolling content with seamless loop
Item {
id: scrollContainer
height: parent.height
width: childrenRect.width
property real scrollX: 0
x: scrollX
RowLayout {
spacing: 50 // Gap between text copies
NText {
id: titleText
text: windowTitle
pointSize: Style.barFontSize
applyUiScale: false
verticalAlignment: Text.AlignVCenter
color: Color.mOnSurface
onTextChanged: {
if (root.scrollingMode === "always") {
titleContainer.isScrolling = false;
titleContainer.isResetting = false;
scrollContainer.scrollX = 0;
scrollStartTimer.restart();
}
}
}
// Second copy for seamless scrolling
NText {
text: windowTitle
font: titleText.font
pointSize: Style.barFontSize
applyUiScale: false
verticalAlignment: Text.AlignVCenter
color: Color.mOnSurface
visible: titleContainer.needsScrolling && titleContainer.isScrolling
}
}
// Reset animation
NumberAnimation on scrollX {
running: titleContainer.isResetting
to: 0
duration: 300
easing.type: Easing.OutQuad
onFinished: {
titleContainer.isResetting = false;
}
}
// Seamless infinite scroll
NumberAnimation on scrollX {
id: infiniteScroll
running: titleContainer.isScrolling && !titleContainer.isResetting
from: 0
to: -(titleContainer.textWidth + 50)
duration: Math.max(4000, windowTitle.length * 100)
loops: Animation.Infinite
easing.type: Easing.Linear
}
NText {
id: titleText
text: windowTitle
pointSize: Style.barFontSize
applyUiScale: false
font.weight: Style.fontWeightMedium
verticalAlignment: Text.AlignVCenter
color: Color.mOnSurface
}
}
}
+43
View File
@@ -130,6 +130,48 @@ Item {
return " ".repeat(currentMaxTextLength);
}
readonly property bool enableColorization: widgetSettings.enableColorization || false
readonly property string colorizeSystemIcon: {
if (widgetSettings.colorizeSystemIcon !== undefined)
return widgetSettings.colorizeSystemIcon;
return widgetMetadata.colorizeSystemIcon !== undefined ? widgetMetadata.colorizeSystemIcon : "none";
}
readonly property bool isColorizing: enableColorization && colorizeSystemIcon !== "none"
readonly property color iconColor: {
if (!isColorizing)
return Color.mOnSurface;
switch (colorizeSystemIcon) {
case "primary":
return Color.mPrimary;
case "secondary":
return Color.mSecondary;
case "tertiary":
return Color.mTertiary;
case "error":
return Color.mError;
default:
return Color.mOnSurface;
}
}
readonly property color iconHoverColor: {
if (!isColorizing)
return Color.mOnHover;
switch (colorizeSystemIcon) {
case "primary":
return Qt.darker(Color.mPrimary, 1.2);
case "secondary":
return Qt.darker(Color.mSecondary, 1.2);
case "tertiary":
return Qt.darker(Color.mTertiary, 1.2);
case "error":
return Qt.darker(Color.mError, 1.2);
default:
return Color.mOnHover;
}
}
implicitWidth: pill.width
implicitHeight: pill.height
@@ -145,6 +187,7 @@ Item {
rotateText: isVerticalBar && currentMaxTextLength > 0
autoHide: false
forceOpen: _pillForceOpen
customTextIconColor: isColorizing ? iconColor : Color.transparent
tooltipText: {
var tooltipLines = [];
+26 -134
View File
@@ -99,6 +99,7 @@ Item {
visible: !shouldHideIdle && (hideMode !== "hidden" || opacity > 0)
opacity: isHidden ? 0.0 : ((hideMode === "transparent" && !hasPlayer) ? 0.0 : 1.0)
property real mainContentWidth: 0
readonly property real contentWidth: {
if (useFixedWidth)
return maxWidth;
@@ -111,14 +112,20 @@ Item {
iconWidth = artSize;
}
var margins = isVertical ? 0 : (Style.marginS * 2);
// Add spacing and text width
var textWidth = 0;
if (titleMetrics.contentWidth > 0) {
textWidth = Style.marginS + titleMetrics.contentWidth + Style.marginXXS * 2;
if (titleContainer.measuredWidth > 0) {
margins += Style.marginS;
textWidth = titleContainer.measuredWidth + Style.marginXXS * 2;
}
var margins = isVertical ? 0 : (Style.marginS * 2);
var total = iconWidth + textWidth + margins;
// calculate the width of all elements except the scrolling text
mainContentWidth = total - textWidth;
return hasPlayer ? Math.min(total, maxWidth) : total;
}
@@ -141,15 +148,6 @@ Item {
}
}
// Hidden text for measurements
NText {
id: titleMetrics
visible: false
text: title
applyUiScale: false
pointSize: Style.barFontSize
}
// Context menu
NPopupContextMenu {
id: contextMenu
@@ -251,7 +249,6 @@ Item {
anchors.fill: parent
anchors.leftMargin: isVertical ? 0 : Style.marginS
anchors.rightMargin: isVertical ? 0 : Style.marginS
clip: true
// Visualizer
Loader {
@@ -335,19 +332,27 @@ Item {
}
// Scrolling title
Item {
NScrollText {
id: titleContainer
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: Style.capsuleHeight
ScrollingText {
anchors.fill: parent
text: title
textColor: hasPlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
fontSize: Style.barFontSize
scrollMode: scrollingMode
needsScroll: titleMetrics.contentWidth > parent.width
text: title
scrollMode: {
if (scrollingMode === "always")
return NScrollText.ScrollMode.Always;
if (scrollingMode === "hover")
return NScrollText.ScrollMode.Hover;
return NScrollText.ScrollMode.Never;
}
cursorShape: hasPlayer ? Qt.PointingHandCursor : Qt.ArrowCursor
maxWidth: root.maxWidth - root.mainContentWidth
NText {
// anchors.fill: parent
color: hasPlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
pointSize: Style.barFontSize
}
}
}
@@ -500,117 +505,4 @@ Item {
ctx.stroke();
}
}
// Scrolling Text Component
component ScrollingText: Item {
id: scrollText
property string text
property color textColor
property real fontSize
property string scrollMode
property bool needsScroll
clip: true
implicitHeight: titleText.height
property bool isScrolling: false
property bool isResetting: false
Timer {
id: scrollTimer
interval: 1000
onTriggered: {
if (scrollMode === "always" && needsScroll) {
scrollText.isScrolling = true;
scrollText.isResetting = false;
}
}
}
MouseArea {
id: hoverArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
cursorShape: hasPlayer ? Qt.PointingHandCursor : Qt.ArrowCursor
}
function updateState() {
if (scrollMode === "never") {
isScrolling = false;
isResetting = false;
} else if (scrollMode === "always") {
if (needsScroll) {
if (hoverArea.containsMouse) {
isScrolling = false;
isResetting = true;
} else {
scrollTimer.restart();
}
}
} else if (scrollMode === "hover") {
isScrolling = hoverArea.containsMouse && needsScroll;
isResetting = !hoverArea.containsMouse && needsScroll;
}
}
onWidthChanged: updateState()
Component.onCompleted: updateState()
Connections {
target: hoverArea
function onContainsMouseChanged() {
scrollText.updateState();
}
}
Item {
id: scrollContainer
y: (parent.height - titleText.contentHeight) / 2
height: titleText.contentHeight
property real scrollX: 0
x: scrollX
Row {
spacing: 50
NText {
id: titleText
text: scrollText.text
color: textColor
pointSize: fontSize
applyUiScale: false
onTextChanged: {
scrollText.isScrolling = false;
scrollText.isResetting = false;
scrollContainer.scrollX = 0;
if (scrollText.needsScroll)
scrollTimer.restart();
}
}
NText {
text: scrollText.text
color: textColor
pointSize: fontSize
applyUiScale: false
visible: scrollText.needsScroll && scrollText.isScrolling
}
}
NumberAnimation on scrollX {
running: scrollText.isResetting
to: 0
duration: 300
easing.type: Easing.OutQuad
onFinished: scrollText.isResetting = false
}
NumberAnimation on scrollX {
running: scrollText.isScrolling && !scrollText.isResetting
from: 0
to: -(titleMetrics.contentWidth + 50)
duration: Math.max(4000, scrollText.text.length * 120)
loops: Animation.Infinite
easing.type: Easing.Linear
}
}
}
}
@@ -20,6 +20,8 @@ ColumnLayout {
property int valueMaxTextLengthVertical: widgetData?.maxTextLength?.vertical ?? widgetMetadata?.maxTextLength?.vertical
property string valueHideMode: (widgetData.hideMode !== undefined) ? widgetData.hideMode : widgetMetadata.hideMode
property bool valueShowIcon: (widgetData.showIcon !== undefined) ? widgetData.showIcon : widgetMetadata.showIcon
property bool valueEnableColorization: widgetData.enableColorization || false
property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon || "none"
function saveSettings() {
var settings = Object.assign({}, widgetData || {});
@@ -48,6 +50,8 @@ ColumnLayout {
"vertical": valueMaxTextLengthVertical
};
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10);
settings.enableColorization = valueEnableColorization;
settings.colorizeSystemIcon = valueColorizeSystemIcon;
return settings;
}
@@ -101,6 +105,43 @@ ColumnLayout {
visible: textCommandInput.text !== ""
}
NToggle {
label: I18n.tr("bar.widget-settings.custom-button.enable-colorization.label")
description: I18n.tr("bar.widget-settings.custom-button.enable-colorization.description")
checked: valueEnableColorization
onToggled: checked => valueEnableColorization = checked
}
NComboBox {
visible: valueEnableColorization
label: I18n.tr("bar.widget-settings.custom-button.color-selection.label")
description: I18n.tr("bar.widget-settings.custom-button.color-selection.description")
model: [
{
"name": I18n.tr("options.colors.none"),
"key": "none"
},
{
"name": I18n.tr("options.colors.primary"),
"key": "primary"
},
{
"name": I18n.tr("options.colors.secondary"),
"key": "secondary"
},
{
"name": I18n.tr("options.colors.tertiary"),
"key": "tertiary"
},
{
"name": I18n.tr("options.colors.error"),
"key": "error"
}
]
currentKey: valueColorizeSystemIcon
onSelected: key => valueColorizeSystemIcon = key
}
RowLayout {
spacing: Style.marginM