From e4fc6f673dd4c981ba65d529cc2c69b5270304fb Mon Sep 17 00:00:00 2001 From: Mengci Cai Date: Fri, 13 Mar 2026 14:02:51 +0800 Subject: [PATCH] feat: add dynamic position updates for notification bubbles Added requestPositionUpdate method to DLayerShellWindow to allow QML components to trigger window position recalculations with custom dimensions. This enables the notification panel to dynamically adjust its position based on content height changes. Key changes: 1. Added Q_INVOKABLE requestPositionUpdate method in DLayerShellWindow 2. Implemented onPositionUpdateRequested slot in LayerShellEmulation to handle custom width/height calculations 3. Modified notification bubble panel to update window height based on content changes 4. Added smooth removal animations for notification bubbles 5. Fixed window visibility handling with delayed hiding when empty The position calculation now properly accounts for anchor constraints and margins when using custom dimensions, ensuring correct window placement regardless of content size changes. Log: Improved notification panel positioning and animations Influence: 1. Test notification display with varying numbers of bubbles 2. Verify window position updates correctly when content height changes 3. Check smooth animations when adding/removing notifications 4. Validate window hides properly after last notification is dismissed 5. Test with different screen resolutions and scaling factors 6. Verify anchor constraints work correctly with dynamic sizing PMS: BUG-284659 --- frame/layershell/dlayershellwindow.cpp | 5 +++ frame/layershell/dlayershellwindow.h | 9 ++++ frame/layershell/x11dlayershellemulation.cpp | 43 ++++++++++++++++++++ frame/layershell/x11dlayershellemulation.h | 1 + panels/notification/bubble/bubblepanel.cpp | 11 ++++- panels/notification/bubble/package/main.qml | 39 ++++++++++++++++-- 6 files changed, 104 insertions(+), 4 deletions(-) diff --git a/frame/layershell/dlayershellwindow.cpp b/frame/layershell/dlayershellwindow.cpp index 25a184c9c..e23f09325 100644 --- a/frame/layershell/dlayershellwindow.cpp +++ b/frame/layershell/dlayershellwindow.cpp @@ -231,6 +231,11 @@ DLayerShellWindow* DLayerShellWindow::get(QWindow* window) return new DLayerShellWindow(window); } +void DLayerShellWindow::requestPositionUpdate(int width, int height) +{ + Q_EMIT positionUpdateRequested(width, height); +} + DLayerShellWindow* DLayerShellWindow::qmlAttachedProperties(QObject *object) { auto window = qobject_cast(object); diff --git a/frame/layershell/dlayershellwindow.h b/frame/layershell/dlayershellwindow.h index c62497357..075c15ffa 100644 --- a/frame/layershell/dlayershellwindow.h +++ b/frame/layershell/dlayershellwindow.h @@ -130,6 +130,14 @@ class DS_SHARE DLayerShellWindow : public QObject void setCloseOnDismissed(bool close); bool closeOnDismissed() const; + /** + * Request position update with specified width and height + * This can be called from QML to trigger a position recalculation + * @param width The window width to use for position calculation + * @param height The window height to use for position calculation + */ + Q_INVOKABLE void requestPositionUpdate(int width, int height); + /** * Gets the LayerShell Window for a given Qt Window * Ownership is not transferred @@ -145,6 +153,7 @@ class DS_SHARE DLayerShellWindow : public QObject void keyboardInteractivityChanged(); void layerChanged(); void scopeChanged(); + void positionUpdateRequested(int width, int height); private: DLayerShellWindow(QWindow* window); diff --git a/frame/layershell/x11dlayershellemulation.cpp b/frame/layershell/x11dlayershellemulation.cpp index c38280967..a3326211f 100644 --- a/frame/layershell/x11dlayershellemulation.cpp +++ b/frame/layershell/x11dlayershellemulation.cpp @@ -33,6 +33,7 @@ LayerShellEmulation::LayerShellEmulation(QWindow* window, QObject *parent) onPositionChanged(); connect(m_dlayerShellWindow, &DLayerShellWindow::anchorsChanged, this, &LayerShellEmulation::onPositionChanged); connect(m_dlayerShellWindow, &DLayerShellWindow::marginsChanged, this, &LayerShellEmulation::onPositionChanged); + connect(m_dlayerShellWindow, &DLayerShellWindow::positionUpdateRequested, this, &LayerShellEmulation::onPositionUpdateRequested); onExclusionZoneChanged(); m_exclusionZoneChangedTimer.setSingleShot(true); @@ -120,6 +121,9 @@ void LayerShellEmulation::onPositionChanged() auto screenRect = screen->geometry(); auto x = screenRect.left() + (screenRect.width() - m_window->width()) / 2; auto y = screenRect.top() + (screenRect.height() - m_window->height()) / 2; + + qWarning() << "caimengci position x=" << x << "y=" << y << "window width=" << m_window->width() << "window height=" << m_window->height(); + if (anchors & DLayerShellWindow::AnchorRight) { // https://doc.qt.io/qt-6/qrect.html#right x = (screen->geometry().right() + 1 - m_window->width() - m_dlayerShellWindow->rightMargin()); @@ -153,6 +157,45 @@ void LayerShellEmulation::onPositionChanged() m_window->setGeometry(rect); } +void LayerShellEmulation::onPositionUpdateRequested(int width, int height) +{ + auto anchors = m_dlayerShellWindow->anchors(); + auto screen = m_window->screen(); + auto screenRect = screen->geometry(); + auto x = screenRect.left() + (screenRect.width() - width) / 2; + auto y = screenRect.top() + (screenRect.height() - height) / 2; + + if (anchors & DLayerShellWindow::AnchorRight) { + x = (screen->geometry().right() + 1 - width - m_dlayerShellWindow->rightMargin()); + } + + if (anchors & DLayerShellWindow::AnchorBottom) { + y = (screen->geometry().bottom() + 1 - height - m_dlayerShellWindow->bottomMargin()); + } + if (anchors & DLayerShellWindow::AnchorLeft) { + x = (screen->geometry().left() + m_dlayerShellWindow->leftMargin()); + } + if (anchors & DLayerShellWindow::AnchorTop) { + y = (screen->geometry().top() + m_dlayerShellWindow->topMargin()); + } + + QRect rect(x, y, width, height); + + const bool horizontallyConstrained = anchors.testFlags({DLayerShellWindow::AnchorLeft, DLayerShellWindow::AnchorRight}); + const bool verticallyConstrained = anchors.testFlags({DLayerShellWindow::AnchorTop, DLayerShellWindow::AnchorBottom}); + + if (horizontallyConstrained) { + rect.setX(screen->geometry().left() + m_dlayerShellWindow->leftMargin()); + rect.setWidth(screen->geometry().width() - m_dlayerShellWindow->leftMargin() - m_dlayerShellWindow->rightMargin()); + } + if (verticallyConstrained) { + rect.setY(screen->geometry().top() + m_dlayerShellWindow->topMargin()); + rect.setHeight(screen->geometry().height() - m_dlayerShellWindow->topMargin() - m_dlayerShellWindow->bottomMargin()); + } + + m_window->setGeometry(rect); +} + /** * https://specifications.freedesktop.org/wm-spec/wm-spec-1.4.html#idm45649101327728 */ diff --git a/frame/layershell/x11dlayershellemulation.h b/frame/layershell/x11dlayershellemulation.h index 72aec4d9e..ee713722b 100644 --- a/frame/layershell/x11dlayershellemulation.h +++ b/frame/layershell/x11dlayershellemulation.h @@ -26,6 +26,7 @@ private slots: void onLayerChanged(); // margins or anchor changed void onPositionChanged(); + void onPositionUpdateRequested(int width, int height); void onExclusionZoneChanged(); void onScopeChanged(); // void onKeyboardInteractivityChanged(); diff --git a/panels/notification/bubble/bubblepanel.cpp b/panels/notification/bubble/bubblepanel.cpp index 8c0edc912..949bfbb3f 100644 --- a/panels/notification/bubble/bubblepanel.cpp +++ b/panels/notification/bubble/bubblepanel.cpp @@ -7,6 +7,7 @@ #include "bubblemodel.h" #include "dataaccessorproxy.h" #include "pluginfactory.h" +#include #include #include @@ -54,6 +55,7 @@ bool BubblePanel::init() connect(m_bubbles, &BubbleModel::rowsInserted, this, &BubblePanel::onBubbleCountChanged); connect(m_bubbles, &BubbleModel::rowsRemoved, this, &BubblePanel::onBubbleCountChanged); + setVisible(true); return true; } @@ -111,7 +113,14 @@ void BubblePanel::onNotificationStateChanged(qint64 id, int processedType) void BubblePanel::onBubbleCountChanged() { bool isEmpty = m_bubbles->items().isEmpty(); - setVisible(!isEmpty && enabled()); + + if (isEmpty) { + QTimer::singleShot(400, this, [this]() { + setVisible(false); + }); + } else { + setVisible(!isEmpty && enabled()); + } } void BubblePanel::addBubble(qint64 id) diff --git a/panels/notification/bubble/package/main.qml b/panels/notification/bubble/package/main.qml index 47a0e7de7..872e8e7af 100644 --- a/panels/notification/bubble/package/main.qml +++ b/panels/notification/bubble/package/main.qml @@ -69,8 +69,8 @@ Window { visible: Applet.visible width: 390 - height: Math.max(10, bubbleView.height + bubbleView.anchors.topMargin + bubbleView.anchors.bottomMargin) - DLayerShellWindow.layer: DLayerShellWindow.LayerOverlay + height: 0 + DLayerShellWindow.layer: DLayerShellWindow.LayerTop DLayerShellWindow.anchors: DLayerShellWindow.AnchorBottom | DLayerShellWindow.AnchorRight DLayerShellWindow.topMargin: windowMargin(0) DLayerShellWindow.rightMargin: windowMargin(1) @@ -89,6 +89,11 @@ Window { root.screen = Qt.binding(function () { return Qt.application.screens[0]}) } + // Function to trigger position update with custom width and height + function updatePosition(width, height) { + DLayerShellWindow.requestPositionUpdate(width, height) + } + ListView { id: bubbleView width: 360 @@ -98,13 +103,18 @@ Window { bottom: parent.bottom bottomMargin: 10 rightMargin: 10 - margins: 30 + topMargin: 10 } spacing: 10 model: Applet.bubbles interactive: false verticalLayoutDirection: ListView.BottomToTop + + // Monitor height changes and update position + onHeightChanged: { + updatePosition(390, bubbleView.height + bubbleView.anchors.topMargin + bubbleView.anchors.bottomMargin) + } add: Transition { id: addTrans // Before starting the new animation, forcibly complete the previous notification bubble's animation @@ -129,8 +139,31 @@ Window { } } delegate: Bubble { + id: delegateItem width: 360 bubble: model + + ListView.onRemove: SequentialAnimation { + PropertyAction { + target: delegateItem + property: "ListView.delayRemove" + value: true + } + ParallelAnimation { + NumberAnimation { + target: delegateItem + property: "x" + to: 360 + duration: 400 + easing.type: Easing.InExpo + } + } + PropertyAction { + target: delegateItem + property: "ListView.delayRemove" + value: false + } + } } } }