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 0000000..4024231
Binary files /dev/null and b/bitcoin-price-1.2.0.plasmoid differ
diff --git a/contents/config/main.xml b/contents/config/main.xml
index cc8de78..2aff24a 100644
--- a/contents/config/main.xml
+++ b/contents/config/main.xml
@@ -5,6 +5,15 @@
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
+
+
+
+
+
+
+
+ 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