Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/gl/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ impl GlContext {
let _: () = msg_send![self.view, setNeedsDisplay: YES];
}
}

/// Pointer to the `NSOpenGLView` this context renders into. Used by
/// the parent `NSView`'s `hitTest:` override to collapse hits on the
/// render subview to the parent, so AppKit routes `mouseDown:` on
/// first click in non-key windows.
pub(crate) fn ns_view(&self) -> id {
self.view
}
}

impl Drop for GlContext {
Expand Down
9 changes: 9 additions & 0 deletions src/gl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,13 @@ impl GlContext {
pub(crate) fn resize(&self, size: cocoa::foundation::NSSize) {
self.context.resize(size);
}

/// Pointer to the `NSOpenGLView` this context renders into. Used by
/// the parent `NSView`'s `hitTest:` override to collapse hits on the
/// render subview to the parent, so AppKit routes `mouseDown:` on
/// first click in non-key windows.
#[cfg(target_os = "macos")]
pub(crate) fn ns_view(&self) -> cocoa::base::id {
self.context.ns_view()
}
}
49 changes: 49 additions & 0 deletions src/macos/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ unsafe fn create_view_class() -> &'static Class {
sel!(viewWillMoveToWindow:),
view_will_move_to_window as extern "C" fn(&Object, Sel, id),
);
class.add_method(
sel!(hitTest:),
hit_test as extern "C" fn(&Object, Sel, NSPoint) -> id,
);
class.add_method(
sel!(updateTrackingAreas:),
update_tracking_areas as extern "C" fn(&Object, Sel, id),
Expand Down Expand Up @@ -346,6 +350,51 @@ unsafe fn reinit_tracking_area(this: &Object, tracking_area: *mut Object) {
];
}

/// `hitTest:` override that collapses hits on baseview's internal
/// OpenGL render subview to this NSView.
///
/// `src/gl/macos.rs` attaches an `NSOpenGLView` as a subview of this
/// view so the GL context is isolated from event handling. The side
/// effect is that `[NSView hitTest:]` returns the GL subview for
/// every click inside our frame — `NSOpenGLView` inherits the
/// default `acceptsFirstMouse:` which returns `NO`, so AppKit treats
/// the first click in a non-key window as an activation click and
/// never dispatches `mouseDown:`. That's the "first click dead zone"
/// symptom reported in baseview#129 / #202 / #169.
///
/// Fix: if the hit lands on our own GL render subview (pointer
/// equality against the `NSOpenGLView` stored in `GlContext`),
/// collapse the result to `self`. AppKit then asks US about
/// `acceptsFirstMouse:` (we return `YES`), and `mouseDown:` is
/// dispatched on the first click. Hits on any other subview pass
/// through unchanged — we only redirect our own render child, not
/// anything the consumer may add.
///
/// No-op without the `opengl` feature: there's no GL subview to
/// collapse, so the override pass-through is equivalent to the
/// default implementation.
extern "C" fn hit_test(this: &Object, _sel: Sel, point: NSPoint) -> id {
let super_result: id = unsafe {
let superclass = msg_send![this, superclass];
msg_send![super(this, superclass), hitTest: point]
};
if super_result == nil {
return nil;
}

#[cfg(feature = "opengl")]
unsafe {
let state = WindowState::from_view(this);
if let Some(gl_context) = state.window_inner.gl_context.as_ref() {
if super_result == gl_context.ns_view() {
return this as *const _ as id;
}
}
}
Comment on lines +386 to +393
Copy link
Copy Markdown
Member

@micahrj micahrj Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really strange way to achieve this. Here is a list of alternative approaches that I think would be better, in order from simple to complex:

  1. We literally store a pointer to the specific NSOpenGLView you are checking for in the view field of GlContext. Just check if super_result is equal to that view pointer. I don't know why you would feel the need to check the class of the NSView.
  2. Subclass NSOpenGLView and override hitTest to always return nil.
  3. Get rid of the NSOpenGLView entirely and just create an NSOpenGLContext instead.


super_result
}

extern "C" fn view_will_move_to_window(this: &Object, _self: Sel, new_window: id) {
unsafe {
let tracking_areas: *mut Object = msg_send![this, trackingAreas];
Expand Down
2 changes: 1 addition & 1 deletion src/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub(super) struct WindowInner {
ns_view: id,

#[cfg(feature = "opengl")]
gl_context: Option<GlContext>,
pub(super) gl_context: Option<GlContext>,
}

impl WindowInner {
Expand Down
Loading