#include "transition_overlay.h" #include #include #include #include #include #include #include #include #include namespace vdi { namespace { constexpr int kTickMs = 400; // 省略号/旋轮动画周期 constexpr int kFadeStepMs = 40; // 淡出步进周期 constexpr int kFadeSteps = 25; // 全程约 1 秒淡出 const char* kSpinnerFrames[] = {"◐", "◓", "◑", "◒"}; } // namespace TransitionOverlay::TransitionOverlay(QWidget* parent) : QWidget(parent) { // 独立置顶无边框窗口只在启动时映射一次,后续不再与 Moonlight 抢焦点。 setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus #ifdef Q_OS_UNIX | Qt::X11BypassWindowManagerHint #endif ); setAttribute(Qt::WA_ShowWithoutActivating); // A plain QWidget subclass needs an explicit opaque palette; a type-name // stylesheet alone may leave the native window using its default white. setAutoFillBackground(true); QPalette background = palette(); background.setColor(QPalette::Window, QColor(QStringLiteral("#0f1420"))); setPalette(background); auto* layout = new QVBoxLayout(this); layout->setSpacing(28); labelSpinner_ = new QLabel(this); QFont spinFont = labelSpinner_->font(); spinFont.setPointSize(44); labelSpinner_->setFont(spinFont); labelSpinner_->setAlignment(Qt::AlignCenter); labelSpinner_->setStyleSheet(QStringLiteral("color: #2f6feb; background: transparent;")); layout->addStretch(1); layout->addWidget(labelSpinner_); labelTitle_ = new QLabel(this); QFont titleFont = labelTitle_->font(); titleFont.setPointSize(20); labelTitle_->setFont(titleFont); labelTitle_->setAlignment(Qt::AlignCenter); labelTitle_->setStyleSheet(QStringLiteral("color: #e6e9ef; background: transparent;")); layout->addWidget(labelTitle_); layout->addStretch(2); tickTimer_ = new QTimer(this); connect(tickTimer_, &QTimer::timeout, this, &TransitionOverlay::onTick); fadeTimer_ = new QTimer(this); connect(fadeTimer_, &QTimer::timeout, this, &TransitionOverlay::onFadeStep); // 活动期间吞掉本窗口的键盘/鼠标事件,避免误触下层页面 installEventFilter(this); } void TransitionOverlay::start(const QString& title) { tickTimer_->stop(); fadeTimer_->stop(); dots_ = 0; setWindowOpacity(1.0); setProperty("baseTitle", title); labelTitle_->setText(title); labelSpinner_->setText(QString::fromUtf8(kSpinnerFrames[0])); if (QScreen* targetScreen = QGuiApplication::primaryScreen()) { setGeometry(targetScreen->geometry()); } show(); raise(); tickTimer_->start(kTickMs); } void TransitionOverlay::finish() { tickTimer_->stop(); fadeTimer_->stop(); hide(); setWindowOpacity(1.0); } void TransitionOverlay::onFirstFrame() { // The exclusive X11 kiosk has no compositor to apply window opacity. // Once the full-screen stream is ready, unmap the opaque cover directly. #ifdef Q_OS_UNIX if (QGuiApplication::platformName() == QStringLiteral("xcb")) { finish(); return; } #endif // 首帧已出画:停止动画并淡出;若尚未显示/已在淡出则忽略 if (!isVisible() || fadeTimer_->isActive()) { return; } tickTimer_->stop(); fadeTimer_->start(kFadeStepMs); } void TransitionOverlay::onTick() { dots_ = (dots_ + 1) % 4; const QString base = property("baseTitle").toString(); if (!base.isEmpty()) { labelTitle_->setText(base + QString(dots_, QLatin1Char('.'))); } labelSpinner_->setText(QString::fromUtf8(kSpinnerFrames[dots_])); } void TransitionOverlay::onFadeStep() { const double opacity = windowOpacity() - 1.0 / kFadeSteps; if (opacity <= 0.0) { finish(); return; } setWindowOpacity(opacity); } bool TransitionOverlay::eventFilter(QObject* watched, QEvent* event) { if (watched == this && isVisible()) { switch (event->type()) { case QEvent::KeyPress: case QEvent::KeyRelease: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseMove: case QEvent::Wheel: return true; default: break; } } return QWidget::eventFilter(watched, event); } } // namespace vdi