Skip to content

fix(macos): clamp context-menu popup so it fits on-screen#352

Open
vdavid wants to merge 3 commits into
tauri-apps:devfrom
vdavid:fix/popup-clamp-onscreen
Open

fix(macos): clamp context-menu popup so it fits on-screen#352
vdavid wants to merge 3 commits into
tauri-apps:devfrom
vdavid:fix/popup-clamp-onscreen

Conversation

@vdavid

@vdavid vdavid commented May 5, 2026

Copy link
Copy Markdown

Problem

popUpMenuPositioningItem:atLocation:inView: does not auto-flip or clamp the menu when it would overflow the screen, so right-clicking near the bottom edge of a window produces a menu with scroll arrows instead of one that's fully visible. It looks bad.
Easy repro: open any consumer of ContextMenu::show_context_menu_for_nsview, right-click within ~one menu-height of the bottom of the screen → the menu opens with a downward scroll arrow rather than flipping upward like a standard AppKit contextual menu. Not nice.

Fix

Switch show_context_menu to +[NSMenu popUpContextMenu:withEvent:forView:], which is AppKit's idiomatic contextual-menu entry point and handles auto-flip, screen clamp, menu bar / Dock margins, and multi-monitor positioning natively. Driving it requires an NSEvent, so we synthesize a right-mouse-down event with +[NSEvent mouseEventWithType:location:modifierFlags:timestamp:windowNumber:context:eventNumber:clickCount:pressure:] whose locationInWindow is the popup point in the target view's window's coordinate space.

popUpContextMenu:withEvent:forView: requires a non-nil NSView. The current API already takes a *const c_void view that the caller guarantees is valid, so we pass it through directly. If +mouseEventWithType: ever fails to synthesize an event, the code falls back to popUpMenuPositioningItem:atLocation:inView: so the menu still appears.

Edge cases handled

  • The popup location is computed in the view's window's coordinate space (the same window the synthesized event targets via [window windowNumber]), which is what popUpContextMenu:withEvent:forView: expects.
  • Bottom edge, right edge, menu-bar / Dock margins, and multi-monitor: handled by AppKit's contextual-menu code path. Future macOS improvements to that code path apply automatically.

Behavior change risks

User-visible end result is the same: menus that previously fit on-screen still fit, menus that previously overflowed now flip up / shift inward instead of showing scroll arrows. Internally the implementation now matches Apple's idiom, so future macOS tweaks to contextual-menu positioning come for free.

Cargo.toml

Adds "NSGraphicsContext" to objc2-app-kit features (required by +mouseEventWithType:..., even though we pass nil for the context argument) and "NSDate" to objc2-foundation features (for the timestamp). No NSScreen feature needed — AppKit handles screen geometry internally.

Test plan

I did this:

  • Ran cargo build, cargo test, cargo clippy --all-targets -- -D warnings, cargo fmt --check, they all pass.
  • Manually tested in Cmdr (my file manager): right-clicked near the bottom and right edges of the screen; now it produces a fully visible, properly clamped menu instead of a scrolling one. I did not multi-monitor verify, unfortunately, because I have no secondary screen now. :/

Changelog

Covector entry included as .changes/macos-popup-clamp.md (patch bump).

`popUpMenuPositioningItem:atLocation:inView:` does not auto-flip the
menu when it would overflow the screen, so right-clicking near the
bottom edge produces a menu with scroll arrows.

Switch to `popUpContextMenu:withEvent:forView:`, which AppKit clamps
and auto-flips against the screen's visible frame for free. The popup
location is computed in the view's window's coordinate space and
attached to a synthesized right-mouse-down `NSEvent` that drives the
call, matching how AppKit's own contextual-menu path works.
@vdavid vdavid marked this pull request as draft May 5, 2026 13:32
@vdavid vdavid force-pushed the fix/popup-clamp-onscreen branch 2 times, most recently from fb97acd to bd556b9 Compare May 5, 2026 19:07
@vdavid vdavid marked this pull request as ready for review May 5, 2026 19:14
@vdavid

vdavid commented Jun 1, 2026

Copy link
Copy Markdown
Author

Hi, @amrbashir! Any chance I could get a review on this one? Sorry for not pinging you earlier, I somehow just assumed someone would review the PR.

@amrbashir

Copy link
Copy Markdown
Member

Could you provide a few screenshots, aka before and after? Also do you know if Windows and Linux handle the clamp automatically or do we need to also implement it there as well

`popUpContextMenu:withEvent:forView:` automatically appends contextual-menu plug-in items (AutoFill, Services, third-party plug-ins) to the menu. The legacy positioning call never did that, so opt out via `setAllowsContextMenuPlugIns(false)` to keep the menu's contents exactly what the consumer built.
@vdavid

vdavid commented Jun 6, 2026

Copy link
Copy Markdown
Author

Sure!
Here is a before/after from the repo's tao example.

Setup was: window at the bottom of my screen, 12 extra items added so the menu is taller than the gap below the click point. Then I always right-clicked roughly where you see the top-left of the context menu in the first screenshot below.

Before (popUpMenuPositioningItem:atLocation:inView:)

You see, there is all that space above, and that sad context menu is just sitting down there, with the downward scroll arrow at its bottom:

muda-before-my-pr

(Btw the old path is broken this same way in both the "explicit position" and the "position-less/mouse-location" modes.)

After (popUpContextMenu:withEvent:forView:)

Much nicer, BUT with an unwanted extra item ("AutoFill"):

muda-after-my-pr

(Fixed, see the scrshot below)

After (with setAllowsContextMenuPlugIns(false))

While taking the screenshots, I caught an unintended side effect of my change: popUpContextMenu: lets macOS append its own items to the menu and a divider and "AutoFill" showed up as a result. The legacy call never did that, so I pushed a commit that opts out via setAllowsContextMenuPlugIns(false). Menu contents stay exactly what the consumer built.

muda-after-my-pr-then-fixed

Windows and Linux

On Windows and Linux, no work needed. Both clamp natively on muda's existing calls:

  • Windows: muda calls TrackPopupMenu, and the OS repositions popup menus to fit the work area on its own. (I don't have a Windows box to demo it, sorry.)
  • Linux: muda calls gtk_menu_popup_at_rect, and GtkMenu:anchor-hints defaults to GDK_ANCHOR_FLIP_X | FLIP_Y | SLIDE_X | SLIDE_Y | RESIZE_X | RESIZE_Y, so GTK flips/slides the menu to keep it on the monitor: https://docs.gtk.org/gtk3/property.Menu.anchor-hints.html

Only macOS was broken: popUpMenuPositioningItem: positions manually, without the OS's clamping treatment. And my changes only affect macOS.

WDYT, good to merge?

@vdavid

vdavid commented Jun 8, 2026

Copy link
Copy Markdown
Author

@amrbashir, sorry, forgot to mention you, not sure how your notifications are set up, so just wanted to send a polite ping.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants