diff --git a/README.md b/README.md index 5a8423f..a52e8c5 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Three display styles, configurable in the settings dialog: | Style | Description | |---|---| | *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 | +| *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 | 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. @@ -35,7 +35,10 @@ representations. 5 minutes. A hint text in the config dialog explains this. - **Split-flap cascade (seconds)** — default 3, range 1–15. Spreads the flip animation of a price change evenly over this many seconds (applies to the - split-flap display style). + split-flap display style). Only shown when that style is selected. +- **Nixie flicker (0 = off)** — default 40, range 0–100. Random cathode-dropout + flicker of the nixie tubes; 0 disables it, higher values flicker more often + and deeper. Only shown when the nixie style is selected. Configurable via right-click on the widget → *Configure Bitcoin Price…* → *General*. diff --git a/bitcoin-price-1.4.0.plasmoid b/bitcoin-price-1.4.0.plasmoid new file mode 100644 index 0000000..60874b4 Binary files /dev/null and b/bitcoin-price-1.4.0.plasmoid differ diff --git a/contents/config/main.xml b/contents/config/main.xml index a51a203..8a8367a 100644 --- a/contents/config/main.xml +++ b/contents/config/main.xml @@ -20,6 +20,12 @@ 1 15 + + + 40 + 0 + 100 + 5 diff --git a/contents/ui/DisplayFlip.qml b/contents/ui/DisplayFlip.qml index 234ae85..01599a9 100644 --- a/contents/ui/DisplayFlip.qml +++ b/contents/ui/DisplayFlip.qml @@ -1,7 +1,9 @@ /* * 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. + * 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. * Pure QtQuick so it can be tested outside the plasmoid shell. */ @@ -18,6 +20,23 @@ Row { spacing: height * 0.06 + function digitsOf(s) { + var out = ""; + for (var i = 0; i < s.length; i++) { + var ch = s.charAt(i); + if ((ch >= "0" && ch <= "9") || ch === "-") { + out += ch; + } + } + 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. + readonly property string digitsOnly: digitsOf(display) + readonly property string boardText: digitsOnly.length > 0 ? digitsOnly : "--" + property var chars: [] property bool ready: false readonly property real cellFont: height * 0.72 @@ -26,7 +45,7 @@ Row { readonly property int flapDuration: 260 Component.onCompleted: { - chars = display.split(""); + chars = boardText.split(""); ready = true; } @@ -34,7 +53,10 @@ Row { if (!ready) { return; } - var nc = display.split(""); + // compute from display directly: derived bindings may still be + // stale at this point (QML makes no ordering guarantee) + 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 = []; @@ -141,8 +163,17 @@ Row { } } - // trend LED (green/red/grey) — boards are monochrome, so the - // trend shows here instead of in the letter color + // euro sign in the plain-text font, white + Text { + anchors.verticalCenter: parent.verticalCenter + text: "\u20AC" + visible: root.digitsOnly.length > 0 + color: "#ffffff" + font.family: Qt.application.font.family + font.pixelSize: root.height * 0.66 + } + + // trend LED (green/red/grey) Rectangle { width: root.height * 0.14 height: width diff --git a/contents/ui/DisplayNixie.qml b/contents/ui/DisplayNixie.qml index b0a1481..fd7c112 100644 --- a/contents/ui/DisplayNixie.qml +++ b/contents/ui/DisplayNixie.qml @@ -1,7 +1,10 @@ /* * 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. + * 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). + * Pure QtQuick: testable outside the plasmoid shell. */ import QtQuick 2.15 @@ -12,12 +15,26 @@ Row { // wired by the parent loader property string display: "--" property color trendColor: "#9e9e9e" + // 0 = no flicker, 100 = frequent and deep flicker + property int flickerIntensity: 40 spacing: height * 0.05 - readonly property string digits: display.replace(/[^0-9]/g, "") + function digitsOf(s) { + var out = ""; + for (var i = 0; i < s.length; i++) { + var ch = s.charAt(i); + if (ch >= "0" && ch <= "9") { + out += ch; + } + } + return out; + } + + readonly property string digits: digitsOf(display) readonly property real tubeWidth: height * 0.52 readonly property real glowFont: height * 0.80 + readonly property bool flickerOn: flickerIntensity > 0 && digits.length > 0 Repeater { model: root.digits.length @@ -48,71 +65,86 @@ Row { opacity: 0.06 } - Text { - id: glowText - anchors.centerIn: parent - text: root.digits[index] - color: "#ffb648" - font.family: "DejaVu Serif" - font.pixelSize: root.glowFont + // digit + glow layers, flickered as one group + Item { + id: glowGroup + anchors.fill: parent - // layered glow: two scaled copies behind the crisp digit Text { + id: glowText anchors.centerIn: parent - text: parent.text - color: "#ff8c00" - opacity: 0.30 - font: parent.font - scale: 1.24 + 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 + } } - Text { - anchors.centerIn: parent - text: parent.text - color: "#ff9d1e" - opacity: 0.50 - font: parent.font - scale: 1.10 + } + + // nixie flicker: random dropout of the cathode glow + Timer { + id: flickTimer + repeat: false + running: root.flickerOn + onTriggered: { + flick.restart(); + scheduleNext(); } - // 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(); + 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)); + } + + Component.onCompleted: scheduleNext() + } + + SequentialAnimation { + id: flick + ScriptAction { + script: { + var range = root.flickerIntensity / 100 * 0.75; + glowGroup.opacity = 1 - (0.15 + Math.random() * range); } } - SequentialAnimation { - id: flick - NumberAnimation { - target: glowText - property: "opacity" - to: 0.82 - duration: 70 - } - NumberAnimation { - target: glowText - property: "opacity" - to: 1.0 - duration: 140 - } + PauseAnimation { duration: 40 + Math.random() * 140 } + NumberAnimation { + target: glowGroup + property: "opacity" + to: 1.0 + duration: 90 + Math.random() * 160 + easing.type: Easing.OutQuad } } } } - // currency indicator, dim amber like an indicator lamp + // euro sign in the plain-text font, white Text { anchors.verticalCenter: parent.verticalCenter text: "\u20AC" - color: "#8a5a20" - font.family: "DejaVu Serif" - font.pixelSize: root.glowFont * 0.6 visible: root.digits.length > 0 + color: "#ffffff" + font.family: Qt.application.font.family + font.pixelSize: root.height * 0.60 } // error / empty state diff --git a/contents/ui/configGeneral.qml b/contents/ui/configGeneral.qml index 0b9f870..e8ec7e3 100644 --- a/contents/ui/configGeneral.qml +++ b/contents/ui/configGeneral.qml @@ -2,6 +2,7 @@ * Config page for nl.a14r.bitcoinprice. * Follows the canonical Plasma 5 config dialog pattern: * Kirigami.FormLayout + cfg_ prefixed property aliases. + * Mode-specific settings are only visible for the matching style. */ import QtQuick 2.15 @@ -15,6 +16,7 @@ Kirigami.FormLayout { property alias cfg_displayMode: modeCombo.currentIndex property alias cfg_refreshMinutes: intervalSpin.value property alias cfg_flapSeconds: flapSpin.value + property alias cfg_nixieFlicker: flickSpin.value Item { Kirigami.FormData.isSection: true @@ -28,6 +30,7 @@ Kirigami.FormLayout { QQC2.SpinBox { id: flapSpin + visible: modeCombo.currentIndex === 1 Kirigami.FormData.label: i18n("Split-flap cascade (seconds):") from: 1 to: 15 @@ -35,6 +38,16 @@ Kirigami.FormLayout { editable: true } + QQC2.SpinBox { + id: flickSpin + visible: modeCombo.currentIndex === 2 + Kirigami.FormData.label: i18n("Nixie flicker (0 = off):") + from: 0 + to: 100 + stepSize: 10 + editable: true + } + QQC2.SpinBox { id: intervalSpin Kirigami.FormData.label: i18n("Refresh interval (minutes):") diff --git a/contents/ui/main.qml b/contents/ui/main.qml index fc6e1ad..6a51e46 100644 --- a/contents/ui/main.qml +++ b/contents/ui/main.qml @@ -28,6 +28,9 @@ Item { // Seconds over which the split-flap display cascades to a new price readonly property int flapSeconds: Math.max(1, plasmoid.configuration.flapSeconds) + // Nixie tube flicker intensity (0 = off, 100 = frequent and deep) + readonly property int nixieFlicker: Math.max(0, Math.min(100, plasmoid.configuration.nixieFlicker)) + // 0 = plain text, 1 = split-flap board, 2 = nixie tubes property int displayMode: 0 readonly property string modeSource: displayMode === 1 ? "DisplayFlip.qml" @@ -143,6 +146,9 @@ Item { if (item.flapSeconds !== undefined) { item.flapSeconds = Qt.binding(function () { return root.flapSeconds; }); } + if (item.flickerIntensity !== undefined) { + item.flickerIntensity = Qt.binding(function () { return root.nixieFlicker; }); + } } } } @@ -181,6 +187,9 @@ Item { if (item.flapSeconds !== undefined) { item.flapSeconds = Qt.binding(function () { return root.flapSeconds; }); } + if (item.flickerIntensity !== undefined) { + item.flickerIntensity = Qt.binding(function () { return root.nixieFlicker; }); + } } } diff --git a/metadata.desktop b/metadata.desktop index 82bf150..99193d1 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.3.0 +X-KDE-PluginInfo-Version=1.4.0 X-KDE-PluginInfo-Website=https://gitea.a14r.be/hermes/plasma-bitcoin-price X-KDE-PluginInfo-Category=Utilities X-KDE-PluginInfo-License=MIT