diff --git a/src/common/treelandlogging.cpp b/src/common/treelandlogging.cpp index 7592a883c..e871b81d2 100644 --- a/src/common/treelandlogging.cpp +++ b/src/common/treelandlogging.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "common/treelandlogging.h" +#include // TreeLand logging category definitions // Naming convention: treeland.module_name.submodule_name @@ -23,7 +24,7 @@ Q_LOGGING_CATEGORY(treelandSeat, "treeland.seat") Q_LOGGING_CATEGORY(treelandOutput, "treeland.output") // Window management -Q_LOGGING_CATEGORY(treelandSurface, "treeland.surface") +Q_LOGGING_CATEGORY(treelandSurface, "treeland.surface", QtDebugMsg) // Protocol module Q_LOGGING_CATEGORY(treelandProtocol, "treeland.protocol") diff --git a/src/core/qml/Animations/GeometryAnimation.qml b/src/core/qml/Animations/GeometryAnimation.qml index ec98cd368..46fe6fc94 100644 --- a/src/core/qml/Animations/GeometryAnimation.qml +++ b/src/core/qml/Animations/GeometryAnimation.qml @@ -43,7 +43,7 @@ Item { readonly property real yScale: root.height / surface.height live: true - sourceItem: surface + sourceItem: surface.surfaceItem hideSource: true sourceRect: surface.boundingRect width: sourceRect.width * xScale @@ -60,6 +60,7 @@ Item { live: false sourceItem: surface + hideSource: true width: sourceRect.width * xScale height: sourceRect.height * yScale x: sourceRect.x * xScale @@ -114,7 +115,7 @@ Item { OpacityAnimator { target: frontEffect duration: root.duration / 2 - easing.type: Easing.OutCubic + easing.type: Easing.Linear from: 1.0 to: 0.0 } diff --git a/src/core/qml/PrelaunchSplash.qml b/src/core/qml/PrelaunchSplash.qml index e3b05989f..9a4715104 100644 --- a/src/core/qml/PrelaunchSplash.qml +++ b/src/core/qml/PrelaunchSplash.qml @@ -13,7 +13,6 @@ Item { required property var iconBuffer required property color backgroundColor readonly property bool isLightBackground: backgroundColor.hslLightness >= 0.5 - signal destroyRequested // Fill the entire parent (SurfaceWrapper) anchors.fill: parent @@ -68,13 +67,4 @@ Item { } } } - - function hideAndDestroy() { - if (!splash.visible) { - console.warn("PrelaunchSplash: Already hidden, ignoring hideAndDestroy call."); - return; - } - splash.visible = false - splash.destroyRequested(); - } } diff --git a/src/core/windowconfigstore.cpp b/src/core/windowconfigstore.cpp index 883f713c0..7e8810b56 100644 --- a/src/core/windowconfigstore.cpp +++ b/src/core/windowconfigstore.cpp @@ -32,6 +32,7 @@ AppConfig *WindowConfigStore::configForApp(const QString &appId) const void WindowConfigStore::saveLastSize(const QString &appId, const QSize &size) { + return; // TO Debug if (appId.isEmpty() || !size.isValid()) { qCWarning(treelandCore) << "WindowConfigStore: saveLastSize invalid parameters for" << appId << size; @@ -49,14 +50,13 @@ void WindowConfigStore::saveLastSize(const QString &appId, const QSize &size) config->setLastWindowHeight(size.height()); } -void WindowConfigStore::withSplashConfigFor( - const QString &appId, - QObject *context, - std::function callback, - std::function skipCallback) const +void WindowConfigStore::withSplashConfigFor(const QString &appId, + QObject *context, + std::function callback, + std::function skipCallback) const { Q_ASSERT_X(callback, Q_FUNC_INFO, "callback must be provided"); Q_ASSERT_X(skipCallback, Q_FUNC_INFO, "skipCallback must be provided"); @@ -81,9 +81,9 @@ void WindowConfigStore::withSplashConfigFor( static_cast(config->lastWindowHeight())); const QSize validatedSize = size.isValid() ? size : QSize(); callback(validatedSize, - config->splashDarkPalette(), - config->splashLightPalette(), - config->splashThemeType()); + config->splashDarkPalette(), + config->splashLightPalette(), + config->splashThemeType()); return; } diff --git a/src/surface/surfacewrapper.cpp b/src/surface/surfacewrapper.cpp index 8110fe289..94871b8fb 100644 --- a/src/surface/surfacewrapper.cpp +++ b/src/surface/surfacewrapper.cpp @@ -165,17 +165,12 @@ SurfaceWrapper::SurfaceWrapper(QmlEngine *qmlEngine, if (initialSize.isValid() && initialSize.width() > 0 && initialSize.height() > 0) { // Also set implicit size to keep QML layout consistent setImplicitSize(initialSize.width(), initialSize.height()); - qCDebug(treelandSurface) << "Prelaunch Splash: set initial size to" << initialSize; + qCWarning(treelandSurface) << "Prelaunch Splash: set initial size to" << initialSize; } else { setImplicitSize(800, 600); } m_prelaunchSplash = m_engine->createPrelaunchSplash(this, radius(), iconBuffer, backgroundColor); - // Connect to QML signal so C++ can destroy the QML item when requested - connect(m_prelaunchSplash, - SIGNAL(destroyRequested()), - this, - SLOT(onPrelaunchSplashDestroyRequested())); setNoDecoration(false); updateHasActiveCapability(ActiveControlState::MappedOrSplash, true); // Splash is true @@ -309,7 +304,7 @@ void SurfaceWrapper::setup() }); } - if (!m_prelaunchSplash || !m_prelaunchSplash->isVisible()) { + if (!m_prelaunchSplash) { setImplicitSize(m_surfaceItem->implicitWidth(), m_surfaceItem->implicitHeight()); connect(m_surfaceItem, &WSurfaceItem::implicitWidthChanged, this, [this] { setImplicitWidth(m_surfaceItem->implicitWidth()); @@ -332,9 +327,7 @@ void SurfaceWrapper::setup() } if (!m_shellSurface->hasCapability(WToplevelSurface::Capability::Focus)) { -#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) m_surfaceItem->setFocusPolicy(Qt::NoFocus); -#endif } if (m_type == Type::XdgToplevel && !m_isProxy) // x11 will set later @@ -423,11 +416,12 @@ void SurfaceWrapper::convertToNormalSurface(WToplevelSurface *shellSurface, Type // Call setup() to initialize surfaceItem related features setup(); + m_surfaceItem->setVisible(false); - // setNoDecoration not called updateTitleBar when type is SplashScreen - updateTitleBar(); - updateDecoration(); if (surface()->mapped()) { + qCWarning(treelandSurface) << "[prelaunch-debug] convertToNormalSurface: already mapped" + << "hasSplash=" << static_cast(m_prelaunchSplash) + << "implicit=" << QSizeF(implicitWidth(), implicitHeight()); // Apply pending outputs only after mapped to ensure wl_surface resource is ready. if (!m_prelaunchOutputs.isEmpty()) { setOutputs(m_prelaunchOutputs); @@ -435,7 +429,8 @@ void SurfaceWrapper::convertToNormalSurface(WToplevelSurface *shellSurface, Type } updateActiveState(); Q_ASSERT(m_prelaunchSplash); - QMetaObject::invokeMethod(m_prelaunchSplash, "hideAndDestroy", Qt::QueuedConnection); + // Start prelaunch hidden sequence: check if size transition is needed + startPrelaunchSplashHideSequence(); } else { // Defer output assignment/updateActiveState until surface becomes mapped connect( @@ -516,10 +511,48 @@ void SurfaceWrapper::setFocus(bool focus, Qt::FocusReason reason) m_surfaceItem->setFocus(false, reason); } -void SurfaceWrapper::onPrelaunchSplashDestroyRequested() +void SurfaceWrapper::startPrelaunchSplashHideSequence() { - if (m_surfaceItem) { - setImplicitSize(m_surfaceItem->implicitWidth(), m_surfaceItem->implicitHeight()); + Q_ASSERT(m_surfaceItem); + + QSizeF targetImplicitSize; + if (auto surf = surface()) { + const QSize surfaceSize = surf->size(); + targetImplicitSize = QSizeF(surfaceSize.width(), surfaceSize.height()); + } + if (targetImplicitSize.width() <= 0 || targetImplicitSize.height() <= 0) { + targetImplicitSize = QSizeF(m_surfaceItem->implicitWidth(), + m_surfaceItem->implicitHeight()); + } + if (targetImplicitSize.width() <= 0 || targetImplicitSize.height() <= 0) { + const QSizeF fallbackSize(qMax(implicitWidth(), 1.0), qMax(implicitHeight(), 1.0)); + qCWarning(treelandSurface) + << "[prelaunch-debug] invalid target implicit size, fallback to current" + << "targetImplicit=" << targetImplicitSize << "fallback=" << fallbackSize; + targetImplicitSize = fallbackSize; + } + + const bool needImplicitSizeTransition = + !qFuzzyCompare(implicitWidth() + 1.0, targetImplicitSize.width() + 1.0) + || !qFuzzyCompare(implicitHeight() + 1.0, targetImplicitSize.height() + 1.0); + + qCWarning(treelandSurface) << "[prelaunch-debug] startHideSequence" + << "needTransition=" << needImplicitSizeTransition + << "hasGeometryAnim=" << static_cast(m_geometryAnimation) + << "hasContainer=" << static_cast(container()) + << "currentImplicit=" << QSizeF(implicitWidth(), implicitHeight()) + << "targetImplicit=" << targetImplicitSize; + + // This sequence can be triggered from multiple paths (e.g. convert + mappedChanged). + // If geometry animation is already running, keep it running and avoid fallback finalize. + if (needImplicitSizeTransition && m_geometryAnimation) { + qCWarning(treelandSurface) + << "[prelaunch-debug] startHideSequence: geometry animation already running, skip"; + return; + } + + auto applySurfaceImplicitBinding = [this, targetImplicitSize]() { + setImplicitSize(targetImplicitSize.width(), targetImplicitSize.height()); connect(m_surfaceItem, &WSurfaceItem::implicitWidthChanged, this, [this] { setImplicitWidth(m_surfaceItem->implicitWidth()); }); @@ -532,17 +565,108 @@ void SurfaceWrapper::onPrelaunchSplashDestroyRequested() &SurfaceWrapper::updateBoundingRect); if (m_decoration) m_decoration->stackBefore(m_surfaceItem); + }; + + auto prepareSurfaceForReveal = [this]() { + // setNoDecoration not called updateTitleBar when type is SplashScreen + updateTitleBar(); + updateDecoration(); + if (m_surfaceItem) + m_surfaceItem->setVisible(true); + }; + + if (needImplicitSizeTransition && !m_geometryAnimation && container()) { + qCWarning(treelandSurface) << "[prelaunch-debug] startHideSequence: create/start geometry animation"; + m_pendingPrelaunchImplicitSize = targetImplicitSize; + const QRectF fromGeometry(position(), size()); + // XWayland clients manage their own position; respect it and don't shift. + // For all other types, keep the center fixed so the window expands from center. + const QPointF toTopLeft = (m_type == Type::XWayland) ? fromGeometry.topLeft() + : fromGeometry.center() + - QPointF(targetImplicitSize.width() / 2.0, targetImplicitSize.height() / 2.0); + m_pendingPrelaunchPosition = toTopLeft; + const QRectF toGeometry(toTopLeft, targetImplicitSize); + m_geometryAnimation = + m_engine->createGeometryAnimation(this, fromGeometry, toGeometry, container()); + + m_geometryAnimation->setOpacity(0.8); + bool ok = connect(m_geometryAnimation, + SIGNAL(ready()), + this, + SLOT(onPrelaunchGeometryAnimationReady())); + Q_ASSERT(ok); + ok = connect(m_geometryAnimation, + SIGNAL(finished()), + this, + SLOT(onPrelaunchGeometryAnimationFinished())); + Q_ASSERT(ok); + ok = QMetaObject::invokeMethod(m_geometryAnimation, "start"); + Q_ASSERT(ok); + qCWarning(treelandSurface) << "[prelaunch-debug] startHideSequence: geometry animation started" + << "from=" << fromGeometry << "to=" << toGeometry; + } else { + qCWarning(treelandSurface) << "[prelaunch-debug] startHideSequence: no geometry animation path, finalize now"; + applySurfaceImplicitBinding(); + prepareSurfaceForReveal(); + finalizePrelaunchSplash(); } +} - updateVisible(); +void SurfaceWrapper::onPrelaunchGeometryAnimationReady() +{ + + qCWarning(treelandSurface) << "[prelaunch-debug] geometryAnimation ready" + << "targetPos=" << m_pendingPrelaunchPosition + << "targetImplicit=" << m_pendingPrelaunchImplicitSize; + + // Move the wrapper to the animation's final position before revealing the real surface, + // so the window appears exactly where the animation ended (no position jump). + const QPointF targetPos = m_pendingPrelaunchPosition; + setPosition(targetPos); + // Keep normalGeometry in sync so subsequent state transitions use the correct position. + setNormalGeometry(QRectF(targetPos, m_pendingPrelaunchImplicitSize)); + + setImplicitSize(m_pendingPrelaunchImplicitSize.width(), + m_pendingPrelaunchImplicitSize.height()); + connect(m_surfaceItem, &WSurfaceItem::implicitWidthChanged, this, [this] { + setImplicitWidth(m_surfaceItem->implicitWidth()); + }); + connect(m_surfaceItem, &WSurfaceItem::implicitHeightChanged, this, [this] { + setImplicitHeight(m_surfaceItem->implicitHeight()); + }); + connect(m_surfaceItem, + &WSurfaceItem::boundingRectChanged, + this, + &SurfaceWrapper::updateBoundingRect); + // setNoDecoration not called updateTitleBar when type is SplashScreen + updateTitleBar(); + //updateDecoration(); + //if (m_decoration) + // m_decoration->stackBefore(m_surfaceItem); + finalizePrelaunchSplash(); + m_surfaceItem->setVisible(true); +} + +void SurfaceWrapper::onPrelaunchGeometryAnimationFinished() +{ + Q_ASSERT(m_geometryAnimation); + m_geometryAnimation->disconnect(this); + m_geometryAnimation->deleteLater(); + m_geometryAnimation = nullptr; +} + +void SurfaceWrapper::finalizePrelaunchSplash() +{ + Q_ASSERT (m_prelaunchSplash); - Q_ASSERT(m_prelaunchSplash); + m_prelaunchSplash->setProperty("visible", false); m_prelaunchSplash->deleteLater(); m_prelaunchSplash = nullptr; + Q_EMIT prelaunchSplashChanged(); + updateHasActiveCapability(ActiveControlState::MappedOrSplash, surface() && surface()->mapped()); if (m_shellSurface) updateActiveState(); - Q_EMIT prelaunchSplashChanged(); } WSurface *SurfaceWrapper::surface() const @@ -1059,9 +1183,6 @@ void SurfaceWrapper::updateBoundingRect() void SurfaceWrapper::updateVisible() { - if (m_prelaunchSplash && m_prelaunchSplash->isVisible()) - return; - setVisible(!m_hideByWorkspace && !isMinimized() && (surface() && surface()->mapped()) && m_socketEnabled && m_hideByshowDesk && !m_confirmHideByLockScreen && Helper::instance()->surfaceBelongsToCurrentSession(this)); @@ -1346,6 +1467,9 @@ void SurfaceWrapper::onMappedChanged() Q_ASSERT(surface()); bool mapped = surface()->mapped() && !m_hideByLockScreen; + qCWarning(treelandSurface) << "[prelaunch-debug] onMappedChanged" + << "mapped=" << mapped + << "hasSplash=" << static_cast(m_prelaunchSplash); if (!m_isProxy) { if (mapped) { @@ -1364,8 +1488,8 @@ void SurfaceWrapper::onMappedChanged() } if (m_prelaunchSplash) { - // The QML part will check for duplicate calls. - QMetaObject::invokeMethod(m_prelaunchSplash, "hideAndDestroy", Qt::QueuedConnection); + qCWarning(treelandSurface) << "[prelaunch-debug] onMappedChanged: trigger hide sequence"; + startPrelaunchSplashHideSequence(); } // Splash can't call onMappedChanged, just use mapped state to update diff --git a/src/surface/surfacewrapper.h b/src/surface/surfacewrapper.h index c85486c59..405746f97 100644 --- a/src/surface/surfacewrapper.h +++ b/src/surface/surfacewrapper.h @@ -375,7 +375,9 @@ public Q_SLOTS: void doSetSurfaceState(State newSurfaceState); Q_SLOT void onAnimationReady(); Q_SLOT void onAnimationFinished(); - Q_SLOT void onPrelaunchSplashDestroyRequested(); + void startPrelaunchSplashHideSequence(); + Q_SLOT void onPrelaunchGeometryAnimationReady(); + Q_SLOT void onPrelaunchGeometryAnimationFinished(); bool startStateChangeAnimation(SurfaceWrapper::State targetState, const QRectF &targetGeometry); void onWindowAnimationFinished(); Q_SLOT void onShowAnimationFinished(); @@ -386,6 +388,7 @@ public Q_SLOTS: void startShowDesktopAnimation(bool show); Q_SLOT void onShowDesktopAnimationFinished(); void updateHasActiveCapability(ActiveControlState state, bool value); + void finalizePrelaunchSplash(); // wayland set by treeland-dde-shell, x11 set by bypassManager/windowTypes void setSkipDockPreView(bool skip); @@ -405,6 +408,8 @@ public Q_SLOTS: QPointer m_coverContent; QPointer m_prelaunchSplash; // Pre-launch splash item QList m_prelaunchOutputs; // Outputs for pre-launch splash + QSizeF m_pendingPrelaunchImplicitSize; + QPointF m_pendingPrelaunchPosition; QRectF m_boundedRect; QRectF m_normalGeometry; QRectF m_maximizedGeometry;