home link download back number special issue

HMDT - Special Issue / Sketch BP / ベジエ曲線ツールのドローオブジェクトの編集の実装


- Modification -

ベジエ曲線ツールのドローオブジェクトの編集の実装

ベジエ曲線ツールのドローオブジェクトの編集に必要なもの

ベジエ曲線ツールの編集も、ポリゴンツールの編集のときと大きくは変わらない。ダブルクリックすると編集モードに入る。で、アンカーポイントを選択して動かせるんだ。

ポリゴンツールと違ってやっかいなのは、1 つはコントロールポイントの取り扱い。もう 1 つは、パスが閉じているときの処理だ。パスが閉じているときは、最初のアンカーポイントと、最後のアンカーポイントが重なっているので、これを両方とも動かしてやらないといけないとこに注意。

実際の実装は 2 つのメソッドをオーバーライドする。アンカーポイントとコントロールポイントを動かすための、editWithEvent:inView:。もう 1 つは、ポイントを削除するための deleteForEditingInView: だ。


ベジエ曲線ツールのドローオブジェクトの編集の実装

編集のためには editWithEvent:inView: をオーバーライドする。ここでの処理は、まずクリックされた点が何かを決めないといけない。アンカーポイントなのか?コントロールポイントなのか?それとも何もない?

Sketch/DocumentModel.subproj/SKTBezierPath.m (created by mkino)
- (BOOL)editWithEvent:(NSEvent*)event inView:(SKTGraphicView*)view {
    NSPoint	point = [view convertPoint:[event locationInWindow] fromView:nil];
    ...
    int		pointIndex = [self pointUnderPoint:point];
    ...

    if (pointIndex < 0) {
        return NO;
    }
    
    if ([self isAnchorPoint:pointIndex]) {
        _selectedPointIndex = pointIndex;
    }
    else {
        if (_selectedPointIndex < 0) {
            return NO;
        }
    }
    ...

まず pointUnderPoint: で、クリックされたポイントを調べる。ポイントがなければ、おしまい。

アンカーポイントをクリックしたら、そのアンカーポイントを選択する。もしアンカーポイントが選択されているときに、その前後のコントロールポイントがクリックされたら、そのポイントをハンドルするんだ。

そして、マウスがドラッグされたら、ポイントを動かすよ。

Sketch/DocumentModel.subproj/SKTBezierPath.m (created by mkino)
- (BOOL)editWithEvent:(NSEvent*)event inView:(SKTGraphicView*)view {
    ...

    while (1) {
        event = [[view window] nextEventMatchingMask:
                    (NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
        
        if ([event type] == NSLeftMouseDragged) {
            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;
            }
            
            [self movePointAtIndex:pointIndex toPoint:point];
            if ([self isAnchorPoint:pointIndex]) {
                int	index = [self indexOfLastAnchorPoint];
                diffSize.width = point.x - oldPoint.x;
                diffSize.height = point.y - oldPoint.y;
                
                if (_isPathClosed && 
                    (pointIndex == 0 || pointIndex == index)) 
                {
                    [self movePointAtIndex:0 toPoint:point];
                    [self movePointAtIndex:index toPoint:point];
                    [self movePointAtIndex:1 bySize:diffSize];
                    [self movePointAtIndex:index - 1 bySize:diffSize];
                }
                else {
                    if (pointIndex > 0) {
                        [self movePointAtIndex:pointIndex - 1 bySize:diffSize];
                    }
                    if (pointIndex < index) {
                        [self movePointAtIndex:pointIndex + 1 bySize:diffSize];
                    }
                }
            }
        }
    ...

基本的には、マウスが動いたところに選択されたポイントを動かすだけだ。ただし、考慮しないといけないケースがいくつかある。まず、アンカーポイントをドラッグしたとき。そのときは、両端のコントロールポイントもいっしょに動かすんだ。そこで、ポイントを動かす差分を求めて、コントロールポイントも動かしてやる。

さらに気をつけないといけないのは、アンカーポイントが最初か最後のとき。この場合は、最初と最後をいっしょに動かしてやらないといけない。その場合の処理も必要なんだ。


アンカーポイントの削除の実装

続いて、アンカーポイントの削除を。deleteForEditingInView: で行う。

Sketch/DocumentModel.subproj/SKTPolygon.m (created by mkino)
- (void)deleteForEditingInView:(SKTGraphicView*)view {
    NSRect	drawingBounds;
    
    if (_selectedPointIndex != -1) {
        // If the points is last one
        if ([_points count] == 1) {
            [[self document] removeGraphic:self];
            return;
        }
        
        drawingBounds = [self drawingBounds];
        
        [self removeAnchorPointAtIndex:_selectedPointIndex];
        _selectedPointIndex = -1;
        
        [self setBounds:[[self origBezierPath] bounds]];
        drawingBounds = NSUnionRect(drawingBounds, [self drawingBounds]);
        [view setNeedsDisplayInRect:drawingBounds];
    }
}

このメソッドは、あまりポリゴンのものと変わらない。ベジエ曲線特有の処理は、removeAnchorPointAtIndex: で行われているんだ。これは、指定したアンカーポイントと、その前後のコントロールポイントを削除するためのメソッドだぜ。

Sketch/DocumentModel.subproj/SKTPolygon.m (created by mkino)
- (void)removeAnchorPointAtIndex:(int)index {
    int	lastIndex;
    
    if (![self isAnchorPoint:index]) {
        return;
    }
    
    lastIndex = [self indexOfLastAnchorPoint];
    if (index == 0 || index == lastIndex) {
        if(_isPathClosed) {
            [_points replaceObjectAtIndex:lastIndex 
                               withObject:[_points objectAtIndex:3]];
            [_points replaceObjectAtIndex:lastIndex - 1 
                               withObject:[_points objectAtIndex:2]];
            [_points removeObjectsInRange:NSMakeRange(0, 3)];
        }
        else {
            if (index == 0) {
                [_points removeObjectsInRange:NSMakeRange(0, 3)];
            }
            else if (index == lastIndex) {
                [_points removeObjectsInRange:NSMakeRange(lastIndex - 2, 3)];
            }
        }
    }
    else {
        [_points removeObjectsInRange:NSMakeRange(index - 1, 3)];
    }
}

このメソッドでは、指定されたポイントとその前後 1 つずつのポイントを削除しているんだ。ただし、いくつかのケースがある。普通の場合、つまり弧の途中のアンカーポイントを削除するときは、普通に 3 つ消せばいい。

まず、弧が閉じていない場合。さらに、その両端を削除する場合。このときは、アンカーポイントと、前後ではなくて、線の先にある 2 つのコントロールポイントを削除する必要があるんだ。

そして、弧が閉じていて、最初か最後のアンカーポイントを削除するとき。このときは、最初のアンカーポイントを削除して、かつ最後のアンカーポイントを 2 つめのアンカーポイントのところまで、移動してやる必要があるんだ。なかなかめんどくさいねー。


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

HMDT - Download / Sketch BP


Home | Link | Download | Back Number | Speciall Issue

Sketch BP

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

HMDT