views:

347

answers:

2

Hello, I'm starting working with Core Animation and I started wondering how to handle various kinds of mouse events like

  1. mouse entered a CALayer
  2. mouse exited a CALayer
  3. mouse click on a CALayer

For now I'm concentrating just on mouseDown message sent to my custom view. According to the documentation I should use -hitTest:(CGPoint)point calling on my root layer (the one set with [customView setLayer:rootLayer]).

It should return the farthest layer in the tree that is in position specified by point. So I did:

[rootLayer hitTest:[event locationInWindow]]

but it doens't work. It seems to work if I just click on rootLayer, otherwise it returns just null. Of course sublayers are added to rootLayer (infact they are drawn)

Am I missing some kind of coordinate conversion? Apart from that, is it the way to handle mouse clicks?

To find whenever mouse enters a CALayer do I have to keep hit testing for every movement update of the mouse?

Thanks in advance

+1  A: 

You need to convert locationInWindow into the containing view's coordinate system. So, if your view is called customView, you would do this:

[rootLayer hitTest:[customView convertPoint:[event locationInWindow] fromView:nil]];

CALayers don't get their own event notifications, so the only way to handle events is by handling them in the containing view.

Alex
+1  A: 

You might want to look at how we handle mouse / touch events in CALayers within the Core Plot framework. We've set up a rudimentary responder chain with our various CALayer subclasses in the layer hierarchy. Recently we added code to scroll a graph using mouse / touch events as an example of how to use this kind of interaction.

I've used something similar myself, only with the mouse click handling being taken care of within the main layer-hosting view. In that view, I overrode -mouseDown: and added code similar to the following:

CGPoint pointOfClick = NSPointToCGPoint([self convertPoint:[theEvent locationInWindow] fromView:nil]);
CALayer *hitLayer = [self.layer hitTest:pointOfClick];
if ( (hitLayer != nil) && [hitLayer isKindOfClass:[MyCustomLayer class]])
{
 [(MyCustomLayer *)hitLayer mouseDown];
}

The MyCustomLayer class would be a custom CALayer subclass which responds to the mouseDown method and does some mouse click handling of its own. Alternatively, you could handle the mouse click response entirely within this method of your layer-hosting view.

One thing to make sure of is that if you've subclassed CALayer, its -containsPoint: method still returns YES if the passed point lies within the bounds of your layer. Otherwise, -hitTest: will ignore that layer.

Brad Larson
This seems clear, what about handling mouseEntered and mouseExited? Do I have to simulate the whole process storing the actual layer in which mouse is and handling when it changes entering or exiting a new one?
Jack
I haven't tried this, but you might be able to create NSTrackingAreas for each of your layers. If you make them a CALayer subclass that responds to -mouseEntered:, -mouseExited:, -mouseMoved:, and -cursorUpdate: messages, you might be able to have each layer be the owner of the tracking area and directly respond to those events. That would seem to be more efficient than hit-testing on every move of the mouse.
Brad Larson

related questions