Skip to content

Editor scrollbar (gtk4::Scrollbar overlay) intercepts clicks meant for hover-popup scrollbar #486

Description

@JDonaghy

Symptom

When the LSP editor hover popup is open and tall enough to need its own scrollbar, the popup's scrollbar is painted via Cairo on the editor DrawingArea. When the popup is wide enough that its right edge overlaps the editor's vertical scrollbar (i.e., when the window is narrow), clicks on the popup scrollbar go to the editor scrollbar instead of operating the popup. The popup scrollbar appears non-interactive in that region, while the editor scrolls under it.

With a wider editor (no overlap), popup scrollbar works correctly.

Root cause

The editor's vertical scrollbar is a native gtk4::Scrollbar widget added via overlay.add_overlay(&vertical):

  • src/gtk/mod.rs:5214let vertical = gtk4::Scrollbar::new(gtk4::Orientation::Vertical, Some(&v_adj));
  • src/gtk/mod.rs:5241overlay.add_overlay(&vertical);

The popup is Cairo-painted on the editor DrawingArea, but in the GTK widget tree the gtk4::Scrollbar overlay sits on top of the DA. GTK widget hit-testing routes clicks to the topmost widget at the click position. So clicks landing on the gtk4::Scrollbar widget (regardless of what the DA paints there visually) are intercepted by the scrollbar widget, never reach the DA, and never reach handle_mouse_click_msg.

vimcode's modal stack (reconcile_editor_hover_modal + dispatch_click with editor_hover registered) cannot arbitrate this because the click event never enters the modal-stack dispatch path. The modal stack only sees clicks the DA receives.

Why this is pre-existing, not caused by #469

The bug exists identically on the legacy quadraui_gtk::draw_rich_text_popup shim path and on the migrated Surface::RichTextPopup path. Both produce identical paint geometry and identical popup_layout.link_hit_regions / popup_layout.scrollbar. The migration in #469 was incorrectly suspected of causing this; user confirmed the same symptom reproduces on `develop` (with the shim) when the scrollbars overlap.

Surfaced during #469 investigation. Filed as separate issue so #469 can close.

Repro

  1. Open a file in vimcode.
  2. Make the editor window narrow enough that the popup scrollbar would overlap the editor vertical scrollbar (typically <100ch of viewport width).
  3. Hover an LSP symbol with rich documentation (markdown body long enough to need scrolling, e.g. `String` in Rust).
  4. Click on the popup's right-edge scrollbar where it visually overlaps the editor scrollbar.
  5. Observed: editor scrolls, popup scrollbar doesn't move.
  6. Expected: popup scrolls.

Fix options

Each is per-backend GTK wiring; the underlying primitive geometry is already shared.

Option A: Hide the native scrollbar when editor_hover is visible (cheap)

In `sync_window_scrollbars` (or similar per-frame sync), when `engine.editor_hover.is_some()`, set `scrollbars.vertical.set_can_target(false)` for the active window. When the hover dismisses, restore. Pros: ~5 lines. Cons: scrollbar still paints but is unclickable; visually confusing. Disables scrolling on the rest of the editor while hovering (probably fine — hover dismisses on any outside click anyway).

Option B: Hide the native scrollbar entirely when editor_hover overlaps it (more precise)

Same as A but only set `can_target(false)` if the hover popup_rect overlaps the scrollbar's allocation. Requires inspecting `scrollbars.vertical.allocation()` against `editor_hover_popup_rect`. ~15 lines.

Option C: Replace the native gtk4::Scrollbar with a Cairo-painted Surface::Scrollbar (right long-term answer)

Mirror what the horizontal scrollbar does (`draw_h_scrollbars` at `src/gtk/draw.rs:990` — uses `Surface::Scrollbar` + `engine.scroll_surfaces.push(ScrollSurface)` so `dispatch_click` arbitrates against the modal stack). Drop the native widget. Pros: unifies vertical + horizontal scrollbar code paths, eliminates GTK widget-tree z-order issues for every modal that overlaps the scrollbar (not just editor_hover). Cons: requires drag handling via `DragState::ScrollbarY` instead of the native widget's drag, requires cursor-indicator repaint logic to move into Cairo too. ~100–150 lines.

Recommended path

Option C in the long run (cleans up the entire per-backend scrollbar story and works for any future popup), but Option A as an immediate stopgap is reasonable if a release is imminent.

Related

Trace data confirming root cause

From #469 user smoke test, click data with editor_hover modal correctly registered:

```
[#469] reconcile_editor_hover_modal: visible=true rect=Some((199.0, 244.0, 880.0, 462.0)) links=2 sb=true
[#469] dispatch_click @(326,379) -> 1 events; modal_top=Some("editor_hover") surfaces=0
[#469] event: MouseDown { widget: Some(WidgetId("editor_hover")), ... }
[#469] reached editor_hover check
[#469] on_popup=true rect=Some((199.0, 244.0, 880.0, 462.0))
[#469] link_hit=Some("command:definition")
```

When clicks reach the DA, the modal-stack arbitration works. The overlap-bug clicks never appear in this log at all — they get consumed by the gtk4::Scrollbar widget before the DA's mouse-click controller fires.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinginfrastructureBuild, CI, distributionuiUI/rendering

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions