From c0ae2bda998a43ca5395ac3a00afb2c653176f8d Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 11 May 2026 01:32:59 +0800 Subject: [PATCH] Fix DropDown submenus on Linux/Wayland (#13563) Hovering a group item (e.g. "Generic" in the filament list) now opens its submenu correctly. The dropdown stays usable while moving the cursor between the parent list and the submenu, and dismisses normally when the user clicks outside. --- src/slic3r/GUI/Widgets/DropDown.cpp | 37 ++++++++++++++++++++++++++ src/slic3r/GUI/Widgets/DropDown.hpp | 2 ++ src/slic3r/GUI/Widgets/PopupWindow.cpp | 3 ++- src/slic3r/GUI/Widgets/PopupWindow.hpp | 6 +++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Widgets/DropDown.cpp b/src/slic3r/GUI/Widgets/DropDown.cpp index 59832067c8..d698f9f7dc 100644 --- a/src/slic3r/GUI/Widgets/DropDown.cpp +++ b/src/slic3r/GUI/Widgets/DropDown.cpp @@ -563,6 +563,34 @@ void DropDown::messureSize() subDropDown->text_off = text_off; subDropDown->use_content_width = true; subDropDown->Create(GetParent()); +#ifdef __WXGTK__ + // Orca: Keep the wx parent as the combobox so wxPopupTransientWindow installs + // its capture handlers on the main dropdown, but make the native GTK + // popup transient for the currently open popup to satisfy Wayland's + // xdg-shell rule that a popup's parent must be the topmost mapped popup. + gtk_window_set_transient_for(GTK_WINDOW(subDropDown->GetHandle()), GTK_WINDOW(GetHandle())); + // Orca: On Wayland, while the sub holds an xdg_popup grab, motion events for + // the cursor over main may not be delivered (Mutter drops motion + // outside the grabbing surface). Poll on idle and synthesize a + // mouseMove on main so its hover highlight tracks and it can dismiss + // the sub when the cursor leaves the parent (group) item. + DropDown* sub = subDropDown; + sub->Bind(wxEVT_IDLE, [sub](wxIdleEvent& e) { + e.Skip(); + if (!sub->IsShown() || !sub->mainDropDown->IsShown()) + return; + wxPoint screen_pt = wxGetMousePosition(); + if (sub->GetScreenRect().Contains(screen_pt) || !sub->mainDropDown->GetScreenRect().Contains(screen_pt)) + return; + wxPoint main_pt = sub->mainDropDown->ScreenToClient(screen_pt); + wxMouseEvent ev(wxEVT_MOTION); + ev.SetEventObject(sub->mainDropDown); + ev.m_x = main_pt.x; + ev.m_y = main_pt.y; + sub->mainDropDown->mouseMove(ev); + e.RequestMore(); + }); +#endif subDropDown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) { e.SetEventObject(this); e.SetId(GetId()); @@ -759,6 +787,15 @@ void DropDown::Dismiss() PopupWindow::Dismiss(); } +bool DropDown::ShouldDismissOnTopWindowDeactivate() +{ + // On Wayland, mapping a chained xdg_popup with grab makes the parent + // toplevel inactive, which would otherwise cascade-dismiss the whole + // chain. Skip when our chain peer is shown. + return !((mainDropDown && mainDropDown->IsShown()) || + (subDropDown && subDropDown->IsShown())); +} + void DropDown::OnDismiss() { if (mainDropDown) { diff --git a/src/slic3r/GUI/Widgets/DropDown.hpp b/src/slic3r/GUI/Widgets/DropDown.hpp index 8b7ab19d61..639a13e526 100644 --- a/src/slic3r/GUI/Widgets/DropDown.hpp +++ b/src/slic3r/GUI/Widgets/DropDown.hpp @@ -110,6 +110,8 @@ protected: void OnDismiss() override; + bool ShouldDismissOnTopWindowDeactivate() override; + private: void paintEvent(wxPaintEvent& evt); void paintNow(); diff --git a/src/slic3r/GUI/Widgets/PopupWindow.cpp b/src/slic3r/GUI/Widgets/PopupWindow.cpp index a8412bcfc9..8fb1cda774 100644 --- a/src/slic3r/GUI/Widgets/PopupWindow.cpp +++ b/src/slic3r/GUI/Widgets/PopupWindow.cpp @@ -86,7 +86,8 @@ void PopupWindow::OnMouseEvent2(wxMouseEvent &evt) void PopupWindow::topWindowActiavate(wxActivateEvent &event) { event.Skip(); - if (!event.GetActive() && IsShown()) DismissAndNotify(); + if (!event.GetActive() && IsShown() && ShouldDismissOnTopWindowDeactivate()) + DismissAndNotify(); } #endif diff --git a/src/slic3r/GUI/Widgets/PopupWindow.hpp b/src/slic3r/GUI/Widgets/PopupWindow.hpp index 55231accce..45afea8213 100644 --- a/src/slic3r/GUI/Widgets/PopupWindow.hpp +++ b/src/slic3r/GUI/Widgets/PopupWindow.hpp @@ -17,6 +17,12 @@ public: #ifdef __WXMSW__ void BindUnfocusEvent(); #endif +protected: + // Orca: Hook so derived classes (e.g. DropDown chains) can skip auto-dismissal + // when the toplevel deactivates as a side effect of their own popup grab + // (notably on Wayland, where mapping a chained xdg_popup with grab makes + // the parent toplevel briefly inactive). + virtual bool ShouldDismissOnTopWindowDeactivate() { return true; } private: #ifdef __WXOSX__ void OnMouseEvent2(wxMouseEvent &evt);