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>
87 lines
2.6 KiB
QML
87 lines
2.6 KiB
QML
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
|
|
}
|
|
}
|
|
}
|