• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
The NSTrackingArea Report
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

The NSTrackingArea Report


  • Subject: The NSTrackingArea Report
  • From: Quincey Morris <email@hidden>
  • Date: Fri, 1 Feb 2008 11:45:15 -0800

I've been playing around with tracking areas with a half-finished test app, and I thought I'd report what I found out. Sorry about the long post.

(A) Mouse-moved events.

Seems to work fine. The only issue is a conceptual one with the documentation. There's a difference between the mouse being "over" a tracking area or view (i.e. the mouse location within the bounds) and "inside" a tracking area or view (i.e. within the bounds of the topmost object under the mouse location).

Mouse-moved events, and tracking areas in general, follow the "over" concept. That is, mouse-moved events are generated for every tracking area the mouse is over. Nesting views or overlaying tracking areas won't mask what's underneath from getting mouse-moved events. If you want just the topmost view or tracking area to get mouse-moved events, you have to filter out any others yourself. I think it would be worthwhile for the documentation to say this explicitly.

(B) Mouse-entered and mouse-exited events.

Seems to work fine, mostly. The only strange behavior I saw was that after scrolling the view by dragging (autoscroll or dragger hand tool), there was sometimes a second mouse-exited event following a previous mouse-exited event with no intervening mouse-entered.

As I posted earlier, there's a Cocoa documentation error for the "assume inside" option. The correct meaning is what's in the Leopard developer release notes (although it's talking about a bug fix to NSTrackingRect, but the NSTrackingArea behavior is the same).

As I posted earlier, entered/exited tracking is defective (or at least disappointing) in that it doesn't tell you whether the mouse is inside or outside the tracking area when you first add the tracking area to a view. If you don't specify the "assume inside" option, you won't get a tracking event until the next time the mouse enters the tracking area (so it has to go out and come back in, if it's already inside). If you do specify "assume inside", you'll get a mouse-exited event the next time the mouse moves, if it's actually outside.

There's also a kind of defect in the documentation for NSView's 'updateTrackingAreas' method. The description implies that the correct thing to do in 'updateTrackingAreas' is to re-create all of the view's tracking areas. In fact, there's no need to re-create tracking areas with the "sync-to-visibleRect" option set, and doing so will cause the same state of temporary indeterminacy that I described in my previous paragraph.

Based on my testing, I'd have to suggest that entered/exited tracking is NOT a suitable mechanism for an application to *persistently* keep track of whether the mouse is inside (or over) or outside its views (or parts of views, if you use tracking areas smaller than the whole view).

What it's good for is causing events to occur when the mouse crosses area boundaries. That is, the entered/exit state implied by the event is only reliable during the processing of the event, and can't reliably be assumed to persist after that.

(C) Cursor-update events.

Seems to work fine, but there's one defect and a documentation error.

The documentation says that the cursorUpdate message is sent to the tracking area's owner. In fact (as correctly described in the Leopard developer release notes), the message is sent to the view that the mouse is inside (i.e. the topmost view under the mouse location). In general, this might be neither the tracking area's owner, nor the view the tracking area is attached to.

The defect with cursor-update events is, again, the lack of persistent state. If you need to set the cursor for a custom view and the cursor is always the same inside the view, then cursor-update events work perfectly, even when views overlap or are nested (for example, if the custom view contains text fields).

If, however, your view wants to set the cursor differently over different objects in the view (think of Safari changing the cursor to a pointing finger over clickable links and an I-beam over selectable text), you're not so well served. You're going to have to set the cursor on mouse-moved events, but you don't directly know at event time whether your view "owns" the cursor or not.

The problem is that mouse-moved uses "over" semantics and cursor- update uses "inside" semantics. To make it work, you're going to have to do an inside/outside check (at least) or a view hit test (at worst) to find out whether you're responsible for the cursor on any given event.

(D) What's an application to do?

To deal with all of these issues, there's a kind of hierarchy of state information that an applications needs to maintain persistently and consistently across all the custom views it's responsible for:

    inside-view status -> over-view status -> mouse location

The inside-view status can actually be found directly, using NSWindow's hit testing, but assuming that might be expensive to do very often, it's worth making it conditional on the over-view status, assuming that can be made fairly cheap. If you can make assumptions about how your views overlap with other views, you might be able to avoid NSWindow hit testing completely, and do a few quick bounds checks instead.

The over-view status can be reduced to a simple NSMouseInRect check, per custom view. As I said earlier, it can't really be made any cheaper by monitoring entered/exited events without sacrificing reliability.

Both of these depend on reliable information about the mouse location, which is where I started this investigation a week ago. (I'm assuming that the mouse location is desired to be synchronized to the event queue. If not, there's already a reliable answer in [NSEvent mouseLocation].)

As far as I can see, the best strategy is to globally remember the mouse location last seen in a mouse-related event, converted to screen coordinates. The easiest way to do this is to subclass NSApplication and to wrap [NSApplication sendEvent:] in an override that monitors mouse events. Then all you need to do is cover the two holes this leaves:

1. At application startup, initialize the global location to [NSEvent mouseLocation], then use [NSApplicaton nextEventMatchingMask: untilDate: inMode: dequeue:] with no wait and dequeue:NO to peek at the first mouse event in the queue if there is one. (I haven't actually tried this, but it seems like it would work.)

2. To cover the case where something in code you didn't write removes events from the queue in a local event loop that you can't monitor (e.g. a text control -- if that's what it does), turn on entered/ exited tracking for your views, so at least you'll get a location update when the mouse returns to one of your custom views. (I have actually tried this, and it works fine, except that I messed things up by trying to keep persistent state based on entered/exited events.)

(E) What could Apple do?

Apple could help out a lot if it would maintain a little bit of state for us. (Actually, Cocoa already knows the state, it just won't tell us.)

First, it would be great if there was a -[NSApplication mouseLocation], the mouse location in screen coordinates from the last mouse-related event dequeued for any reason, whether in the main event loop or a local event loop, properly initialized at application startup even if there are no events yet.

Second, it would be great if there was a -[NSTrackingArea isMouseEntered] to report what the tracking area knows but can't currently tell us, properly initialized when the tracking area's view becomes associated with a window.

Third, it would be great if there was a -[NSApplication cursorUpdateView] or a -[NSWindow cursorUpdateView]) or a -[NSView isCursorUpdateInside] to tell us which view most recently received a cursor-update event and is therefore responsible for the cursor until the next cursor-update event.

--

Again, apologies for the long post. Apologies for getting sidetracked on the mouse location issue earlier in the discussion. Apologies for any errors of fact or deficiencies of opinion in this post. Apologies for obsessing over a tiny issue that clearly does *not* burn bright in the minds of most who read this list.

And now, back to my regularly scheduled life ...



_______________________________________________

Cocoa-dev mailing list (email@hidden)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden


  • Follow-Ups:
    • Re: The NSTrackingArea Report
      • From: j o a r <email@hidden>
  • Prev by Date: What paradigm behind NSColor's drawing methods?
  • Next by Date: Re: How to "capture" the mouse
  • Previous by thread: Re: What paradigm behind NSColor's drawing methods?
  • Next by thread: Re: The NSTrackingArea Report
  • Index(es):
    • Date
    • Thread