diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 9bb41432..0f8ae3e9 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -123,9 +123,13 @@ "stream-description": "Geben Sie einen Befehl ein, der kontinuierlich ausgeführt werden soll." }, "dynamic-text": "Dynamischer Text", - "hide-vertical": { - "description": "Wenn aktiviert, wird der Text aus der Befehlsausgabe nicht angezeigt, wenn sich die Leiste in einem vertikalen Layout befindet (links oder rechts).", - "label": "Text in vertikaler Leiste ausblenden" + "max-text-length-horizontal": { + "description": "Maximale Anzahl an Zeichen, die in horizontaler Leiste angezeigt werden (0 zum Ausblenden des Textes)", + "label": "Max. Textlänge (horizontal)" + }, + "max-text-length-vertical": { + "description": "Maximale Anzahl an Zeichen, die in vertikaler Leiste angezeigt werden (0 zum Ausblenden des Textes)", + "label": "Max. Textlänge (vertikal)" }, "icon": { "description": "Symbol aus der Bibliothek auswählen.", @@ -141,6 +145,23 @@ "label": "Mittelklick", "update-text": "Text auf Mittelklick aktualisieren" }, + "wheel": { + "description": "Befehl, der bei Verwendung des Scrollrads ausgeführt wird.\nVerwenden Sie $delta für die Scrollrad-Delta im Befehl", + "label": "Scrollrad", + "update-text": "Anzeigetext beim Scrollen aktualisieren" + }, + "wheel-mode-separate": { + "label": "Separate Scrollrad-Befehle", + "description": "Separate Befehle für Scrollrad hoch und runter aktivieren" + }, + "wheel-up": { + "description": "Befehl, der ausgeführt wird, wenn das Scrollrad hochgescrollt wird.", + "label": "Scrollrad hoch Befehl" + }, + "wheel-down": { + "description": "Befehl, der ausgeführt wird, wenn das Scrollrad heruntergescrollt wird.", + "label": "Scrollrad runter Befehl" + }, "parse-json": { "description": "Die Befehlsausgabe als JSON-Objekt parsen, um Text und Symbol dynamisch festzulegen.", "label": "Ausgabe als JSON parsen" diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index b381f422..07f2aa2e 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -123,9 +123,13 @@ "stream-description": "Enter a command to run continuously." }, "dynamic-text": "Dynamic text", - "hide-vertical": { - "description": "If enabled, the text from the command output will not be shown when the bar is in a vertical layout (left or right).", - "label": "Hide text in vertical bar" + "max-text-length-horizontal": { + "description": "Maximum number of characters to show in horizontal bar (0 to hide text)", + "label": "Max text length (horizontal)" + }, + "max-text-length-vertical": { + "description": "Maximum number of characters to show in vertical bar (0 to hide text)", + "label": "Max text length (vertical)" }, "icon": { "description": "Select an icon from the library.", @@ -141,6 +145,23 @@ "label": "Middle click", "update-text": "Update displayed text on middle-click" }, + "wheel": { + "description": "Command to execute when the scroll wheel is used.\nUse $delta for the scroll wheel delta in the command", + "label": "Scroll wheel", + "update-text": "Update displayed text on scroll" + }, + "wheel-mode-separate": { + "label": "Separate wheel commands", + "description": "Enable separate commands for wheel up and down" + }, + "wheel-up": { + "description": "Command to execute when the scroll wheel is scrolled up.", + "label": "Wheel up command" + }, + "wheel-down": { + "description": "Command to execute when the scroll wheel is scrolled down.", + "label": "Wheel down command" + }, "parse-json": { "description": "Parse the command output as a JSON object to dynamically set text and icon.", "label": "Parse output as JSON" diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index bb8aa996..52ffc128 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -123,9 +123,13 @@ "stream-description": "Introduce un comando para ejecutar continuamente." }, "dynamic-text": "Texto dinámico", - "hide-vertical": { - "description": "Si está activado, el texto de la salida del comando no se mostrará cuando la barra esté en un diseño vertical (izquierda o derecha).", - "label": "Ocultar texto en barra vertical" + "max-text-length-horizontal": { + "description": "Número máximo de caracteres a mostrar en la barra horizontal (0 para ocultar el texto)", + "label": "Longitud máxima de texto (horizontal)" + }, + "max-text-length-vertical": { + "description": "Número máximo de caracteres a mostrar en la barra vertical (0 para ocultar el texto)", + "label": "Longitud máxima de texto (vertical)" }, "icon": { "description": "Selecciona un icono de la biblioteca.", @@ -141,6 +145,23 @@ "label": "Clic medio", "update-text": "Actualizar el texto mostrado al hacer clic con el botón central" }, + "wheel": { + "description": "Comando a ejecutar cuando se usa la rueda de desplazamiento.\nUsa $delta para el delta de la rueda de desplazamiento en el comando", + "label": "Rueda de desplazamiento", + "update-text": "Actualizar texto mostrado al desplazarse" + }, + "wheel-mode-separate": { + "label": "Comandos de rueda separados", + "description": "Habilitar comandos separados para rueda arriba y abajo" + }, + "wheel-up": { + "description": "Comando a ejecutar cuando la rueda de desplazamiento se desplaza hacia arriba.", + "label": "Comando de rueda hacia arriba" + }, + "wheel-down": { + "description": "Comando a ejecutar cuando la rueda de desplazamiento se desplaza hacia abajo.", + "label": "Comando de rueda hacia abajo" + }, "parse-json": { "description": "Analizar la salida del comando como un objeto JSON para establecer dinámicamente el texto y el ícono.", "label": "Analizar salida como JSON" diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 6c3145c4..c8525648 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -123,9 +123,13 @@ "stream-description": "Entrez une commande à exécuter en continu." }, "dynamic-text": "Texte dynamique", - "hide-vertical": { - "description": "Si activé, le texte de la sortie de la commande ne sera pas affiché lorsque la barre est en disposition verticale (gauche ou droite).", - "label": "Masquer le texte dans la barre verticale" + "max-text-length-horizontal": { + "description": "Nombre maximal de caractères à afficher dans la barre horizontale (0 pour masquer le texte)", + "label": "Longueur max du texte (horizontal)" + }, + "max-text-length-vertical": { + "description": "Nombre maximal de caractères à afficher dans la barre verticale (0 pour masquer le texte)", + "label": "Longueur max du texte (vertical)" }, "icon": { "description": "Sélectionnez une icône dans la bibliothèque.", @@ -141,6 +145,23 @@ "label": "Clic milieu", "update-text": "Mettre à jour le texte affiché lors d'un clic molette." }, + "wheel": { + "description": "Commande à exécuter lorsque la molette est utilisée.\nUtilisez $delta pour le delta de la molette dans la commande", + "label": "Molette", + "update-text": "Mettre à jour le texte affiché au défilement" + }, + "wheel-mode-separate": { + "label": "Commandes de molette séparées", + "description": "Activer des commandes séparées pour la molette haut et bas" + }, + "wheel-up": { + "description": "Commande à exécuter lorsque la molette est défilée vers le haut.", + "label": "Commande molette haut" + }, + "wheel-down": { + "description": "Commande à exécuter lorsque la molette est défilée vers le bas.", + "label": "Commande molette bas" + }, "parse-json": { "description": "Analyser la sortie de la commande en tant qu'objet JSON pour définir dynamiquement le texte et l'icône.", "label": "Analyser la sortie en JSON" diff --git a/Assets/Translations/nl.json b/Assets/Translations/nl.json index 2ba5324d..43369044 100644 --- a/Assets/Translations/nl.json +++ b/Assets/Translations/nl.json @@ -123,9 +123,13 @@ "stream-description": "Voer een commando in dat continu wordt uitgevoerd." }, "dynamic-text": "Dynamische tekst", - "hide-vertical": { - "description": "Indien ingeschakeld wordt de tekst uit de commando-uitvoer niet getoond wanneer de balk verticaal is (links of rechts).", - "label": "Tekst verbergen in verticale balk" + "max-text-length-horizontal": { + "description": "Maximaal aantal tekens dat moet worden weergegeven in horizontale balk (0 om tekst te verbergen)", + "label": "Max. tekstlengte (horizontaal)" + }, + "max-text-length-vertical": { + "description": "Maximaal aantal tekens dat moet worden weergegeven in verticale balk (0 om tekst te verbergen)", + "label": "Max. tekstlengte (verticaal)" }, "icon": { "description": "Selecteer een pictogram uit de bibliotheek.", @@ -141,6 +145,23 @@ "label": "Middelste muisklik", "update-text": "Tekst bijwerken bij middelste muisklik" }, + "wheel": { + "description": "Commando om uit te voeren wanneer het scrollwiel wordt gebruikt.\nGebruik $delta voor de scrollwiel-delta in het commando", + "label": "Scrollwiel", + "update-text": "Weergegeven tekst bij scrollen bijwerken" + }, + "wheel-mode-separate": { + "label": "Afzonderlijke scrollwielcommando's", + "description": "Afzonderlijke commando's inschakelen voor scrollwiel omhoog en omlaag" + }, + "wheel-up": { + "description": "Commando om uit te voeren wanneer het scrollwiel omhoog wordt bewogen.", + "label": "Scrollwiel omhoog commando" + }, + "wheel-down": { + "description": "Commando om uit te voeren wanneer het scrollwiel omlaag wordt bewogen.", + "label": "Scrollwiel omlaag commando" + }, "parse-json": { "description": "Interpreteer de commando-uitvoer als JSON-object om tekst en pictogram dynamisch in te stellen.", "label": "Uitvoer als JSON parseren" diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index fe8f7458..d4f24ea0 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -123,9 +123,13 @@ "stream-description": "Insira um comando para executar continuamente." }, "dynamic-text": "Texto dinâmico", - "hide-vertical": { - "description": "Se ativado, o texto da saída do comando não será exibido quando a barra estiver em um layout vertical (esquerda ou direita).", - "label": "Ocultar texto na barra vertical" + "max-text-length-horizontal": { + "description": "Número máximo de caracteres a serem exibidos na barra horizontal (0 para ocultar o texto)", + "label": "Comprimento máximo do texto (horizontal)" + }, + "max-text-length-vertical": { + "description": "Número máximo de caracteres a serem exibidos na barra vertical (0 para ocultar o texto)", + "label": "Comprimento máximo do texto (vertical)" }, "icon": { "description": "Selecione um ícone da biblioteca.", @@ -141,6 +145,23 @@ "label": "Clique do meio", "update-text": "Atualizar texto exibido com clique do meio" }, + "wheel": { + "description": "Comando a executar quando a roda de rolagem é usada.\nUse $delta para o delta da roda de rolagem no comando", + "label": "Roda de rolagem", + "update-text": "Atualizar texto exibido ao rolar" + }, + "wheel-mode-separate": { + "label": "Comandos de roda separados", + "description": "Ativar comandos separados para roda para cima e para baixo" + }, + "wheel-up": { + "description": "Comando a executar quando a roda de rolagem é rolada para cima.", + "label": "Comando de roda para cima" + }, + "wheel-down": { + "description": "Comando a executar quando a roda de rolagem é rolada para baixo.", + "label": "Comando de roda para baixo" + }, "parse-json": { "description": "Analisa a saída do comando como um objeto JSON para definir dinamicamente o texto e o ícone.", "label": "Analisar saída como JSON" diff --git a/Assets/Translations/ru.json b/Assets/Translations/ru.json index 4a5064ed..722a1da0 100644 --- a/Assets/Translations/ru.json +++ b/Assets/Translations/ru.json @@ -123,9 +123,13 @@ "stream-description": "Введите команду для непрерывного выполнения." }, "dynamic-text": "Динамический текст", - "hide-vertical": { - "description": "Если включено, текст из вывода команды не будет отображаться, когда панель находится в вертикальном макете (слева или справа).", - "label": "Скрывать текст в вертикальной панели" + "max-text-length-horizontal": { + "description": "Максимальное количество символов для отображения в горизонтальной панели (0 для скрытия текста)", + "label": "Макс. длина текста (горизонтально)" + }, + "max-text-length-vertical": { + "description": "Максимальное количество символов для отображения в вертикальной панели (0 для скрытия текста)", + "label": "Макс. длина текста (вертикально)" }, "icon": { "description": "Выберите иконку из библиотеки.", @@ -141,6 +145,23 @@ "label": "Клик средней кнопкой", "update-text": "Обновить отображаемый текст по среднему клику" }, + "wheel": { + "description": "Команда для выполнения при использовании колеса прокрутки.\nИспользуйте $delta для дельты колеса прокрутки в команде", + "label": "Колесо прокрутки", + "update-text": "Обновить отображаемый текст при прокрутке" + }, + "wheel-mode-separate": { + "label": "Раздельные команды колеса прокрутки", + "description": "Включить раздельные команды для колеса прокрутки вверх и вниз" + }, + "wheel-up": { + "description": "Команда для выполнения при прокрутке колеса вверх.", + "label": "Команда прокрутки колеса вверх" + }, + "wheel-down": { + "description": "Команда для выполнения при прокрутке колеса вниз.", + "label": "Команда прокрутки колеса вниз" + }, "parse-json": { "description": "Разобрать вывод команды как объект JSON для динамической установки текста и иконки.", "label": "Разобрать вывод как JSON" diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 0f15c1a9..9ea3d368 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -123,9 +123,13 @@ "stream-description": "Sürekli çalıştırılacak bir komut girin." }, "dynamic-text": "Dinamik metin", - "hide-vertical": { - "description": "Etkinleştirilirse, komut çıktısındaki metin, çubuk dikey düzende (sol veya sağ) olduğunda gösterilmeyecektir.", - "label": "Dikey çubukta metni gizle" + "max-text-length-horizontal": { + "description": "Yatay çubukta gösterilecek maksimum karakter sayısı (metni gizlemek için 0)", + "label": "Maks. metin uzunluğu (yatay)" + }, + "max-text-length-vertical": { + "description": "Dikey çubukta gösterilecek maksimum karakter sayısı (metni gizlemek için 0)", + "label": "Maks. metin uzunluğu (dikey)" }, "icon": { "description": "Kütüphaneden bir ikon seçin.", @@ -141,6 +145,23 @@ "label": "Orta tıklama", "update-text": "Orta tıklamayla görüntülenen metni güncelle" }, + "wheel": { + "description": "Kaydırma tekerleği kullanıldığında yürütülecek komut.\nKomutta kaydırma tekerleği deltası için $delta kullanın", + "label": "Kaydırma tekerleği", + "update-text": "Kaydırmada gösterilen metni güncelle" + }, + "wheel-mode-separate": { + "label": "Ayrı kaydırma tekerleği komutları", + "description": "Kaydırma tekerleği yukarı ve aşağı için ayrı komutları etkinleştir" + }, + "wheel-up": { + "description": "Kaydırma tekerleği yukarı kaydırıldığında yürütülecek komut.", + "label": "Kaydırma tekerleği yukarı komutu" + }, + "wheel-down": { + "description": "Kaydırma tekerleği aşağı kaydırıldığında yürütülecek komut.", + "label": "Kaydırma tekerleği aşağı komutu" + }, "parse-json": { "description": "Komut çıktısını metin ve ikon dinamik olarak ayarlamak için bir JSON nesnesi olarak ayrıştırın.", "label": "Çıktıyı JSON olarak ayrıştır" diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index ac2cb1ef..369cdbbf 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -123,9 +123,13 @@ "stream-description": "Введіть команду для безперервного запуску." }, "dynamic-text": "Динамічний текст", - "hide-vertical": { - "description": "Якщо увімкнено, текст з виводу команди не відображатиметься, коли панель знаходиться у вертикальному розташуванні (ліворуч або праворуч).", - "label": "Приховати текст у вертикальній панелі" + "max-text-length-horizontal": { + "description": "Максимальна кількість символів для відображення в горизонтальній панелі (0 щоб приховати текст)", + "label": "Макс. довжина тексту (горизонтально)" + }, + "max-text-length-vertical": { + "description": "Максимальна кількість символів для відображення в вертикальній панелі (0 щоб приховати текст)", + "label": "Макс. довжина тексту (вертикально)" }, "icon": { "description": "Вибрати значок з бібліотеки.", @@ -141,6 +145,23 @@ "label": "Середній клік", "update-text": "Оновити відображуваний текст при натисканні середньою кнопкою миші" }, + "wheel": { + "description": "Команда для виконання при використанні колеса прокрутки.\nВикористовуйте $delta для дельти колеса прокрутки в команді", + "label": "Колесо прокрутки", + "update-text": "Оновити відображуваний текст при прокрутці" + }, + "wheel-mode-separate": { + "label": "Окремі команди колеса прокрутки", + "description": "Увімкнути окремі команди для колеса прокрутки вгору та вниз" + }, + "wheel-up": { + "description": "Команда для виконання при прокрутці колеса вгору.", + "label": "Команда прокрутки колеса вгору" + }, + "wheel-down": { + "description": "Команда для виконання при прокрутці колеса вниз.", + "label": "Команда прокрутки колеса вниз" + }, "parse-json": { "description": "Розбирати виведення команди як JSON-об'єкт для динамічного встановлення тексту та значка.", "label": "Розбирати виведення як JSON" diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index be29c979..d09fb837 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -123,9 +123,13 @@ "stream-description": "输入一个要持续运行的命令。" }, "dynamic-text": "动态文本", - "hide-vertical": { - "description": "如果启用,当栏处于垂直布局(左或右)时,将不显示命令输出的文本。", - "label": "在垂直栏中隐藏文本" + "max-text-length-horizontal": { + "description": "在水平栏中显示的最大字符数(0 为隐藏文本)", + "label": "最大文本长度(水平)" + }, + "max-text-length-vertical": { + "description": "在垂直栏中显示的最大字符数(0 为隐藏文本)", + "label": "最大文本长度(垂直)" }, "icon": { "description": "从库中选择图标。", @@ -141,6 +145,23 @@ "label": "中键点击", "update-text": "鼠标中键点击时更新显示的文本" }, + "wheel": { + "description": "使用滚轮时执行的命令。\n在命令中使用 $delta 表示滚轮增量", + "label": "滚轮", + "update-text": "滚轮滚动时更新显示的文本" + }, + "wheel-mode-separate": { + "label": "分开滚轮命令", + "description": "为滚轮向上和向下启用单独的命令" + }, + "wheel-up": { + "description": "滚轮向上滚动时执行的命令。", + "label": "滚轮向上命令" + }, + "wheel-down": { + "description": "滚轮向下滚动时执行的命令。", + "label": "滚轮向下命令" + }, "parse-json": { "description": "将命令输出解析为 JSON 对象,以动态设置文本和图标。", "label": "将输出解析为 JSON" diff --git a/Modules/Bar/Extras/BarPillVertical.qml b/Modules/Bar/Extras/BarPillVertical.qml index ed6b69e9..d571b7db 100644 --- a/Modules/Bar/Extras/BarPillVertical.qml +++ b/Modules/Bar/Extras/BarPillVertical.qml @@ -51,10 +51,9 @@ Item { // Sizing logic for vertical bars readonly property int buttonSize: Style.capsuleHeight readonly property int pillHeight: buttonSize - readonly property int pillPaddingVertical: 3 * 2 // Very precise adjustment don't replace by Style.margin readonly property int pillOverlap: Math.round(buttonSize * 0.5) - readonly property int maxPillWidth: rotateText ? Math.max(buttonSize, Math.round(textItem.implicitHeight + pillPaddingVertical * 2)) : buttonSize - readonly property int maxPillHeight: rotateText ? Math.max(1, Math.round(textItem.implicitWidth + pillPaddingVertical * 2 + Math.round(iconCircle.height / 4))) : Math.max(1, Math.round(textItem.implicitHeight + pillPaddingVertical * 4)) + readonly property int maxPillWidth: rotateText ? Math.max(buttonSize, Math.round(textItem.implicitHeight + Style.marginM * 2)) : buttonSize + readonly property int maxPillHeight: rotateText ? Math.max(1, Math.round(textItem.implicitWidth + Style.marginM * 2 + Math.round(iconCircle.height / 4))) : Math.max(1, Math.round(textItem.implicitHeight + Style.marginM * 2)) readonly property real iconSize: { switch (root.density) { @@ -132,7 +131,7 @@ Item { id: textItem anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: rotateText ? Math.round(iconCircle.height / 4) : getVerticalCenterOffset() + anchors.verticalCenterOffset: openDownward ? Style.marginXXS : -Style.marginXXS rotation: rotateText ? -90 : 0 text: root.text + root.suffix family: Settings.data.ui.fontFixed @@ -145,11 +144,8 @@ Item { visible: revealed function getVerticalCenterOffset() { - var offset = openDownward ? Math.round(pillPaddingVertical * 0.75) : -Math.round(pillPaddingVertical * 0.75); - if (forceOpen) { - offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS; - } - return offset; + // A small, symmetrical offset to push the text slightly away from the icon's edge. + return openDownward ? Style.marginXS : -Style.marginXS; } } Behavior on width { diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index a2ef18f7..6cd7a702 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -39,15 +39,21 @@ Item { readonly property bool rightClickUpdateText: widgetSettings.rightClickUpdateText ?? widgetMetadata.rightClickUpdateText readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec readonly property bool middleClickUpdateText: widgetSettings.middleClickUpdateText ?? widgetMetadata.middleClickUpdateText + readonly property string wheelExec: widgetSettings.wheelExec || widgetMetadata.wheelExec + readonly property string wheelUpExec: widgetSettings.wheelUpExec || widgetMetadata.wheelUpExec + readonly property string wheelDownExec: widgetSettings.wheelDownExec || widgetMetadata.wheelDownExec + readonly property string wheelMode: widgetSettings.wheelMode || widgetMetadata.wheelMode + readonly property bool wheelUpdateText: widgetSettings.wheelUpdateText ?? widgetMetadata.wheelUpdateText + readonly property bool wheelUpUpdateText: widgetSettings.wheelUpUpdateText ?? widgetMetadata.wheelUpUpdateText + readonly property bool wheelDownUpdateText: widgetSettings.wheelDownUpdateText ?? widgetMetadata.wheelDownUpdateText readonly property string textCommand: widgetSettings.textCommand !== undefined ? widgetSettings.textCommand : (widgetMetadata.textCommand || "") readonly property bool textStream: widgetSettings.textStream !== undefined ? widgetSettings.textStream : (widgetMetadata.textStream || false) readonly property int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000) readonly property string textCollapse: widgetSettings.textCollapse !== undefined ? widgetSettings.textCollapse : (widgetMetadata.textCollapse || "") readonly property bool parseJson: widgetSettings.parseJson !== undefined ? widgetSettings.parseJson : (widgetMetadata.parseJson || false) - readonly property bool hideTextInVerticalBar: widgetSettings.hideTextInVerticalBar !== undefined ? widgetSettings.hideTextInVerticalBar : (widgetMetadata.hideTextInVerticalBar || false) - readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec) - - readonly property bool shouldShowText: !isVerticalBar || !hideTextInVerticalBar + readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec || + (wheelMode === "unified" && wheelExec) || + (wheelMode === "separate" && (wheelUpExec || wheelDownExec))) implicitWidth: pill.width implicitHeight: pill.height @@ -58,9 +64,9 @@ Item { screen: root.screen oppositeDirection: BarService.getPillDirection(root) icon: _dynamicIcon !== "" ? _dynamicIcon : customIcon - text: shouldShowText ? _dynamicText : "" + text: (!isVerticalBar || currentMaxTextLength > 0) ? _dynamicText : "" density: Settings.data.bar.density - rotateText: isVerticalBar && !hideTextInVerticalBar + rotateText: isVerticalBar && currentMaxTextLength > 0 autoHide: false forceOpen: _dynamicText !== "" tooltipText: { @@ -76,6 +82,16 @@ Item { if (middleClickExec !== "") { tooltipLines.push(`Middle click: ${middleClickExec}.`); } + if (wheelMode === "unified" && wheelExec !== "") { + tooltipLines.push(`Wheel: ${wheelExec}.`); + } else if (wheelMode === "separate") { + if (wheelUpExec !== "") { + tooltipLines.push(`Wheel up: ${wheelUpExec}.`); + } + if (wheelDownExec !== "") { + tooltipLines.push(`Wheel down: ${wheelDownExec}.`); + } + } } if (_dynamicTooltip !== "") { @@ -95,6 +111,7 @@ Item { onClicked: root.onClicked() onRightClicked: root.onRightClicked() onMiddleClicked: root.onMiddleClicked() + onWheel: delta => root.onWheel(delta) } // Internal state for dynamic text @@ -102,12 +119,39 @@ Item { property string _dynamicIcon: "" property string _dynamicTooltip: "" + // Maximum length for text display before scrolling (different values for horizontal and vertical) + readonly property var maxTextLength: { + "horizontal": ((widgetSettings && widgetSettings.maxTextLength && widgetSettings.maxTextLength.horizontal !== undefined) ? + widgetSettings.maxTextLength.horizontal : + ((widgetMetadata && widgetMetadata.maxTextLength && widgetMetadata.maxTextLength.horizontal !== undefined) ? + widgetMetadata.maxTextLength.horizontal : + 10)), + "vertical": ((widgetSettings && widgetSettings.maxTextLength && widgetSettings.maxTextLength.vertical !== undefined) ? + widgetSettings.maxTextLength.vertical : + ((widgetMetadata && widgetMetadata.maxTextLength && widgetMetadata.maxTextLength.vertical !== undefined) ? + widgetMetadata.maxTextLength.vertical : + 10)) + } + readonly property int _staticDuration: 6 // How many cycles to stay static at start/end + + // Encapsulated state for scrolling text implementation + property var _scrollState: { + "originalText": "", + "needsScrolling": false, + "offset": 0, + "phase": 0, // 0=static start, 1=scrolling, 2=static end + "phaseCounter": 0 + } + + // Current max text length based on bar orientation + readonly property int currentMaxTextLength: isVerticalBar ? maxTextLength.vertical : maxTextLength.horizontal + // Periodically run the text command (if set) Timer { id: refreshTimer interval: Math.max(250, textIntervalMs) repeat: true - running: shouldShowText && !textStream && textCommand && textCommand.length > 0 + running: (!isVerticalBar || currentMaxTextLength > 0) && !textStream && textCommand && textCommand.length > 0 triggeredOnStart: true onTriggered: root.runTextCommand() } @@ -116,10 +160,58 @@ Item { Timer { id: restartTimer interval: 1000 - running: shouldShowText && textStream && !textProc.running + running: (!isVerticalBar || currentMaxTextLength > 0) && textStream && !textProc.running onTriggered: root.runTextCommand() } + // Timer for scrolling text display + Timer { + id: scrollTimer + interval: 300 + repeat: true + running: false + onTriggered: { + if (_scrollState.needsScrolling && _scrollState.originalText.length > currentMaxTextLength) { + // Traditional marquee with pause at beginning and end + if (_scrollState.phase === 0) { // Static at beginning + _dynamicText = _scrollState.originalText.substring(0, Math.min(currentMaxTextLength, _scrollState.originalText.length)); + _scrollState.phaseCounter++; + if (_scrollState.phaseCounter >= _staticDuration) { + _scrollState.phaseCounter = 0; + _scrollState.phase = 1; // Move to scrolling + } + } else if (_scrollState.phase === 1) { // Scrolling + _scrollState.offset++; + var start = _scrollState.offset; + var end = start + currentMaxTextLength; + + if (start >= _scrollState.originalText.length - currentMaxTextLength) { + // Reached or passed the end, ensure we show the last part + var textEnd = _scrollState.originalText.length; + var textStart = Math.max(0, textEnd - currentMaxTextLength); + _dynamicText = _scrollState.originalText.substring(textStart, textEnd); + _scrollState.phase = 2; // Move to static end phase + _scrollState.phaseCounter = 0; + } else { + _dynamicText = _scrollState.originalText.substring(start, end); + } + } else if (_scrollState.phase === 2) { // Static at end + // Ensure end text is displayed correctly + var textEnd = _scrollState.originalText.length; + var textStart = Math.max(0, textEnd - currentMaxTextLength); + _dynamicText = _scrollState.originalText.substring(textStart, textEnd); + _scrollState.phaseCounter++; + if (_scrollState.phaseCounter >= _staticDuration) { + // Do NOT loop back to start, just stop scrolling + scrollTimer.stop(); + } + } + } else { + scrollTimer.stop(); + } + } + } + SplitParser { id: textStdoutSplit onRead: line => root.parseDynamicContent(line) @@ -162,16 +254,33 @@ Item { let tooltip = parsed.tooltip || ""; if (checkCollapse(text)) { + _scrollState.originalText = ""; _dynamicText = ""; _dynamicIcon = ""; _dynamicTooltip = ""; + _scrollState.needsScrolling = false; + _scrollState.phase = 0; + _scrollState.phaseCounter = 0; return; } - _dynamicText = text; + _scrollState.originalText = text; + _scrollState.needsScrolling = text.length > currentMaxTextLength && currentMaxTextLength > 0; + if (_scrollState.needsScrolling) { + // Start with the beginning of the text + _dynamicText = text.substring(0, currentMaxTextLength); + _scrollState.phase = 0; // Start at phase 0 (static beginning) + _scrollState.phaseCounter = 0; + _scrollState.offset = 0; + scrollTimer.start(); // Start the scrolling timer + } else { + _dynamicText = text; + scrollTimer.stop(); + } _dynamicIcon = icon; _dynamicTooltip = toHtml(tooltip); + _scrollState.offset = 0; return; } catch (e) { Logger.w("CustomButton", `Failed to parse JSON. Content: "${lineToParse}"`); @@ -179,15 +288,32 @@ Item { } if (checkCollapse(contentStr)) { + _scrollState.originalText = ""; _dynamicText = ""; _dynamicIcon = ""; _dynamicTooltip = ""; + _scrollState.needsScrolling = false; + _scrollState.phase = 0; + _scrollState.phaseCounter = 0; return; } - _dynamicText = contentStr; + _scrollState.originalText = contentStr; + _scrollState.needsScrolling = contentStr.length > currentMaxTextLength && currentMaxTextLength > 0; + if (_scrollState.needsScrolling) { + // Start with the beginning of the text + _dynamicText = contentStr.substring(0, currentMaxTextLength); + _scrollState.phase = 0; // Start at phase 0 (static beginning) + _scrollState.phaseCounter = 0; + _scrollState.offset = 0; + scrollTimer.start(); // Start the scrolling timer + } else { + _dynamicText = contentStr; + scrollTimer.stop(); + } _dynamicIcon = ""; _dynamicTooltip = toHtml(contentStr); + _scrollState.offset = 0; } function checkCollapse(text) { @@ -215,8 +341,8 @@ Item { if (leftClickExec) { Quickshell.execDetached(["sh", "-c", leftClickExec]); Logger.i("CustomButton", `Executing command: ${leftClickExec}`); - } else if (!hasExec && !leftClickUpdateText) { - // No script was defined, open settings + } else if (!leftClickUpdateText) { + // No left click script was defined, open settings var settingsPanel = PanelService.getPanel("settingsPanel", screen); settingsPanel.requestedTab = SettingsPanel.Tab.Bar; settingsPanel.open(); @@ -247,17 +373,25 @@ Item { } function toHtml(str) { - const htmlRegex = /<\/?[a-zA-Z][\s\S]*>/; + const htmlTagRegex = /<\/?[a-zA-Z][^>]*>/g; + const placeholders = []; + let i = 0; + const protectedStr = str.replace(htmlTagRegex, tag => { + placeholders.push(tag); + return `___HTML_TAG_${i++}___`; + }); - if (htmlRegex.test(str)) { - return str; - } + let escaped = protectedStr + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(/\r\n|\r|\n/g, "
"); - const escaped = str.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); + escaped = escaped.replace(/___HTML_TAG_(\d+)___/g, (_, index) => placeholders[Number(index)]); - const withBreaks = escaped.replace(/\r\n|\r|\n/g, "
"); - - return withBreaks; + return escaped; } function runTextCommand() { @@ -268,4 +402,81 @@ Item { textProc.command = ["sh", "-lc", textCommand]; textProc.running = true; } + + function onWheel(delta) { + if (wheelMode === "unified" && wheelExec) { + let normalizedDelta = delta > 0 ? 1 : -1; + + let command = wheelExec.replace(/\$delta([+\-*/]\d+)?/g, function(match, operation) { + if (operation) { + try { + let operator = operation.charAt(0); + let operand = parseInt(operation.substring(1)); + + let result; + switch(operator) { + case '+': result = normalizedDelta + operand; break; + case '-': result = normalizedDelta - operand; break; + case '*': result = normalizedDelta * operand; break; + case '/': result = Math.floor(normalizedDelta / operand); break; + default: result = normalizedDelta; + } + + return result.toString(); + } catch (e) { + Logger.w("CustomButton", `Error evaluating expression: ${match}, using normalized value ${normalizedDelta}`); + return normalizedDelta.toString(); + } + } else { + return normalizedDelta.toString(); + } + }); + + Quickshell.execDetached(["sh", "-c", command]) + Logger.i("CustomButton", `Executing command: ${command}`) + } else if (wheelMode === "separate") { + if ((delta > 0 && wheelUpExec) || (delta < 0 && wheelDownExec)) { + let commandExec = delta > 0 ? wheelUpExec : wheelDownExec; + let normalizedDelta = delta > 0 ? 1 : -1; + + let command = commandExec.replace(/\$delta([+\-*/]\d+)?/g, function(match, operation) { + if (operation) { + try { + let operator = operation.charAt(0); + let operand = parseInt(operation.substring(1)); + + let result; + switch(operator) { + case '+': result = normalizedDelta + operand; break; + case '-': result = normalizedDelta - operand; break; + case '*': result = normalizedDelta * operand; break; + case '/': result = Math.floor(normalizedDelta / operand); break; + default: result = normalizedDelta; + } + + return result.toString(); + } catch (e) { + Logger.w("CustomButton", `Error evaluating expression: ${match}, using normalized value ${normalizedDelta}`); + return normalizedDelta.toString(); + } + } else { + return normalizedDelta.toString(); + } + }); + + Quickshell.execDetached(["sh", "-c", command]) + Logger.i("CustomButton", `Executing command: ${command}`) + } + } + + if (!textStream) { + if (wheelMode === "unified" && wheelUpdateText) { + runTextCommand() + } else if (wheelMode === "separate") { + if ((delta > 0 && wheelUpUpdateText) || (delta < 0 && wheelDownUpdateText)) { + runTextCommand() + } + } + } + } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml index dfbebd81..b498784d 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml @@ -16,7 +16,8 @@ ColumnLayout { property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon property bool valueTextStream: widgetData.textStream !== undefined ? widgetData.textStream : widgetMetadata.textStream property bool valueParseJson: widgetData.parseJson !== undefined ? widgetData.parseJson : widgetMetadata.parseJson - property bool valueHideTextInVerticalBar: widgetData.hideTextInVerticalBar !== undefined ? widgetData.hideTextInVerticalBar : widgetMetadata.hideTextInVerticalBar + property int valueMaxTextLengthHorizontal: widgetData?.maxTextLength?.horizontal ?? widgetMetadata?.maxTextLength?.horizontal + property int valueMaxTextLengthVertical: widgetData?.maxTextLength?.vertical ?? widgetMetadata?.maxTextLength?.vertical function saveSettings() { var settings = Object.assign({}, widgetData || {}); @@ -27,11 +28,21 @@ ColumnLayout { settings.rightClickUpdateText = rightClickUpdateText.checked; settings.middleClickExec = middleClickExecInput.text; settings.middleClickUpdateText = middleClickUpdateText.checked; + settings.wheelMode = separateWheelToggle.internalChecked ? "separate" : "unified"; + settings.wheelExec = wheelExecInput.text; + settings.wheelUpExec = wheelUpExecInput.text; + settings.wheelDownExec = wheelDownExecInput.text; + settings.wheelUpdateText = wheelUpdateText.checked; + settings.wheelUpUpdateText = wheelUpUpdateText.checked; + settings.wheelDownUpdateText = wheelDownUpdateText.checked; settings.textCommand = textCommandInput.text; settings.textCollapse = textCollapseInput.text; settings.textStream = valueTextStream; settings.parseJson = valueParseJson; - settings.hideTextInVerticalBar = valueHideTextInVerticalBar; + settings.maxTextLength = { + "horizontal": valueMaxTextLengthHorizontal, + "vertical": valueMaxTextLengthVertical + }; settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10); return settings; } @@ -137,6 +148,104 @@ ColumnLayout { } } + // Wheel command settings + NToggle { + id: separateWheelToggle + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.custom-button.wheel-mode-separate.label", "Separate wheel commands") + description: I18n.tr("bar.widget-settings.custom-button.wheel-mode-separate.description", "Enable separate commands for wheel up and down") + property bool internalChecked: (widgetData?.wheelMode || widgetMetadata?.wheelMode || "unified") === "separate" + checked: internalChecked + onToggled: checked => { + internalChecked = checked + } + } + + ColumnLayout { + Layout.fillWidth: true + Layout.preferredWidth: parent.width + + RowLayout { + id: unifiedWheelLayout + visible: !separateWheelToggle.checked + spacing: Style.marginM + + NTextInput { + id: wheelExecInput + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.custom-button.wheel.label") + description: I18n.tr("bar.widget-settings.custom-button.wheel.description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData?.wheelExec || widgetMetadata?.wheelExec || "" + } + + NToggle { + id: wheelUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(wheelUpdateText, I18n.tr("bar.widget-settings.custom-button.wheel.update-text"), "auto") + onExited: TooltipService.hide() + checked: widgetData?.wheelUpdateText ?? widgetMetadata?.wheelUpdateText + onToggled: isChecked => checked = isChecked + } + } + + ColumnLayout { + id: separatedWheelLayout + Layout.fillWidth: true + visible: separateWheelToggle.checked + + RowLayout { + spacing: Style.marginM + + NTextInput { + id: wheelUpExecInput + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.custom-button.wheel-up.label") + description: I18n.tr("bar.widget-settings.custom-button.wheel-up.description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData?.wheelUpExec || widgetMetadata?.wheelUpExec || "" + } + + NToggle { + id: wheelUpUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(wheelUpUpdateText, I18n.tr("bar.widget-settings.custom-button.wheel.update-text"), "auto") + onExited: TooltipService.hide() + checked: (widgetData?.wheelUpUpdateText !== undefined) ? widgetData.wheelUpUpdateText : (widgetMetadata?.wheelUpUpdateText ?? false) + onToggled: isChecked => checked = isChecked + } + } + + RowLayout { + spacing: Style.marginM + + NTextInput { + id: wheelDownExecInput + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.custom-button.wheel-down.label") + description: I18n.tr("bar.widget-settings.custom-button.wheel-down.description") + placeholderText: I18n.tr("placeholders.enter-command") + text: widgetData?.wheelDownExec || widgetMetadata?.wheelDownExec || "" + } + + NToggle { + id: wheelDownUpdateText + enabled: !valueTextStream + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.bottomMargin: Style.marginS + onEntered: TooltipService.show(wheelDownUpdateText, I18n.tr("bar.widget-settings.custom-button.wheel.update-text"), "auto") + onExited: TooltipService.hide() + checked: (widgetData?.wheelDownUpdateText !== undefined) ? widgetData.wheelDownUpdateText : (widgetMetadata?.wheelDownUpdateText ?? false) + onToggled: isChecked => checked = isChecked + } + } + } + } + NDivider { Layout.fillWidth: true } @@ -145,11 +254,22 @@ ColumnLayout { label: I18n.tr("bar.widget-settings.custom-button.dynamic-text") } - NToggle { - label: I18n.tr("bar.widget-settings.custom-button.hide-vertical.label", "Hide text in vertical bar") - description: I18n.tr("bar.widget-settings.custom-button.hide-vertical.description", "If enabled, the text from the command output will not be shown when the bar is in a vertical layout (left or right).") - checked: valueHideTextInVerticalBar - onToggled: checked => valueHideTextInVerticalBar = checked + NSpinBox { + label: I18n.tr("bar.widget-settings.custom-button.max-text-length-horizontal.label", "Max text length (horizontal)") + description: I18n.tr("bar.widget-settings.custom-button.max-text-length-horizontal.description", "Maximum number of characters to show in horizontal bar (0 to hide text)") + from: 0 + to: 100 + value: valueMaxTextLengthHorizontal + onValueChanged: valueMaxTextLengthHorizontal = value + } + + NSpinBox { + label: I18n.tr("bar.widget-settings.custom-button.max-text-length-vertical.label", "Max text length (vertical)") + description: I18n.tr("bar.widget-settings.custom-button.max-text-length-vertical.description", "Maximum number of characters to show in vertical bar (0 to hide text)") + from: 0 + to: 100 + value: valueMaxTextLengthVertical + onValueChanged: valueMaxTextLengthVertical = value } NToggle { diff --git a/Services/UI/BarWidgetRegistry.qml b/Services/UI/BarWidgetRegistry.qml index 7357fe2a..52fd336a 100644 --- a/Services/UI/BarWidgetRegistry.qml +++ b/Services/UI/BarWidgetRegistry.qml @@ -124,7 +124,17 @@ Singleton { "textIntervalMs": 3000, "textCollapse": "", "parseJson": false, - "hideTextInVerticalBar": false + "wheelExec": "", + "wheelUpExec": "", + "wheelDownExec": "", + "wheelMode": "unified", + "wheelUpdateText": false, + "wheelUpUpdateText": false, + "wheelDownUpdateText": false, + "maxTextLength": { + "horizontal": 10, + "vertical": 10 + } }, "KeyboardLayout": { "allowUserSettings": true,