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.
This commit is contained in:
SoftFever
2026-05-11 01:32:59 +08:00
committed by GitHub
parent d37444b34a
commit c0ae2bda99
4 changed files with 47 additions and 1 deletions

View File

@@ -563,6 +563,34 @@ void DropDown::messureSize()
subDropDown->text_off = text_off; subDropDown->text_off = text_off;
subDropDown->use_content_width = true; subDropDown->use_content_width = true;
subDropDown->Create(GetParent()); 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) { subDropDown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) {
e.SetEventObject(this); e.SetEventObject(this);
e.SetId(GetId()); e.SetId(GetId());
@@ -759,6 +787,15 @@ void DropDown::Dismiss()
PopupWindow::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() void DropDown::OnDismiss()
{ {
if (mainDropDown) { if (mainDropDown) {

View File

@@ -110,6 +110,8 @@ protected:
void OnDismiss() override; void OnDismiss() override;
bool ShouldDismissOnTopWindowDeactivate() override;
private: private:
void paintEvent(wxPaintEvent& evt); void paintEvent(wxPaintEvent& evt);
void paintNow(); void paintNow();

View File

@@ -86,7 +86,8 @@ void PopupWindow::OnMouseEvent2(wxMouseEvent &evt)
void PopupWindow::topWindowActiavate(wxActivateEvent &event) void PopupWindow::topWindowActiavate(wxActivateEvent &event)
{ {
event.Skip(); event.Skip();
if (!event.GetActive() && IsShown()) DismissAndNotify(); if (!event.GetActive() && IsShown() && ShouldDismissOnTopWindowDeactivate())
DismissAndNotify();
} }
#endif #endif

View File

@@ -17,6 +17,12 @@ public:
#ifdef __WXMSW__ #ifdef __WXMSW__
void BindUnfocusEvent(); void BindUnfocusEvent();
#endif #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: private:
#ifdef __WXOSX__ #ifdef __WXOSX__
void OnMouseEvent2(wxMouseEvent &evt); void OnMouseEvent2(wxMouseEvent &evt);