From d1586da9dac122856b598d9d3ea05105bb7aa912 Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Wed, 29 Apr 2026 12:42:47 +0800 Subject: [PATCH] fix: resolve flickering and position shift when switching panel popups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor the open/close mechanism for PanelPopup, PanelMenu, and PanelToolTip to eliminate visual flickering and incorrect positioning that occurred when rapidly switching between popups. The previous implementation used a timer-based delayed opening approach, which could cause the old popup to close and the new one to appear at an intermediate frame with incorrect geometry. Changes: 1. Replaced Timer-based delayed opening with a geometry update callback mechanism 2. Added `openPending` property to track pending operations 3. Added `finalizeOpen()` function to complete opening after geometry update 4. Added `onUpdateGeometryFinished` signal handling to coordinate opening with geometry updates 5. Removed `delayed: true` from x/y Binding properties for immediate position updates 6. Added proper handling for reopening when popup is already visible 7. Added `geometryUpdatePending` guard in PanelPopupWindow to prevent duplicate updates 8. Added default initialization values for `m_dragging` and `m_pressing` in popupwindow.h 9. Fixed trailing newline issue in PanelPopupWindow.qml Log: Fixed popup flickering and position issues when switching panels Influence: 1. Test rapid switching between multiple popups/menus/tooltips 2. Verify popup open/close animations are smooth without flickering 3. Test popup positioning accuracy when opened from different screen locations 4. Verify that popups close properly when clicking outside or switching focus 5. Test the "reopen" scenario: close a popup and immediately open the same or a different one 6. Verify that the X11 grab focus transition works correctly after this change 7. Test on both X11 and Wayland sessions if applicable fix: 解决面板弹窗切换时的闪烁和位置偏移问题 重构PanelPopup、PanelMenu和PanelToolTip的打开/关闭机制,消除快速切换弹窗 时出现的视觉闪烁和位置错误。之前的实现使用基于定时器的延迟打开方式,可能 导致旧弹窗关闭后新弹窗在中间帧显示错误的几何位置。 变更内容: 1. 将基于Timer的延迟打开替换为几何更新回调机制 2. 添加`openPending`属性跟踪待处理操作 3. 添加`finalizeOpen()`函数在几何更新完成后完成打开操作 4. 添加`onUpdateGeometryFinished`信号处理,协调打开与几何更新 5. 从x/y绑定属性中移除`delayed: true`,实现即时位置更新 6. 添加弹窗已可见时重新打开的正确处理 7. 在PanelPopupWindow中添加`geometryUpdatePending`防护,防止重复更新 8. 在popupwindow.h中为`m_dragging`和`m_pressing`添加默认初始化值 9. 修复PanelPopupWindow.qml中缺失的换行符 Log: 修复面板切换时弹窗闪烁和位置问题 Influence: 1. 测试多个弹窗/菜单/提示框之间的快速切换 2. 验证弹窗打开/关闭动画是否流畅无闪烁 3. 测试从不同屏幕位置打开时弹窗定位的准确性 4. 验证点击外部区域或切换焦点时弹窗是否正确关闭 5. 测试"重新打开"场景:关闭弹窗后立即打开相同或不同的弹窗 6. 验证此更改后X11抓取焦点过渡功能是否正常工作 7. 如适用,在X11和Wayland会话上测试 --- frame/popupwindow.h | 4 +- frame/qml/PanelMenu.qml | 42 ++++++++++++++++---- frame/qml/PanelPopup.qml | 71 ++++++++++++++++++++-------------- frame/qml/PanelPopupWindow.qml | 18 +++++---- frame/qml/PanelToolTip.qml | 55 ++++++++++++++++++-------- 5 files changed, 128 insertions(+), 62 deletions(-) diff --git a/frame/popupwindow.h b/frame/popupwindow.h index 8a6eb2f54..24481389d 100644 --- a/frame/popupwindow.h +++ b/frame/popupwindow.h @@ -34,8 +34,8 @@ class PopupWindow : public QQuickApplicationWindow private: void setX11GrabFocusTransition(bool transition); - bool m_dragging; - bool m_pressing; + bool m_dragging = false; + bool m_pressing = false; bool m_x11GrabFocusTransition = false; }; DS_END_NAMESPACE diff --git a/frame/qml/PanelMenu.qml b/frame/qml/PanelMenu.qml index 0108cbd22..c6a06efaa 100644 --- a/frame/qml/PanelMenu.qml +++ b/frame/qml/PanelMenu.qml @@ -15,6 +15,7 @@ Item { property int menuX: 0 property int menuY: 0 property bool readyBinding: false + property bool openPending: false // WM_NAME, used for kwin. property string windowTitle: "dde-shell/panelmenu" width: menu.childrenRect.width @@ -32,13 +33,11 @@ Item { } Binding { when: readyBinding - delayed: true target: menuWindow; property: "xOffset" value: control.menuX } Binding { when: readyBinding - delayed: true target: menuWindow; property: "yOffset" value: control.menuY } @@ -53,32 +52,56 @@ Item { if (!menuWindow) return + if (menuWindow.visible) { + menuWindow.close() + menuWindow.currentItem = null + Qt.callLater(function () { + if (!menu.visible) { + control.open() + } + }) + return + } + readyBinding = Qt.binding(function () { return menuWindow && menuWindow.currentItem === control }) menuWindow.currentItem = control Qt.callLater(function () { - menuWindow.title = windowTitle - menuWindow.show() - DS.grabMouse(menuWindow) - DS.grabKeyboard(menuWindow) + if (!menuWindow || menuWindow.currentItem !== control) + return + openPending = true + menuWindow.requestUpdateGeometry() }) } function close() { + openPending = false if (!menuWindow) return if (!readyBinding) return - + menuWindow.close() menuWindow.currentItem = null DS.grabKeyboard(menuWindow, false) } + function finalizeOpen() + { + if (!menuWindow || !openPending || !readyBinding || menuWindow.currentItem !== control) + return + + openPending = false + menuWindow.title = windowTitle + menuWindow.show() + DS.grabMouse(menuWindow) + DS.grabKeyboard(menuWindow) + } + Connections { target: menuWindow function onActiveChanged() @@ -90,6 +113,11 @@ Item { control.close() } } + + function onUpdateGeometryFinished() + { + control.finalizeOpen() + } } Item { diff --git a/frame/qml/PanelPopup.qml b/frame/qml/PanelPopup.qml index b0bdb8995..625c13abe 100644 --- a/frame/qml/PanelPopup.qml +++ b/frame/qml/PanelPopup.qml @@ -15,6 +15,7 @@ Item { property int popupX: 0 property int popupY: 0 property bool readyBinding: false + property bool openPending: false property bool grabInactivePending: false property int grabInactiveTimeout: 200 // WM_NAME, used for kwin. @@ -34,13 +35,11 @@ Item { } Binding { when: readyBinding - delayed: true target: popupWindow; property: "xOffset" value: control.popupX } Binding { when: readyBinding - delayed: true target: popupWindow; property: "yOffset" value: control.popupY } @@ -61,6 +60,12 @@ Item { if (popupWindow.visible) { popupWindow.close() popupWindow.currentItem = null + Qt.callLater(function () { + if (!popup.visible) { + control.open() + } + }) + return } readyBinding = Qt.binding(function () { @@ -68,24 +73,41 @@ Item { }) popupWindow.currentItem = control - timer.start() + openPending = true + Qt.callLater(function () { + if (!popupWindow || !openPending || !readyBinding || popupWindow.currentItem !== control) + return + popupWindow.requestUpdateGeometry() + }) } - Timer { - id: timer - interval: 10 - onTriggered: { - if (!popupWindow) - return + function close() + { + openPending = false + grabInactivePending = false + grabInactiveTimer.stop() + if (!popupWindow) + return - if (!readyBinding) - return + // avoid to closing window by other PanelPopup. + if (!readyBinding) + return - popupWindow.title = windowTitle - popupWindow.show() - popupWindow.requestActivate() - } + popupWindow.close() + popupWindow.currentItem = null } + + function finalizeOpen() + { + if (!popupWindow || !openPending || !readyBinding || popupWindow.currentItem !== control) + return + + openPending = false + popupWindow.title = windowTitle + popupWindow.show() + popupWindow.requestActivate() + } + Timer { id: grabInactiveTimer interval: control.grabInactiveTimeout @@ -100,20 +122,6 @@ Item { } } } - function close() - { - grabInactivePending = false - grabInactiveTimer.stop() - if (!popupWindow) - return - - // avoid to closing window by other PanelPopup. - if (!readyBinding) - return - - popupWindow.close() - popupWindow.currentItem = null - } Connections { target: popupWindow @@ -140,6 +148,11 @@ Item { } } + function onUpdateGeometryFinished() + { + control.finalizeOpen() + } + function onX11FocusOutByGrab() { if (!popupWindow || !readyBinding || !popup.visible || popupWindow.currentItem !== control) { diff --git a/frame/qml/PanelPopupWindow.qml b/frame/qml/PanelPopupWindow.qml index 4de58dd69..78a452fec 100644 --- a/frame/qml/PanelPopupWindow.qml +++ b/frame/qml/PanelPopupWindow.qml @@ -17,6 +17,7 @@ PopupWindow { property Item currentItem property int requestedWidth: 10 property int requestedHeight: 10 + property bool geometryUpdatePending: false signal requestUpdateGeometry() signal updateGeometryFinished() @@ -135,12 +136,15 @@ PopupWindow { onYOffsetChanged: requestUpdateGeometry() onRequestUpdateGeometry: { - if (updateGeometryer) { - Qt.callLater(function () { - updateGeometryer() - updateGeometryFinished() - }) - } + if (!updateGeometryer || geometryUpdatePending) + return + + geometryUpdatePending = true + Qt.callLater(function () { + geometryUpdatePending = false + updateGeometryer() + updateGeometryFinished() + }) } D.StyledBehindWindowBlur { @@ -163,4 +167,4 @@ PopupWindow { DStyle.Style.behindWindowBlur.darkNoBlurColor) } } -} \ No newline at end of file +} diff --git a/frame/qml/PanelToolTip.qml b/frame/qml/PanelToolTip.qml index 1ef671329..0141df5dc 100644 --- a/frame/qml/PanelToolTip.qml +++ b/frame/qml/PanelToolTip.qml @@ -18,6 +18,7 @@ Item { property int toolTipX: 0 property int toolTipY: 0 property bool readyBinding: false + property bool openPending: false // WM_NAME, used for kwin. property string windowTitle: "dde-shell/paneltooltip" width: toolTip.width @@ -35,13 +36,11 @@ Item { } Binding { when: readyBinding - delayed: true target: toolTipWindow; property: "xOffset" value: control.toolTipX - (toolTip.leftPadding + toolTip.rightPadding) / 2 } Binding { when: readyBinding - delayed: true target: toolTipWindow; property: "yOffset" value: control.toolTipY } @@ -51,31 +50,34 @@ Item { if (!toolTipWindow) return + if (toolTipWindow.visible) { + toolTipWindow.close() + toolTipWindow.currentItem = null + Qt.callLater(function () { + if (!toolTip.visible) { + control.open() + } + }) + return + } + readyBinding = Qt.binding(function () { return toolTipWindow && toolTipWindow.currentItem === control }) toolTipWindow.currentItem = control - timer.start() - } - - Timer { - id: timer - interval: 10 - onTriggered: { - if (!toolTipWindow) + openPending = true + Qt.callLater(function () { + if (!toolTipWindow || !openPending || !readyBinding || toolTipWindow.currentItem !== control) return - if (!readyBinding) - return - - toolTipWindow.title = windowTitle - toolTipWindow.show() - } + toolTipWindow.requestUpdateGeometry() + }) } - + function close() { + openPending = false if (!toolTipWindow) return @@ -85,11 +87,30 @@ Item { toolTipWindow.close() toolTipWindow.currentItem = null } + function hide() { close() } + function finalizeOpen() + { + if (!toolTipWindow || !openPending || !readyBinding || toolTipWindow.currentItem !== control) + return + + openPending = false + toolTipWindow.title = windowTitle + toolTipWindow.show() + } + + Connections { + target: toolTipWindow + function onUpdateGeometryFinished() + { + control.finalizeOpen() + } + } + Control { id: toolTip visible: readyBinding