Merge pull request #807 from lonerOrz/feat/custombutton

Enhance custom button
This commit is contained in:
Lemmy
2025-11-21 12:02:57 -05:00
committed by GitHub
14 changed files with 614 additions and 67 deletions
+24 -3
View File
@@ -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"
+24 -3
View File
@@ -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"
+24 -3
View File
@@ -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"
+24 -3
View File
@@ -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"
+24 -3
View File
@@ -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"
+24 -3
View File
@@ -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"
+24 -3
View File
@@ -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"
+24 -3
View File
@@ -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"
+24 -3
View File
@@ -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"
+24 -3
View File
@@ -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"
+5 -9
View File
@@ -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 {
+231 -20
View File
@@ -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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;")
.replace(/\r\n|\r|\n/g, "<br/>");
const escaped = str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
escaped = escaped.replace(/___HTML_TAG_(\d+)___/g, (_, index) => placeholders[Number(index)]);
const withBreaks = escaped.replace(/\r\n|\r|\n/g, "<br/>");
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()
}
}
}
}
}
@@ -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 {
+11 -1
View File
@@ -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,