Question

Let's say I have a floating, borderless, circular NSWindow.

It is circular because the content view simply draws a red circle.

That content view needs to be layer-backed ([contentView setWantsLayer:YES]), because I'm applying CoreAnimations on it, e.g., animated scaling.

Usually, the clickable area of a NSWindow is defined by the transparency of the pixels of the content view. However, once the content view of a NSWindow becomes layer-backed, transparent areas will also receive clicks, unfortunately.

In my case, this is a serious problem, because I only want to receive clicks within the radius. But now, a click within the rect of the window, but beyond the circle radius, will activate the window (and thus, the entire app), which it shouldn't. Also the window is draggable via the corner of its content view.

click location

My initial thought was to implement [NSWindow sendEvent:] in a subclass and check whether the click was performed within the radius, using [theEvent locationInWindow]. I thought I could simply discard the event, if it's beyond the radius, by not calling [super sendEvent:theEvent] then. This however did not work: I noticed, that the mouseDown:; window method is called even before the sendEvent:; method.

I've search a lot but the only idea I found, was to have a proxy like non-layer backed NSWindow on top of the window, which delegates clicks conditionally, but this led to unpredictable UI behavior.

Do you guys have any idea, how to solve it?

Was it helpful?

Solution

So after a few weeks, I came to the following results:

A) Proxy window: Make use of a non layer-backed proxy window, which is placed on top of the target window as a child window. The proxy window has the same shape, as the target window, and since it is not layer-backed, it will properly receive and ignore events. The proxy window delegates all events to the target window by overwriting sendEvent:. The target window is set to ignore all mouse events.

B) Global Mouse Pointer observation: Install both a global and local event monitor for NSMouseMovedMask|NSLeftMouseDraggedMask events using addGlobalMonitorForEventsMatchingMask and addLocalMonitorForEventsMatchingMask. The event monitors disable and enable ignoring mouse events on all registered target windows based on the current global mouse position. In the case of circular windows, the distance between the mouse pointer and every target window must be calculated.

Both approaches work well in generally, but I've been experiencing some unpredictable misbehaviors of the child window approach (where the child window is 'out-of-sync' of its parent's position).

UPDATE: Both approaches have some significant disadvantages: In A), the proxy window sometimes may be out of sync and may be placed slightly off the actual window.

In B), the event monitor has a big impact on battery life while moving the mouse, even if the app is not the front-most application.

OTHER TIPS

If you want to Discard mouseDown event based on position you can use the:

CGPathContainsPoint(path,transform,point,eoFill):Bool

Setup your path to match your graphics. Circles, ellipses, rectangles, triangles or paths and even compositional paths (paths with holes in them).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top