Boot splash and periodic announcement banner
Splash: resources/splash/splash.gif built into the binary, played once by SplashOverlay over a black cover present from the first frame (no UI flash), then fades into the UI; hides icons loading. GIF, so no QtMultimedia/gstreamer. Toggle: splash.enabled. Banner (InfoBanner): metro-style band that fades/slides in with a hold pictogram; text that does not fit runs as a seamless ticker for two rounds. Never takes touches; hidden during splash and settings. Config section banner (enabled, text, intervalSec, showSec, position, accentColor), editable in the web UI and on-device settings; shown once after saving. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import QtQuick 2.12
|
||||
|
||||
// Boot splash: plays resources/splash/splash.gif once over the UI, then fades
|
||||
// into it. Also hides button icons popping in while they load.
|
||||
// GIF on purpose: AnimatedImage needs only QtGui's gif plugin, while mp4 would
|
||||
// pull in QtMultimedia + gstreamer. The GIF starts and ends on black.
|
||||
//
|
||||
// The root is a plain black cover that exists from the very first frame, so
|
||||
// the UI never flashes before the GIF starts. It goes away only after the
|
||||
// GIF has finished (or right away, with a fade, if the splash is disabled).
|
||||
// Config: splash.enabled (read once at startup).
|
||||
Rectangle {
|
||||
id: splash
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
|
||||
readonly property bool active: visible
|
||||
|
||||
function finish() {
|
||||
if (!visible || fadeOut.running)
|
||||
return
|
||||
fadeOut.start()
|
||||
}
|
||||
|
||||
// Swallow touches until the UI is visible. The recovery gesture works
|
||||
// anyway: it listens at window level in C++.
|
||||
MouseArea { anchors.fill: parent }
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.fill: parent
|
||||
active: settingsContainer.splashEnabled
|
||||
sourceComponent: AnimatedImage {
|
||||
// Square GIF: cover the screen and crop its thin grey frame.
|
||||
width: Math.max(splash.width, splash.height) * 1.02
|
||||
height: width
|
||||
x: (splash.width - width) / 2
|
||||
y: (splash.height - height) / 2
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "qrc:/splash/splash.gif"
|
||||
asynchronous: false
|
||||
cache: false
|
||||
smooth: true
|
||||
playing: true
|
||||
onStatusChanged: {
|
||||
if (status === Image.Error) {
|
||||
console.warn("Splash: cannot load gif, skipping")
|
||||
splash.finish()
|
||||
}
|
||||
}
|
||||
onCurrentFrameChanged: {
|
||||
if (frameCount > 1 && currentFrame === frameCount - 1) {
|
||||
playing = false
|
||||
splash.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Splash disabled: keep the cover a moment so icons can load, then fade.
|
||||
Timer {
|
||||
interval: 600
|
||||
running: !loader.active
|
||||
onTriggered: splash.finish()
|
||||
}
|
||||
|
||||
// Safety net: never keep the UI hidden if the gif stalls.
|
||||
Timer {
|
||||
interval: 10000
|
||||
running: true
|
||||
onTriggered: splash.finish()
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: fadeOut
|
||||
target: splash
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: 450
|
||||
easing.type: Easing.InOutQuad
|
||||
onStopped: {
|
||||
splash.visible = false
|
||||
loader.active = false // frees the decoder and textures
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user