mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user