mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-14 03:11:19 +00:00
fix(gui): scroll the main preset list while its submenu is open
The filament list groups presets by vendor: hovering a vendor in the main popup opens a side popup with that vendor's materials. On GTK the side popup holds the popup grab, and gtk_main_do_event hands every GDK_SCROLL of the popup chain to the grab widget. GDK names the event for the window under the cursor, so the side popup received the wheel events raised over the main list and consumed them: the main list could not be scrolled while a vendor was hovered. The problem shows on both X11 and Wayland, because the rerouting happens in GTK, not in the compositor. The side popup now inspects the event's GDK window and, when the event belongs to the main popup, replays it there. The replay goes through gtk_propagate_event(GetConnectWidget()): wxGTK connects its mouse handlers to the inner client widget of a popup (a wxPizza child of the outer GtkWindow), so a signal emitted on the outer GtkWindow reaches no handler at all. The widget reference in .claude/skills/orca-wxwidgets records the mechanism and the connect-widget fact.
This commit is contained in:
@@ -570,6 +570,11 @@ Details [source] (`Widgets/PopupWindow.cpp`).
|
||||
- On GTK the sub binds an idle handler. While both are shown and the cursor is over main but not over the
|
||||
sub, it builds a `wxMouseEvent(wxEVT_MOTION)` from `wxGetMousePosition()`, calls `mainDropDown->mouseMove(ev)`
|
||||
**directly** (nothing is posted), and calls `e.RequestMore()` to keep polling.
|
||||
- On GTK the sub also receives the wheel events the user raises over main: GDK sends a `wl_pointer.axis` only
|
||||
to the surface the pointer is over, and GTK then hands that `GDK_SCROLL` to the chain's grab widget, which is
|
||||
the sub. The sub installs a `GtkWidget::event` handler that replays the event on main with
|
||||
`gtk_propagate_event(main->GetConnectWidget(), event)` when the event's GDK window belongs to main
|
||||
(`DropDown::messureSize`).
|
||||
- **GTK wrapper sizing.** `messureSize` calls `gtk_window_resize` on `m_widget` after `SetSize`, because wxGTK's
|
||||
`DoSetSize` only uses `gtk_widget_set_size_request` ("gtk_window_resize does not work for GTK_WINDOW_POPUP",
|
||||
`src/gtk/popupwin.cpp:181-188`).
|
||||
@@ -607,6 +612,32 @@ Details [source] (`Widgets/PopupWindow.cpp`).
|
||||
```
|
||||
Cite: c0ae2bda99, b9ff15054f (`DropDown::mouseMove`, `DropDown::messureSize`, `DropDown::ShouldDismissOnTopWindowDeactivate`,
|
||||
`PopupWindow::ShouldDismissOnTopWindowDeactivate`).
|
||||
- **Rule:** On GTK, route a chained popup's wheel event by the GDK window it names, not by the grab widget and not
|
||||
by the global cursor position.
|
||||
**Why:** While the sub holds the grab, `gtk_main_do_event` hands every `GDK_SCROLL` of the chain to the grab
|
||||
widget (the sub) with its original, un-rebased coordinates [source] (`gtkmain.c:1789-1792`, 1923-1924), while
|
||||
GDK Wayland sends the axis to the surface under the pointer only (`pointer_handle_axis` → `create_scroll_event`,
|
||||
`gdkdevice-wayland.c:1929, 1527`). The sub therefore sees and consumes wheel events raised over main, so the
|
||||
main list never scrolls, on X11 and Wayland alike. A hit test against `wxGetMousePosition()` cannot replace the
|
||||
window test: global coordinates are unreliable on Wayland (`mouse-keyboard-focus.md`). `DropDown` connects a
|
||||
`GtkWidget::event` handler on the sub's `GtkWindow` (its `m_widget`), which is emitted before wx's
|
||||
`::scroll-event` handler on the same widget and suppresses it when it returns TRUE (`gtkwidget.c:7696`), then
|
||||
delivers the event with `gtk_propagate_event(main->GetConnectWidget(), event)`. Returning TRUE there also stops
|
||||
the parent-chain propagation.
|
||||
```cpp
|
||||
// Wrong: emitted on the popup's outer GtkWindow, where wx has no handlers at all
|
||||
g_signal_emit_by_name(main->m_widget, "scroll-event", event, &handled);
|
||||
// Right (wxGTK): replay GTK's own delivery on the widget wx listens to
|
||||
gtk_propagate_event(main->GetConnectWidget(), event);
|
||||
```
|
||||
**Where wx listens [source].** `wxPopupWindow::Create` builds a popup from two widgets: `m_widget` is the outer
|
||||
`GtkWindow` and `m_wxwindow` is a `wxPizza` child added to it. `wxWindowGTK::GetConnectWidget()` is
|
||||
`m_wxwindow ? m_wxwindow : m_widget`, and `ConnectWidget()` — which binds `button_press_event`, `motion_notify_event`
|
||||
and `scroll_event` — is called on that widget (`src/gtk/window.cpp` `Create`, `GetConnectWidget`). A signal emitted
|
||||
on the outer `GtkWindow` therefore reaches no wx handler; only events GTK delivers to the inner widget, or to a
|
||||
descendant of the grab widget, are seen by wx. `GTKGetConnectWindow()`/`GTKGetMainWindow()` are the matching
|
||||
GdkWindows.
|
||||
Cite: `DropDown::messureSize`, `DropDown::mouseWheelMoved`.
|
||||
- **Rule:** When shrinking a dropdown to fit above the display bottom, give up if fewer than two rows would
|
||||
remain.
|
||||
**Why:** On Wayland the popup can land where almost no vertical space is left. Clamping to that sliver made an
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/gdicmn.h>
|
||||
#include <wx/window.h>
|
||||
#include <wx/utils.h>
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include <gtk/gtk.h>
|
||||
@@ -665,6 +666,35 @@ void DropDown::messureSize()
|
||||
sub->mainDropDown->mouseMove(ev);
|
||||
e.RequestMore();
|
||||
});
|
||||
// Orca: the submenu holds the GTK grab, so GTK gives it the wheel events of the whole
|
||||
// chain. GDK names the window under the cursor, so re-emit main's events on main.
|
||||
g_signal_connect(sub->m_widget, "event", G_CALLBACK(+[](GtkWidget *, GdkEvent *event, gpointer data) -> gboolean {
|
||||
auto *sub = static_cast<DropDown *>(data);
|
||||
DropDown *main = sub->mainDropDown;
|
||||
if (event->type != GDK_SCROLL || !main || !main->IsShown() || !sub->IsShown())
|
||||
return FALSE;
|
||||
gpointer owner = nullptr;
|
||||
if (event->any.window)
|
||||
gdk_window_get_user_data(event->any.window, &owner);
|
||||
GtkWidget *widget = (owner && GTK_IS_WIDGET(owner)) ? GTK_WIDGET(owner) : nullptr;
|
||||
bool for_main = widget && (widget == main->m_widget || gtk_widget_is_ancestor(widget, main->m_widget));
|
||||
bool for_sub = widget && (widget == sub->m_widget || gtk_widget_is_ancestor(widget, sub->m_widget));
|
||||
if (!for_main && for_sub) {
|
||||
// The compositor may keep the pointer focus on the submenu while the cursor is over main.
|
||||
wxPoint pt((int) event->scroll.x, (int) event->scroll.y);
|
||||
if (sub->GetClientRect().Contains(pt))
|
||||
return FALSE;
|
||||
wxPoint off = sub->GetScreenRect().GetTopLeft() - main->GetScreenRect().GetTopLeft();
|
||||
event->scroll.x += off.x;
|
||||
event->scroll.y += off.y;
|
||||
for_main = true;
|
||||
}
|
||||
if (!for_main)
|
||||
return FALSE;
|
||||
// wx connects its mouse handlers to GetConnectWidget(), the inner widget of the popup.
|
||||
gtk_propagate_event(main->GetConnectWidget(), event);
|
||||
return TRUE;
|
||||
}), sub);
|
||||
#endif
|
||||
subDropDown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) {
|
||||
e.SetEventObject(this);
|
||||
|
||||
Reference in New Issue
Block a user