From 33dd9834636015b416239713aad97aac3c473430 Mon Sep 17 00:00:00 2001 From: ArnoldCordewiner Date: Sat, 5 Sep 2026 21:13:33 +0200 Subject: [PATCH] v1.5.0: bigger spaced euro sign, trend arrows on flip/nixie, two-phase flip cascade (reset to 0 over 2s then new values), fix nixie flicker endurance --- README.md | 22 +++---- bitcoin-price-1.5.0.plasmoid | Bin 0 -> 11826 bytes contents/ui/DisplayFlip.qml | 107 ++++++++++++++++++++++------------- contents/ui/DisplayNixie.qml | 50 ++++++++++------ contents/ui/main.qml | 14 ++++- metadata.desktop | 2 +- 6 files changed, 126 insertions(+), 69 deletions(-) create mode 100644 bitcoin-price-1.5.0.plasmoid diff --git a/README.md b/README.md index a52e8c5..9ecbf1c 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,21 @@ Three display styles, configurable in the settings dialog: | Style | Description | |---|---| | *Plain text* | Trend-colored text (green/red/grey) | -| *Split-flap board* | Airport board: white digit flaps, white € sign in the default font, cascading flip animation on price changes (configurable duration), trend LED | -| *Nixie tubes* | Retro amber glowing digits in glass tubes with configurable random flicker, white € sign in the default font, trend LED | +| *Split-flap board* | Airport board: white digit flaps, white € sign and a trend arrow in the default font. On a price change all flaps first reset to 0 (swept over ~2 s), then take their new values — the whole cycle spreads over the configured cascade time | +| *Nixie tubes* | Retro amber glowing digits in glass tubes with configurable random flicker, white € sign and a trend arrow in the default font | -In the split-flap and nixie styles the digits keep their display color and the -trend (up/down/unchanged) is shown on a small LED next to the display. -Display style changes apply immediately, without restarting the shell. +In the split-flap and nixie styles a trend arrow sits right of the € sign: +green ▲ when the price went up, red ▼ when it went down. When the price is +unchanged the last arrow stays; on the very first reading (and on API errors) +no arrow is shown. Display style changes apply immediately, without +restarting the shell. -| Situation | Plain text color | Trend LED | +| Situation | Plain text color | Arrow in flip/nixie modes | |---|---|---| -| Price up | green | green | -| Price down | red | red | -| Price unchanged / first reading | grey | grey | -| API failure (network error, 4xx/5xx, rate limit 429) | `--` | grey | +| Price up | green | green ▲ | +| Price down | red | red ▼ | +| Price unchanged | grey | last arrow stays | +| First reading / API failure | grey / `--` | no arrow | Format: `68,969 €` (thousands separators follow the system locale, rounded to whole euros) preceded by the bundled Bitcoin icon in the compact and full diff --git a/bitcoin-price-1.5.0.plasmoid b/bitcoin-price-1.5.0.plasmoid new file mode 100644 index 0000000000000000000000000000000000000000..94db64a6db2c3d580a3df0687a9ef15e7550ac7a GIT binary patch literal 11826 zcmai4V|X3P*50vgHfU_ywryLD-Pmaw+g8)qP8!>2*d&c@eCg@^&gs#;$um1Uf8Mob z%`DA&<)uJCQ2_t|B!HlVM5kqG8;ltU0H6g101yGV02>o017iax16pGfM=K{gdlh9U z0LX}Wy5*1M>;?k>0^I`v0Dk*$j_H!^p@$7V@ebz(*rb4BjH)|p}#W7J+MuhE$CE|Im$

eJ7JqrnU)@~PMPHU=MP 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 E0L-7BhX4Qo literal 0 HcmV?d00001 diff --git a/contents/ui/DisplayFlip.qml b/contents/ui/DisplayFlip.qml index 01599a9..da15c88 100644 --- a/contents/ui/DisplayFlip.qml +++ b/contents/ui/DisplayFlip.qml @@ -1,9 +1,11 @@ /* * Split-flap display mode — airport departure board style. * Only digits are shown as flaps; the euro sign is rendered as plain - * text (default font, white) next to the board, and the trend shows - * on a small LED. Changed characters flip with a cascading animation - * spread over the configured number of seconds. + * text (default font, white) next to the board, followed by a trend + * arrow (green up / red down; unchanged keeps the last arrow). + * On a price change the board first resets every flap to "0" (swept + * over ~2 s), then takes the new values (swept over the remainder of + * the cascade setting). * Pure QtQuick so it can be tested outside the plasmoid shell. */ @@ -14,8 +16,15 @@ Row { // wired by the parent loader property string display: "--" - property color trendColor: "#9e9e9e" - // seconds over which the cascade to a new price is spread + // 1 = up, -1 = down, 0 = unchanged -> keep showing the last arrow + property int trendDir: 0 + property int lastDir: 0 + onTrendDirChanged: { + if (trendDir !== 0) { + lastDir = trendDir; + } + } + // total seconds over which the reset-to-zero + new value cycle spreads property int flapSeconds: 3 spacing: height * 0.06 @@ -31,9 +40,9 @@ Row { return out; } - // UI-only derived values (euro visibility); logic must not rely on these — - // QML gives no ordering guarantee between a change handler and the - // re-evaluation of dependent bindings. + // UI-only derived values (euro/arrow visibility); logic must not rely + // on these — QML gives no ordering guarantee between a change handler + // and the re-evaluation of dependent bindings. readonly property string digitsOnly: digitsOf(display) readonly property string boardText: digitsOnly.length > 0 ? digitsOnly : "--" @@ -58,21 +67,21 @@ Row { var digits = digitsOf(display); var nc = (digits.length > 0 ? digits : "--").split(""); var n = Math.max(chars.length, nc.length); - // spread the changed cells evenly across flapSeconds - var changed = []; + + // Phase 1: every flap resets to "0", swept over ~2 s. + // Phase 2: every flap takes its new value, swept over the + // remainder of the total cascade time. + var total = Math.max(1, flapSeconds) * 1000; + var resetSpan = Math.min(2000, Math.floor(total * 2 / 3)); + var valueStart = resetSpan; + var resetStep = n > 1 ? Math.floor(Math.max(0, resetSpan - flapDuration) / (n - 1)) : 0; + var valueStep = n > 1 ? Math.floor(Math.max(0, total - valueStart - flapDuration) / (n - 1)) : 0; + for (var i = 0; i < n; i++) { - var from = i < chars.length ? chars[i] : ""; + var cell = rep.itemAt(i); var to = i < nc.length ? nc[i] : ""; - if (from !== to) { - changed.push([i, to]); - } - } - var span = Math.max(1, flapSeconds) * 1000 - flapDuration; - var step = changed.length > 1 ? Math.max(0, Math.floor(span / (changed.length - 1))) : 0; - for (var j = 0; j < changed.length; j++) { - var cell = rep.itemAt(changed[j][0]); if (cell) { - cell.flipTo(changed[j][1], j * step); + cell.schedule(to, i * resetStep, valueStart + i * valueStep); } } chars = nc; @@ -92,28 +101,43 @@ Row { border.color: "#26262b" border.width: 1 - // plain property (not a binding) so the flip animation - // controls when the character actually changes + // plain properties (not bindings) so the animations control + // when the character actually changes property string ch: "" - property string pendingCh: "" + property string targetCh: "" + property string animCh: "" Component.onCompleted: { ch = root.chars[index] !== undefined ? root.chars[index] : ""; } - function flipTo(newCh, delay) { + // schedule the two-phase cycle: reset to "0", then target + function schedule(t, resetDelay, valueDelay) { + targetCh = t; + resetTimer.interval = Math.max(0, resetDelay); + resetTimer.restart(); + valueTimer.interval = Math.max(0, valueDelay); + valueTimer.restart(); + } + + function startFlip(newCh) { if (newCh === ch) { return; } - pendingCh = newCh; - delayTimer.interval = Math.max(0, delay); - delayTimer.restart(); + animCh = newCh; + flipAnim.restart(); } Timer { - id: delayTimer + id: resetTimer interval: 0 - onTriggered: flipAnim.start() + onTriggered: cell.startFlip("0") + } + + Timer { + id: valueTimer + interval: 0 + onTriggered: cell.startFlip(cell.targetCh) } transform: Scale { @@ -132,7 +156,7 @@ Row { easing.type: Easing.InQuad } ScriptAction { - script: cell.ch = cell.pendingCh + script: cell.ch = cell.animCh } NumberAnimation { target: scaleTransform @@ -163,6 +187,12 @@ Row { } } + // breathing room between the board and the currency sign + Item { + width: root.height * 0.22 + height: 1 + } + // euro sign in the plain-text font, white Text { anchors.verticalCenter: parent.verticalCenter @@ -170,17 +200,16 @@ Row { visible: root.digitsOnly.length > 0 color: "#ffffff" font.family: Qt.application.font.family - font.pixelSize: root.height * 0.66 + font.pixelSize: root.height * 0.85 } - // trend LED (green/red/grey) - Rectangle { - width: root.height * 0.14 - height: width - radius: width / 2 + // trend arrow: green up / red down; unchanged keeps the last arrow + Text { anchors.verticalCenter: parent.verticalCenter - color: root.trendColor - border.color: "#26262b" - border.width: 1 + text: root.lastDir > 0 ? "\u25B2" : "\u25BC" + visible: root.digitsOnly.length > 0 && root.lastDir !== 0 + color: root.lastDir > 0 ? "#2ecc71" : "#e74c3c" + font.family: Qt.application.font.family + font.pixelSize: root.height * 0.5 } } diff --git a/contents/ui/DisplayNixie.qml b/contents/ui/DisplayNixie.qml index fd7c112..328cc79 100644 --- a/contents/ui/DisplayNixie.qml +++ b/contents/ui/DisplayNixie.qml @@ -3,7 +3,8 @@ * Glow is faked with layered text copies, so no QtGraphicalEffects * dependency. Tubes flicker randomly (nixie cathode style); the * flicker intensity is configurable (0 = off, 100 = nervous). - * The euro sign is rendered as plain text (default font, white). + * The euro sign is plain text (default font, white), followed by a + * trend arrow (green up / red down; unchanged keeps the last arrow). * Pure QtQuick: testable outside the plasmoid shell. */ @@ -14,7 +15,14 @@ Row { // wired by the parent loader property string display: "--" - property color trendColor: "#9e9e9e" + // 1 = up, -1 = down, 0 = unchanged -> keep showing the last arrow + property int trendDir: 0 + property int lastDir: 0 + onTrendDirChanged: { + if (trendDir !== 0) { + lastDir = trendDir; + } + } // 0 = no flicker, 100 = frequent and deep flicker property int flickerIntensity: 40 @@ -98,18 +106,21 @@ Row { } } - // nixie flicker: random dropout of the cathode glow + // nixie flicker: random dropout of the cathode glow. + // repeat: true keeps the cycle alive; each trigger reschedules + // the next interval (intensity 40 -> ~4 s between flicks, + // 100 -> ~1.5 s). Timer { id: flickTimer - repeat: false + repeat: true running: root.flickerOn + interval: 1500 onTriggered: { flick.restart(); scheduleNext(); } function scheduleNext() { - // intensity 40 -> ~4 s between flicks, 100 -> ~1.5 s var base = 6000 - root.flickerIntensity * 45; interval = Math.max(700, base * (0.6 + Math.random() * 0.8)); } @@ -137,6 +148,12 @@ Row { } } + // breathing room between the tubes and the currency sign + Item { + width: root.height * 0.22 + height: 1 + } + // euro sign in the plain-text font, white Text { anchors.verticalCenter: parent.verticalCenter @@ -144,7 +161,17 @@ Row { visible: root.digits.length > 0 color: "#ffffff" font.family: Qt.application.font.family - font.pixelSize: root.height * 0.60 + font.pixelSize: root.height * 0.85 + } + + // trend arrow: green up / red down; unchanged keeps the last arrow + Text { + anchors.verticalCenter: parent.verticalCenter + text: root.lastDir > 0 ? "\u25B2" : "\u25BC" + visible: root.digits.length > 0 && root.lastDir !== 0 + color: root.lastDir > 0 ? "#2ecc71" : "#e74c3c" + font.family: Qt.application.font.family + font.pixelSize: root.height * 0.45 } // error / empty state @@ -156,15 +183,4 @@ Row { font.pixelSize: root.glowFont * 0.8 visible: root.digits.length === 0 } - - // trend LED (green/red/grey) - Rectangle { - width: root.height * 0.11 - height: width - radius: width / 2 - anchors.verticalCenter: parent.verticalCenter - color: root.trendColor - border.color: "#4a2e12" - border.width: 1 - } } diff --git a/contents/ui/main.qml b/contents/ui/main.qml index 6a51e46..99d0c62 100644 --- a/contents/ui/main.qml +++ b/contents/ui/main.qml @@ -142,7 +142,12 @@ Item { source: root.modeSource onLoaded: { item.display = Qt.binding(function () { return root.displayText; }); - item.trendColor = Qt.binding(function () { return root.priceColor; }); + if (item.trendColor !== undefined) { + item.trendColor = Qt.binding(function () { return root.priceColor; }); + } + if (item.trendDir !== undefined) { + item.trendDir = Qt.binding(function () { return root.trend; }); + } if (item.flapSeconds !== undefined) { item.flapSeconds = Qt.binding(function () { return root.flapSeconds; }); } @@ -183,7 +188,12 @@ Item { source: root.modeSource onLoaded: { item.display = Qt.binding(function () { return root.displayText; }); - item.trendColor = Qt.binding(function () { return root.priceColor; }); + if (item.trendColor !== undefined) { + item.trendColor = Qt.binding(function () { return root.priceColor; }); + } + if (item.trendDir !== undefined) { + item.trendDir = Qt.binding(function () { return root.trend; }); + } if (item.flapSeconds !== undefined) { item.flapSeconds = Qt.binding(function () { return root.flapSeconds; }); } diff --git a/metadata.desktop b/metadata.desktop index 99193d1..4ec00b8 100644 --- a/metadata.desktop +++ b/metadata.desktop @@ -10,7 +10,7 @@ X-KDE-ServiceTypes=Plasma/Applet X-KDE-PluginInfo-Author=Arnold Cordewiner X-KDE-PluginInfo-Email=hermes@a14r.be X-KDE-PluginInfo-Name=nl.a14r.bitcoinprice -X-KDE-PluginInfo-Version=1.4.0 +X-KDE-PluginInfo-Version=1.5.0 X-KDE-PluginInfo-Website=https://gitea.a14r.be/hermes/plasma-bitcoin-price X-KDE-PluginInfo-Category=Utilities X-KDE-PluginInfo-License=MIT