Files
Button/qml/SplashOverlay.qml
T
GrigoandClaude Opus 5.5 f07e153f09 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>
2026-11-10 10:58:16 +00:00

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
}
}
}