Sunday, October 18, 2009

Dragging (part 2)



This is where it gets interesting. First, in order to receive mouse events, we need to override acceptsFirstResponder to return YES. A flag is set to show that we've had a mouseDown but not yet a corresponding mouseUp, so we are interested in mouseDragged events.

During the drag, we keep track of the point where the mouse was the last time we did anything. The shape is moved based on the difference between the current and previous positions. However, we have to check if the shape is about to move beyond the bounds of the view, and not move in that case. We use a function defined in "NSGeometry.h" to check this.

There is code that is commented out for doing the move with an affine transform (it works). But I decided it's simpler to just make a new NSBezierPath object.

Although this example seems to be OK, it is a little jerky near the edge of the view. It feels like the mouse position and the point we're tracking can get out of synch. You have to go slowly to nudge the shape right up to the edge. Perhaps I can find a better implementation.


- (BOOL)acceptsFirstResponder { 
return YES;
}

- (IBAction)mouseDown:(NSEvent *)e {
p = [self convertPoint:[e locationInWindow]
fromView:nil];
R = [shape bounds];
NSLog(@"mouseDown %3.2f %3.2f",
p.x, p.y);
draggingInProgress = NSPointInRect(p,R);
if (draggingInProgress) {
lastPoint = p;
NSLog(@"start drag");
}
}

- (IBAction)mouseDragged:(NSEvent *)e {
if (!(draggingInProgress)) return;
p = [self convertPoint:[e locationInWindow]
fromView:nil];
NSLog(@"mouseDragged %3.2f %3.2f", p.x, p.y);
didMove = [self tryMovingObject:shape
forOldPoint:lastPoint
newPoint:p];
if (didMove) {
lastPoint = p;
[self setNeedsDisplay:YES];
}
}

- (IBAction)mouseUp:(NSEvent *)e {
draggingInProgress = NO;
NSLog(@"stop drag");
}

- (BOOL)tryMovingObject:(id)obj
forOldPoint:(NSPoint)op
newPoint:(NSPoint)np {
dx = np.x - op.x;
dy = np.y - op.y;
bo = [obj bounds];
bn = NSMakeRect(bo.origin.x + dx,
bo.origin.y + dy,
bo.size.width,
bo.size.height);
if (!(NSContainsRect([self bounds], bn))) {
NSLog(@"reject move");
return NO;
}
[self setShape:[NSBezierPath
bezierPathWithOvalInRect:bn]];
/*
t = [NSAffineTransform transform];
[t translateXBy:(np.x-op.x) yBy:(np.y-op.y)];
[obj transformUsingAffineTransform:t];
*/
return YES;
}

@end