- Modification -
ポリゴンツールのドローオブジェクトの作成の実装

■ ポリゴンツールのドローオブジェクトの作成に必要なもの
さてと、ポリゴンツール、作ってみますか。ポリゴンを描くのに必要なものを考えてみよう。
実装するときは、ポリゴンは NSBezierPath で表す。ただし、曲線は使わないで、直線だけで構成されるので、コントロールポイントは使わないんだ。アンカーポイントだけで描ける。だから、まず、そのアンカーポイントを保持しておく配列が必要だね。
あと、そのパスが閉じているかどうか、を表す情報も必要だ。ポリゴンのオブジェクトを一意に決めるのに必要なのは、この 2 つだね。というわけで、クラスの定義はこんな感じ。
|
Sketch/DocumentModel.subproj/SKTPolygon.h (created by mkino)
|
@interface SKTPolygon : SKTGraphic {
@private
NSMutableArray* _anchorPoints;
BOOL _isPathClosed;
...
}
|
_anchorPoints が、ポリゴンの各点を保持しておくための配列。NSPoint をこの中に入れていく。_isPathClosed が、パスが閉じているかどうかを示すものだ。
で、このアンカーポイントを決定するには、ドローオブジェクト作成時のメソッドを作る必要があるんだ。ポリゴンツールを選択したら、クリックを繰り返して、オブジェクトを作り上げるとこまでを、ハンドルするメソッドね。これは、SKTGraphic の createWithEvent:inView: をオーバーライドしてやることで実現してやろう。
■ ポリゴンツールのドローオブジェクトの作成の実装
オブジェクト作成のための実装では、createWithEvent:inView: をオーバーライドする。これは SKTGraphic でオブジェクトを作るためのメソッドなんだ。これが、実装してみると、なかなかに長いぞ!
|
Sketch/DocumentModel.subproj/SKTPolygon.m (created by mkino)
|
- (BOOL)createWithEvent:(NSEvent *)event inView:(SKTGraphicView *)view {
NSPoint point = [view convertPoint:[event locationInWindow] fromView:nil];
NSRect bounds, drawingBounds;
BOOL snapsToGrid = [view snapsToGrid];
float spacing = [view gridSpacing];
BOOL echoToRulers = [[view enclosingScrollView] rulersVisible];
NSEventType type;
[self startBoundsManipulation];
if (snapsToGrid) {
point.x = floor((point.x / spacing) + 0.5) * spacing;
point.y = floor((point.y / spacing) + 0.5) * spacing;
}
[self setBounds:NSMakeRect(point.x, point.y, 0.0, 0.0)];
if (echoToRulers) {
[view beginEchoingMoveToRulers:[self bounds]];
}
[self appendPoint:point];
// Tracking loop
while (1) {
event = [[view window] nextEventMatchingMask:
(NSLeftMouseDownMask |
NSLeftMouseDraggedMask |
NSLeftMouseUpMask)];
point = [view convertPoint:[event locationInWindow] fromView:nil];
if (snapsToGrid) {
point.x = floor((point.x / spacing) + 0.5) * spacing;
point.y = floor((point.y / spacing) + 0.5) * spacing;
}
type = [event type];
if (type == NSLeftMouseDown && [event clickCount] <= 1) {
int index = [self anchorPointUnderPoint:point];
if (index == 0) {
// Close path and go out from loop
_isPathClosed = YES;
break;
}
// Append point for a bezier path
[self appendPoint:point];
}
else if (type == NSLeftMouseUp || type == NSLeftMouseDragged) {
// Move the last point
[self movePointAtIndex:[_anchorPoints count] - 1 toPoint:point];
}
// Set bounds and calculate drawing bounds
drawingBounds = [self drawingBounds];
[self setBounds:[[self bezierPath] bounds]];
drawingBounds = NSUnionRect([self drawingBounds], drawingBounds);
[view setNeedsDisplayInRect:drawingBounds];
if (echoToRulers) {
[view continueEchoingMoveToRulers:[self bounds]];
}
// In case of double click
if ([event clickCount] >= 2) {
break;
}
}
if (echoToRulers) {
[view stopEchoingMoveToRulers];
}
[self stopBoundsManipulation];
bounds = [self bounds];
if ((bounds.size.width > 0.0) || (bounds.size.height > 0.0)) {
return YES;
} else {
return NO;
}
}
|
このメソッドでは、最初にローカル変数の初期化と、いくつかの準備があって、トラッキングループに入る。この中で、ポリゴンの作成を行うんだ。こここでの処理は、
- イベントの待ち
- アンカーポイントのハンドル
- 再描画領域の確定
っていう流れをとる。それぞれを見てみよう。
◆ イベントを待つ
トラッキングループでは、マウスのダウン、マウスのドラッグ、マウスのアップのイベントを待つんだ。
|
Sketch/DocumentModel.subproj/SKTPolygon.m (created by mkino)
|
- (BOOL)createWithEvent:(NSEvent *)event inView:(SKTGraphicView *)view {
...
// Tracking loop
while (1) {
event = [[view window] nextEventMatchingMask:
(NSLeftMouseDownMask |
NSLeftMouseDraggedMask |
NSLeftMouseUpMask)];
point = [view convertPoint:[event locationInWindow] fromView:nil];
if (snapsToGrid) {
point.x = floor((point.x / spacing) + 0.5) * spacing;
point.y = floor((point.y / spacing) + 0.5) * spacing;
}
...
}
|
nextEventMatchingMask: を使ってイベントを待つ。上に書いたイベントのどれかが来ると、動きだすんだ。動きだしたら、マウスの座標を求める。グリッドにスナップしているときは、グリッドに合わせた値をとろう。
◆ アンカーポイントのハンドル
イベントと座標が決まったら、それに対応して、アンカーポイントをハンドルするんだ。
|
Sketch/DocumentModel.subproj/SKTPolygon.m (created by mkino)
|
- (BOOL)createWithEvent:(NSEvent *)event inView:(SKTGraphicView *)view {
...
// Tracking loop
while (1) {
...
type = [event type];
if (type == NSLeftMouseDown && [event clickCount] <= 1) {
int index = [self anchorPointUnderPoint:point];
if (index == 0) {
// Close path and go out from loop
_isPathClosed = YES;
break;
}
// Append point for a bezier path
[self appendPoint:point];
}
else if (type == NSLeftMouseUp || type == NSLeftMouseDragged) {
// Move the last point
[self movePointAtIndex:[_anchorPoints count] - 1 toPoint:point];
}
...
|
まず、マウスダウンで、クリックカウントが 1 のとき。このときは、新しいアンカーポイントを追加するか、最初のアンカーポイントをクリックしたならパスを閉じるか、っていう処理になる。まず、最初のアンカーポイントをクリックしたかどうかチェックする。これには anchorPointUnderPoint: っていうメソッドを使う。このメソッドは、指定したポイントの下にアンカーポイントがあるかどうか調べるんだ。もし最初のアンカーポイントだったら、パスを閉じて、トラックループから抜ける。そうじゃない場合は、新しいアンカーポイントを追加する。
クリックカウントをチェックしているのは、ダブルクリック以上のときは、アンカーポイントを追加しないで、トラックループを抜ける処理を走らせるためなんだ。
そして、マウスアップかマウスドラッグのときは、いちばん最後に追加されたアンカーポイントを選んで、それを動かす。
◆ 再描画領域の確定
そして、最後に再描画領域の確定。
|
Sketch/DocumentModel.subproj/SKTPolygon.m (created by mkino)
|
- (BOOL)createWithEvent:(NSEvent *)event inView:(SKTGraphicView *)view {
...
// Tracking loop
while (1) {
...
// Set bounds and calculate drawing bounds
drawingBounds = [self drawingBounds];
[self setBounds:[[self bezierPath] bounds]];
drawingBounds = NSUnionRect([self drawingBounds], drawingBounds);
[view setNeedsDisplayInRect:drawingBounds];
if (echoToRulers) {
[view continueEchoingMoveToRulers:[self bounds]];
}
// In case of double click
if ([event clickCount] >= 2) {
break;
}
}
...
}
|
再描画領域を求めるには、SKGraphic の drawingBounds: っていうメソッドが使えるんだ。ただし、一回呼ぶだけではだめである。なぜなら、マウスがドラッグされるときは、オブジェクトの大きさが変わるから。しかも大きくなるときと小さくなるときがある。だから、変更前と変更後の再描画領域をもとめて、それの union をとってやる必要があるんだ。
最後に、クリックカウントを判定してやる。ダブルクリック以上だったら、トラッキングループを抜ける。これでおしまい。
|