From 361a319d94ef1452774b3e2394c8f5fffaca6266 Mon Sep 17 00:00:00 2001 From: ArnoldCordewiner Date: Sat, 5 Sep 2026 20:22:59 +0200 Subject: [PATCH] v1.2.0: add split-flap board and nixie tube display modes with display style config --- README.md | 27 +++++-- bitcoin-price-1.2.0.plasmoid | Bin 0 -> 9655 bytes contents/config/main.xml | 9 +++ contents/ui/DisplayFlip.qml | 144 ++++++++++++++++++++++++++++++++++ contents/ui/DisplayNixie.qml | 138 ++++++++++++++++++++++++++++++++ contents/ui/DisplayText.qml | 27 +++++++ contents/ui/configGeneral.qml | 7 ++ contents/ui/main.qml | 62 +++++++++------ metadata.desktop | 2 +- 9 files changed, 385 insertions(+), 31 deletions(-) create mode 100644 bitcoin-price-1.2.0.plasmoid create mode 100644 contents/ui/DisplayFlip.qml create mode 100644 contents/ui/DisplayNixie.qml create mode 100644 contents/ui/DisplayText.qml diff --git a/README.md b/README.md index 262866a..a6075eb 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,25 @@ KDE Plasma 5 widget that shows the bitcoin price in EUR, fetched from the [CoinGecko API](https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=eur). -## Display +## Display styles -| Situation | Display | +Three display styles, configurable in the settings dialog: + +| Style | Description | |---|---| -| Price up compared to the previous reading | green | -| Price down compared to the previous reading | red | -| Price unchanged (or first reading) | grey | -| API failure (network error, 4xx/5xx, rate limit 429) | `--` | +| *Plain text* | Trend-colored text (green/red/grey) | +| *Split-flap board* | Airport board: white characters on black flaps, cascading flip animation on price changes, trend LED | +| *Nixie tubes* | Retro amber glowing digits in glass tubes (with a soft flicker), trend LED | + +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. + +| Situation | Plain text color | Trend LED | +|---|---|---| +| Price up | green | green | +| Price down | red | red | +| Price unchanged / first reading | grey | grey | +| API failure (network error, 4xx/5xx, rate limit 429) | `--` | grey | Format: `68,969 €` (thousands separators follow the system locale, rounded to whole euros) preceded by the bundled Bitcoin icon in the compact and full @@ -27,13 +38,13 @@ Configurable via right-click on the widget → *Configure Bitcoin Price…* → ## Installation ### Graphical -Double-click `bitcoin-price-1.1.0.plasmoid` in Dolphin (Plasma installs it to +Double-click `bitcoin-price-1.2.0.plasmoid` in Dolphin (Plasma installs it to `~/.local/share/plasma/plasmoids/` automatically). The latest build is attached to the [v1.1.0 release](../../releases/tag/v1.1.0). ### Terminal ```bash -kpackagetool5 --type Plasma/Applet --upgrade bitcoin-price-1.1.0.plasmoid +kpackagetool5 --type Plasma/Applet --upgrade bitcoin-price-1.2.0.plasmoid # Then restart the shell if needed: plasmashell --replace & ``` diff --git a/bitcoin-price-1.2.0.plasmoid b/bitcoin-price-1.2.0.plasmoid new file mode 100644 index 0000000000000000000000000000000000000000..40242317f30db3cd7f71a7bebc513063a29605e6 GIT binary patch literal 9655 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 + + + + + + + + 0 + 5 diff --git a/contents/ui/DisplayFlip.qml b/contents/ui/DisplayFlip.qml new file mode 100644 index 0000000..f041aae --- /dev/null +++ b/contents/ui/DisplayFlip.qml @@ -0,0 +1,144 @@ +/* + * Split-flap display mode — airport departure board style. + * White characters on black cells with a flap split line; + * changed characters flip with a cascading animation. + * Pure QtQuick so it can be tested outside the plasmoid shell. + */ + +import QtQuick 2.15 + +Row { + id: root + + // wired by the parent loader + property string display: "--" + property color trendColor: "#9e9e9e" + + spacing: height * 0.06 + + property var chars: [] + property bool ready: false + readonly property real cellFont: height * 0.72 + readonly property real cellWidth: cellFont * 0.80 + + Component.onCompleted: { + chars = display.split(""); + ready = true; + } + + onDisplayChanged: { + if (!ready) { + return; + } + var nc = display.split(""); + var n = Math.max(chars.length, nc.length); + for (var i = 0; i < n; i++) { + var from = i < chars.length ? chars[i] : ""; + var to = i < nc.length ? nc[i] : ""; + if (from !== to) { + var cell = rep.itemAt(i); + if (cell) { + cell.flipTo(to, i * 45); + } + } + } + chars = nc; + } + + Repeater { + id: rep + model: root.chars.length + + delegate: Rectangle { + id: cell + + width: root.cellWidth + height: root.height + radius: 2 + color: "#0b0b0d" + border.color: "#26262b" + border.width: 1 + + // plain property (not a binding) so the flip animation + // controls when the character actually changes + property string ch: "" + property string pendingCh: "" + + Component.onCompleted: { + ch = root.chars[index] !== undefined ? root.chars[index] : ""; + } + + function flipTo(newCh, delay) { + if (newCh === ch) { + return; + } + pendingCh = newCh; + delayTimer.interval = Math.max(0, delay); + delayTimer.restart(); + } + + Timer { + id: delayTimer + interval: 0 + onTriggered: flipAnim.start() + } + + transform: Scale { + id: scaleTransform + origin.y: cell.height / 2 + yScale: 1 + } + + SequentialAnimation { + id: flipAnim + NumberAnimation { + target: scaleTransform + property: "yScale" + to: 0.06 + duration: 130 + easing.type: Easing.InQuad + } + ScriptAction { + script: cell.ch = cell.pendingCh + } + NumberAnimation { + target: scaleTransform + property: "yScale" + to: 1 + duration: 130 + easing.type: Easing.OutQuad + } + } + + Text { + anchors.centerIn: parent + text: cell.ch + color: "#f2f2f2" + font.family: "DejaVu Sans Mono" + font.pixelSize: root.cellFont + } + + // the flap split line across the middle + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + y: parent.height / 2 - 1 + width: parent.width + height: 2 + color: "#000000" + opacity: 0.55 + } + } + } + + // trend LED (green/red/grey) — boards are monochrome, so the + // trend shows here instead of in the letter color + Rectangle { + width: root.height * 0.14 + height: width + radius: width / 2 + anchors.verticalCenter: parent.verticalCenter + color: root.trendColor + border.color: "#26262b" + border.width: 1 + } +} diff --git a/contents/ui/DisplayNixie.qml b/contents/ui/DisplayNixie.qml new file mode 100644 index 0000000..b0a1481 --- /dev/null +++ b/contents/ui/DisplayNixie.qml @@ -0,0 +1,138 @@ +/* + * Nixie tube display mode — retro amber glowing digits in glass tubes. + * Glow is faked with layered text copies, so no QtGraphicalEffects + * dependency. Pure QtQuick: testable outside the plasmoid shell. + */ + +import QtQuick 2.15 + +Row { + id: root + + // wired by the parent loader + property string display: "--" + property color trendColor: "#9e9e9e" + + spacing: height * 0.05 + + readonly property string digits: display.replace(/[^0-9]/g, "") + readonly property real tubeWidth: height * 0.52 + readonly property real glowFont: height * 0.80 + + Repeater { + model: root.digits.length + + delegate: Rectangle { + id: tube + + width: root.tubeWidth + height: root.height + radius: width * 0.30 + gradient: Gradient { + GradientStop { position: 0.0; color: "#3a2410" } + GradientStop { position: 0.5; color: "#241505" } + GradientStop { position: 1.0; color: "#160c03" } + } + border.color: "#4a2e12" + border.width: 1 + + // glass reflection strip + Rectangle { + anchors.top: parent.top + anchors.topMargin: parent.height * 0.06 + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width * 0.5 + height: parent.height * 0.16 + radius: height / 2 + color: "#ffffff" + opacity: 0.06 + } + + Text { + id: glowText + anchors.centerIn: parent + text: root.digits[index] + color: "#ffb648" + font.family: "DejaVu Serif" + font.pixelSize: root.glowFont + + // layered glow: two scaled copies behind the crisp digit + Text { + anchors.centerIn: parent + text: parent.text + color: "#ff8c00" + opacity: 0.30 + font: parent.font + scale: 1.24 + } + Text { + anchors.centerIn: parent + text: parent.text + color: "#ff9d1e" + opacity: 0.50 + font: parent.font + scale: 1.10 + } + + // occasional gentle nixie flicker + Timer { + interval: 2200 + Math.random() * 3200 + repeat: false + running: root.visible && root.digits.length > 0 + onTriggered: { + flick.stop(); + flick.start(); + interval = 2200 + Math.random() * 3200; + restart(); + } + } + SequentialAnimation { + id: flick + NumberAnimation { + target: glowText + property: "opacity" + to: 0.82 + duration: 70 + } + NumberAnimation { + target: glowText + property: "opacity" + to: 1.0 + duration: 140 + } + } + } + } + } + + // currency indicator, dim amber like an indicator lamp + Text { + anchors.verticalCenter: parent.verticalCenter + text: "\u20AC" + color: "#8a5a20" + font.family: "DejaVu Serif" + font.pixelSize: root.glowFont * 0.6 + visible: root.digits.length > 0 + } + + // error / empty state + Text { + anchors.verticalCenter: parent.verticalCenter + text: "--" + color: "#7a4e1c" + font.family: "DejaVu Serif" + 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/DisplayText.qml b/contents/ui/DisplayText.qml new file mode 100644 index 0000000..628f498 --- /dev/null +++ b/contents/ui/DisplayText.qml @@ -0,0 +1,27 @@ +/* + * Plain text display mode — colored price text. + * Pure QtQuick on purpose: no Plasma imports, so it can be loaded + * and tested outside the plasmoid shell. + */ + +import QtQuick 2.15 + +Text { + id: root + + // wired by the parent loader: the formatted price string + property string display: "--" + // trend color (green/red/grey) from the widget root + property color trendColor: "#9e9e9e" + + text: root.display + color: root.trendColor + textFormat: Text.PlainText + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: Qt.application.font.family + font.pixelSize: root.height > 0 + ? Math.round(root.height * 0.82) + : Qt.application.font.pixelSize +} diff --git a/contents/ui/configGeneral.qml b/contents/ui/configGeneral.qml index bfa6cee..b57c85d 100644 --- a/contents/ui/configGeneral.qml +++ b/contents/ui/configGeneral.qml @@ -12,12 +12,19 @@ Kirigami.FormLayout { id: generalPage // The cfg_ prefix is what Plasma uses to read/write plasmoid.configuration + property alias cfg_displayMode: modeCombo.currentIndex property alias cfg_refreshMinutes: intervalSpin.value Item { Kirigami.FormData.isSection: true } + QQC2.ComboBox { + id: modeCombo + Kirigami.FormData.label: i18n("Display style:") + model: [i18n("Plain text"), i18n("Split-flap board"), i18n("Nixie tubes")] + } + QQC2.SpinBox { id: intervalSpin Kirigami.FormData.label: i18n("Refresh interval (minutes):") diff --git a/contents/ui/main.qml b/contents/ui/main.qml index c0da2a3..d684e0d 100644 --- a/contents/ui/main.qml +++ b/contents/ui/main.qml @@ -7,6 +7,10 @@ * - Grey : unchanged (or first reading) * - "--" : API failure (network error, 4xx/5xx, 429 rate limit) * + * Display modes: 0 = plain text (trend-colored), 1 = split-flap board, + * 2 = nixie tubes. In the flip and nixie modes the trend shows on a + * small LED, the digits themselves stay white / amber. + * * Refresh interval: configurable, minimum 5 minutes * (CoinGecko limit: 1 call per 5 minutes). */ @@ -20,6 +24,12 @@ Item { id: root readonly property int refreshMinutes: Math.max(5, plasmoid.configuration.refreshMinutes) + + // 0 = plain text, 1 = split-flap board, 2 = nixie tubes + property int displayMode: 0 + readonly property string modeSource: displayMode === 1 ? "DisplayFlip.qml" + : displayMode === 2 ? "DisplayNixie.qml" + : "DisplayText.qml" readonly property string apiUrl: "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=eur" // -1 = down, 0 = unchanged / no previous reading yet, 1 = up @@ -61,8 +71,13 @@ Item { } Component.onCompleted: { - console.log("[bitcoinprice] started, refresh interval:", - refreshMinutes, "minutes"); + try { + displayMode = Math.max(0, Math.min(2, plasmoid.configuration.displayMode)); + } catch (e) { + // outside a real plasmoid shell (tests): keep the default + } + console.log("[bitcoinprice] started, refresh interval:", refreshMinutes, + "minutes, display mode:", displayMode); } Plasmoid.backgroundHints: PlasmaCore.Types.StandardBackground @@ -75,7 +90,9 @@ Item { Item { id: compact - Layout.minimumWidth: row.implicitWidth + PlasmaCore.Units.smallSpacing * 2 + Layout.minimumWidth: btcIcon.width + row.spacing + + (displayLoader.item ? displayLoader.item.implicitWidth : 0) + + PlasmaCore.Units.smallSpacing * 2 Layout.minimumHeight: PlasmaCore.Units.iconSizes.smallMedium Row { @@ -93,19 +110,16 @@ Item { visible: root.state === "ok" } - Text { - id: priceLabel + Loader { + id: displayLoader anchors.verticalCenter: parent.verticalCenter - text: root.displayText - color: root.priceColor - textFormat: Text.PlainText - elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: PlasmaCore.Theme.defaultFont.family - font.pixelSize: compact.height > PlasmaCore.Units.iconSizes.small - ? Math.round(compact.height * 0.6) - : PlasmaCore.Theme.defaultFont.pixelSize + height: compact.height * 0.62 + width: item ? item.implicitWidth : 0 + source: root.modeSource + onLoaded: { + item.display = Qt.binding(function () { return root.displayText; }); + item.trendColor = Qt.binding(function () { return root.priceColor; }); + } } } } @@ -115,8 +129,8 @@ Item { Item { id: fullRep - implicitWidth: PlasmaCore.Units.gridUnit * 14 - implicitHeight: PlasmaCore.Units.gridUnit * 9 + implicitWidth: PlasmaCore.Units.gridUnit * 16 + implicitHeight: PlasmaCore.Units.gridUnit * 10 Column { anchors.centerIn: parent @@ -131,12 +145,16 @@ Item { visible: root.state === "ok" } - Text { + Loader { + id: fullDisplayLoader anchors.horizontalCenter: parent.horizontalCenter - text: root.displayText - color: root.priceColor - font.family: PlasmaCore.Theme.defaultFont.family - font.pixelSize: PlasmaCore.Units.gridUnit * 2 + height: PlasmaCore.Units.gridUnit * 2.4 + width: item ? item.implicitWidth : 0 + source: root.modeSource + onLoaded: { + item.display = Qt.binding(function () { return root.displayText; }); + item.trendColor = Qt.binding(function () { return root.priceColor; }); + } } Text { diff --git a/metadata.desktop b/metadata.desktop index 02dd7a0..d0e3d90 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.1.1 +X-KDE-PluginInfo-Version=1.2.0 X-KDE-PluginInfo-Website=https://gitea.a14r.be/hermes/plasma-bitcoin-price X-KDE-PluginInfo-Category=Utilities X-KDE-PluginInfo-License=MIT