Re: NSBezierPath (Modifying Paths)
Re: NSBezierPath (Modifying Paths)
- Subject: Re: NSBezierPath (Modifying Paths)
- From: Graham Cox <email@hidden>
- Date: Thu, 5 Feb 2009 11:11:21 +1100
I just thought I'd throw my 2ยข worth in here, since I've been down
this very long road in great detail while building DrawKit, so I've
learned a thing or two about this.
On 5 Feb 2009, at 5:54 am, Joseph Crawford wrote:
You make some very good points. Let me first start out by saying I
was doing ALL the path work in drawRect: based on someone telling me
that was the best practice, I then had a few people including you
tell me that is not the case.
Absolutely wrong. -drawRect: is solely and unequivocally for drawing
*only*. If you find yourself adding or removing points to a path in
there, you've made an error in design.
This is all just messing around trying to replicate the capabilities
of the curve tool in many drawing applications, I already saw how to
replicate the drawing of a line and that would be a different tool
to choose from.
Some apps don't do this all that well actually. For my money, the
approach that Inkscape (and DrawKit) takes is the one I've found the
more intuitive. This uses a mouse down to set the on-path point, then
the immediately following drag to set the off-path (control) point for
the two control points that are either side of the just-placed on-path
point. These two control points and the on-path point always form a
straight line, thus the curve that passes through the on-path point is
tangent to this line. This ensures that the join between elements of
the curve is smooth. If you don't want that you can go back and change
the points later, but for initially laying down a curve, this is rapid
and graceful. The next on-path point along then requires a new mouse-
down, which implies that during the intervening mouse-up period, you
need to attach the current end-point of the curve to the mouse and use
mouse-moved events. Because of that, using a view's mouseDown/
mouseDragged/mouseUp methods is terribly unwieldy, so I found that
making your own tracking loop and entering it from the mouseDown only,
and staying in it until you've finished the entire curve works much
better. This forms a short-term temporary mode where you are
constructing your curved path. The next problem is how do you get out
of this mode? I do it in several ways. If you double-click at the end
of the path it finishes, or if you click on the first point of the
path, forming a closed path, it finishes, or if you hit Escape, it
finishes. Having the modal loop here allows you to make use of four
mouse gestures (down, drag, up, move) instead of the usual three. It
also allows you to use a different modal loop for others kinds of
drawing tool - lines and polygons require a different series of
gestures to create them.
There are other niceties too, like allowing modifier keys to constrain
drags in various ways, such as forcing the angles between a control
point and the on-path point to be whole intervals, or temporarily
toggling "snap to grid" plus a few others. Real drawing apps need
these sorts of things, so while you will probably feel that's overkill
at this stage, it may be worth thinking about how you'd incorporate
those into your design.
What would you suggest I look into for wanting to modify the points?
Essentially all that Cocoa provides is the -
elementAtIndex:associatedPoints: method and its inverse. That's about
as minimalistic as it gets - all of the other interactive stuff needed
you'll have to write. In DrawKit I tackle this in several stages.
First, I have a category on NSBezierPath that extends the low-level
tools available for basic path manipulation. This provides methods for
identifying the different points on a path relative to one another
(for example if dragging a curve element control point you need to
also modify its mate, bearing in mind that if the next or previous
element isn't a curve it doesn't exist, etc). The highest level method
in this category is the method:
- (void) moveControlPointPartcode:(int) pc toPoint:(NSPoint) p
colinear:(BOOL) colin coradial:(BOOL) corad constrainAngle:(BOOL) acon;
Which wraps up all the nasty tricky stuff into one method which
basically implements moving of any point on any path, so you'd call
this in a loop (or from mouseDragged:) when dragging.
I have other classes that call this when they are responding to user
edits in their paths, but basically this one does the grunt work. By
the way you could use this if you want, DrawKit is free under a BSD
license.
I had this working perfectly with a tempPath for the dragging and
actually had it grey in color and when it was drawn it was done so
in black (the visual queue) but was told that I could do it with
only one path which is why I was looking into modifying the path
elements. When I have one point the dragging operation always added
more and more points which led to the line being drawn with every
drag and that was not the wanted solution.
NSBezierPath is not rich enough as a class on its own to form the
basis for the data model of a drawing app. You need to create a class
of your own that "has a" path plus other attributes such as its stroke
and fill colours, etc. This object is stored in your data model and
draws its path on demand applying the visual attributes as it does so.
In that way you can have paths with different colours. NSBezierPath on
its own has no "colour" property, reflecting the fact that it's not
exclusively used for drawing anyway - it can be used to represent
curves in other situations, or for clipping.
A drawing app also needs essential methods such as "find the object
hit by the mouse", "find the objects intersecting this rect" and many
others. These sound trivial but turn out to have some subtle kinks -
given arbitrary drawn effects on a path for example, how do you
reliably detect a hit? (n.b. -containsPoint: is naive). It also needs
to maintain a coherent drawing order, layers maybe, a grid, and
probably a means of maintaining a selection and indicating what
objects are selected to the user, and a way to apply operations to the
selection, either using the mouse or through commands, and undo. Since
the common approaches for doing these have become very standardised
across a variety of apps over the years, it's possible to create a
framework that provides all of this functionality in a generic and
flexible way. Hence DrawKit. Other toolkits such as Qt provide similar
things as well.
I'd say that if you're just doing this as a learning exercise, go for
it. But if your aim is to actually create a drawing app, it's well
worth considering not reinventing the wheel since it turns out it's a
pretty big one! Anyway, feel free to use DK (or cannibalise it as you
wish). http:apptree.net/drawkit.htm
You can download a very bare-bones demo app that also shows how DK
implements curve creation/editing among others here: http://apptree.net/code/dk/Binaries/DKMiniDemo_app_1.2.zip
good luck!
--Graham
_______________________________________________
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