home link download back number special issue

HMDT - Special Issue / Sketch BP / ポリゴンツールのドローオブジェクトの作成の実装


- 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 をとってやる必要があるんだ。

最後に、クリックカウントを判定してやる。ダブルクリック以上だったら、トラッキングループを抜ける。これでおしまい。


- ソースコードのダウンロード -

HMDT - Download / Sketch BP


Home | Link | Download | Back Number | Speciall Issue

Sketch BP

mailto: mkino@xd5.so-net.ne.jp

HMDT