Skip to content

Add macOS Cocoa drag&drop handler for drop-zone highlight#6181

Merged
Grantim merged 4 commits into
masterfrom
agent/mlib-7373-macos-dragdrop-handler
May 29, 2026
Merged

Add macOS Cocoa drag&drop handler for drop-zone highlight#6181
Grantim merged 4 commits into
masterfrom
agent/mlib-7373-macos-dragdrop-handler

Conversation

@meshinspector-agent

Copy link
Copy Markdown
Contributor

Context

On macOS the drop-zone highlight shown while dragging a file over the window never appears, although the file drop itself works. The highlight is driven by the viewer's dragEntranceSignal / dragOverSignal, emitted by native per-platform handlers — but getDragDropHandler() returned null on macOS, so no drag enter/over/leave events ever reached the UI.

Changes

  • Add MRDragDropCocoaHandler (.h pure-C++ PIMPL + .mm implementation), modeled on MRTouchpadCocoaHandler: it injects NSDraggingDestination methods into the GLFW content view via the Objective-C runtime.
    • draggingEntered: chains to GLFW's implementation and emits dragEntranceSignal(true).
    • draggingUpdated: emits dragOverSignal(x, y), converting the Cocoa location to the top-left-origin, framebuffer-pixel convention used by the Win32/Wasm handlers.
    • draggingExited: emits dragEntranceSignal(false).
    • performDragOperation: is left untouched, so GLFW keeps delivering the actual drop via dragDropSignal (which also clears the highlight).
  • Wire getDragDropHandler() to return the Cocoa handler under __APPLE__.

No CMake / .vcxproj changes: *.mm is auto-globbed on Apple and AppKit is already linked; the handler never enters the Windows build.

Verification

  • Builds: the non-Apple factory path (MRDragDropHandler.cpp) compiles cleanly on the Linux CI image (g++-12 -std=c++20 -fsyntax-only). The Objective-C++ .mm is __APPLE__-only, so the macOS build is its first real compile.
  • Not exercisable here: the highlight is a macOS-only UI behavior; it cannot be driven on the Linux runner (no AppKit; the .mm is __APPLE__-guarded), so there is no MCP/automated path to it on this CI.
  • Manual macOS check (pre-merge): with upload-binaries, CI publishes the macOS build — download it and confirm that dragging a file over the window highlights the scene-list and viewport drop zones on enter/move and clears them on leave/drop, and that dropping still loads the file.

CI

upload-binaries — to publish the macOS artifact for the manual drag&drop check above.

On macOS getDragDropHandler() returned null, so the viewer never received
drag enter/over/leave events and the drop-zone highlight never appeared,
even though the file drop itself already works through GLFW. Inject the
NSDraggingDestination methods into the GLFW content view to emit the
existing dragEntranceSignal/dragOverSignal, mirroring the Win32 handler.
@meshinspector-agent meshinspector-agent Bot added the upload-binaries Upload built installers and packages to the Releases page label May 29, 2026
@Grantim

Grantim commented May 29, 2026

Copy link
Copy Markdown
Contributor

please fix builds

check_vcxproj.py requires every .h under source/ to be registered in the
matching .vcxproj, which failed the config job: this macOS-only PIMPL
header is deliberately not part of the Windows build. Add it to
IGNORED_FILENAMES next to the sibling Cocoa headers (MRFileDialogCocoa.h,
MRTouchpadCocoaHandler.h).
@meshinspector-agent

Copy link
Copy Markdown
Contributor Author

Addressing "please fix builds" — the whole red cascade traced back to a single failing check, config / prepare-config:

  • Root causescripts/check_vcxproj.py requires every .h under source/ to be registered in the matching .vcxproj. The new macOS-only header MRDragDropCocoaHandler.h is deliberately not part of the Windows build, so the check reported it as a missing ClInclude and prepare-config exited 1. Fixed in f251bb9 by adding it to IGNORED_FILENAMES, right next to the sibling Cocoa PIMPL headers (MRFileDialogCocoa.h, MRTouchpadCocoaHandler.h) — the existing convention for macOS-only headers excluded from the VS project. (.mm files aren't checked, so no other entry was needed.)

  • Why everything else was red — all downstream of that one failure:

    • update-dev-documentation — the build/versioning jobs gate on prepare-config, so they were skipped; with no PythonStubs/CBindings artifacts produced, the doc job failed.
    • test-distribution / macos-test — with versioning skipped, release_tag was empty, so it downloaded the stale latest release (3.1.2.192) instead of a fresh PR package. That old binary links libjsoncpp.26.dylib, which current Homebrew jsoncpp 1.9.7 no longer ships → dyld abort. Unrelated to this PR's source; master's macos-test is green because it tests a freshly-built package.
  • State nowconfig / prepare-config is ✅ on f251bb9 and the build/test jobs are no longer skipped (queued/running), so the doc and macos-test cascades clear once the fresh package builds.

Verified locally (MeshInspector build image): reproduced the check failing on the old head and passing after the fix; compiled the PR's only changed translation unit MRDragDropHandler.cpp cleanly with the real CI CMake config (on Linux it's a no-op — the Cocoa include is __APPLE__-guarded).

Not verifiable here: the Objective-C++ MRDragDropCocoaHandler.mm and the drop-zone highlight are macOS/AppKit-only — no AppKit toolchain on the Linux runner and no MCP-drivable surface — so the now-unblocked macos-build-test job is their first real compile, and the manual drag&drop check from the PR description still applies.

@Grantim

Grantim commented May 29, 2026

Copy link
Copy Markdown
Contributor

Issue: the drop-zone highlight sometimes stays stuck because the "leave" (dragEntranceSignal(false)) never arrives. The handler clears the highlight only from draggingExited:, but AppKit does not send draggingExited: in several terminal cases:

  • Accepted drop → AppKit sends performDragOperation:, not draggingExited:. For file drops that's fine (GLFW's performDragOperation: fires the drop callback → dragDrop_() clears the flag). But GLFW returns YES without calling the drop callback when the pasteboard has no file URLs — and GLFW registers NSPasteboardTypeURL as well, so a non-file URL drag (a Safari tab / link / bookmark) enters → highlight on, then drops → no drop callback and no exit → stuck.
  • Cancelled / interrupted sessions: Esc while inside the window; Cmd-Tab / Mission Control / switching Space or display / source app quitting mid-drag; or a fast flick out coinciding with focus loss. None of these guarantee draggingExited:.

draggingExited: is documented only for "the image exits the view's bounds", so relying on it as the sole clear path is the root cause. (Win32 avoids this because OLE's IDropTarget guarantees exactly one of Drop/DragLeave after DragEnter; Cocoa's only outcome-independent terminal callback is draggingEnded:.)

Fix (scoped to MRDragDropCocoaHandler.mm): also inject draggingEnded: — the callback AppKit sends to the destination at the end of every session (drop, exit, or cancel; macOS 10.7+) — and emit dragEntranceSignal(false) from it, exactly like onDraggingExited does (registry guard → emplaceEvent("Drag leave", … dragEntranceSignal(false))postEmptyEvent()).

  • Declare onDraggingEnded alongside the other static callbacks in Impl.
  • Install it with the same class_addMethod / class_replaceMethod pattern as draggingExited:; selector @selector(draggingEnded:), type encoding "v@:@".
  • Keep draggingExited: as-is (still correct for a genuine bounds-exit). The double-clear is idempotent — dragging_ = false twice, and after a real file drop dragDrop_ already cleared it — so firing on draggingEnded: after a normal drop is harmless. No need to touch the enter path.

Verification — this can't be exercised on CI (no macOS/AppKit on the runners, and the .mm is __APPLE__-only), so no automated check is expected here. As a manual pre-merge pass on the upload-binaries macOS build, the highlight should clear in each of:

  1. drag a file in, then press Esc (cancel while still inside the window);
  2. drag a non-file item in (e.g. a Safari tab / link) and release it on the window;
  3. drag a file in, then trigger Mission Control / switch Space mid-drag;
  4. regression: a normal file drop still loads the file and clears the highlight.

draggingExited: only fires on a genuine bounds-exit, so it is not guaranteed
in every terminal dragging case: an accepted non-file URL drop (GLFW returns
YES without firing its drop callback) or a cancelled session (Esc, Mission
Control, Space/display switch, source app quitting) left the highlight stuck.
Hook draggingEnded: -- sent to the destination at the end of every session --
to also emit dragEntranceSignal(false). The double-clear is idempotent.
@meshinspector-agent

Copy link
Copy Markdown
Contributor Author

Addressing the review — agreed, relying on draggingExited: as the sole clear path is the bug, and draggingEnded: is the right terminal hook (it's the only outcome-independent callback AppKit guarantees after a session starts). Implemented in 53e7e84, scoped to MRDragDropCocoaHandler.mm, enter/over/exit paths untouched:

  • "also inject draggingEnded:" — declared onDraggingEnded alongside the other static callbacks; installed it with the same class_addMethodclass_replaceMethod fallback used for draggingExited: (selector @selector(draggingEnded:), type encoding "v@:@").
  • "emit dragEntranceSignal(false) exactly like onDraggingExited"onDraggingEnded mirrors it: registry guard → emplaceEvent("Drag leave", … dragEntranceSignal(false))postEmptyEvent(). As you noted, the double-clear is idempotent (with draggingExited: on a genuine bounds-exit, and with the post-drop clear after a real file drop), so a normal drop is unaffected.
  • Kept draggingExited: and the enter path as-is; updated the destructor comment to note draggingEnded: also stays installed.

Verification — consistent with your note, there's no automated check possible here: source/MRViewer/CMakeLists.txt globs *.mm only under IF(APPLE), so the file isn't part of the Linux build and there's no AppKit/Objective-C++ toolchain on the runner to even syntax-check it. So no build/MCP gate ran for this change. First real compile is the macOS upload-binaries artifact; the four manual scenarios you listed remain the pre-merge pass:

  1. file in → Esc (cancel while inside the window);
  2. non-file item (Safari tab/link) released on the window;
  3. file in → Mission Control / Space switch mid-drag;
  4. regression: a normal file drop still loads the file and clears the highlight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Grantim Grantim merged commit a1b84fb into master May 29, 2026
49 of 50 checks passed
@Grantim Grantim deleted the agent/mlib-7373-macos-dragdrop-handler branch May 29, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

upload-binaries Upload built installers and packages to the Releases page

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants