home link download back number special issue

HMDT - Special Issue / Sketch BP / Sketch に見るドローオブジェクトの移動、変形


- Case Study -

Sketch に見るドローオブジェクトの移動、変形

ドローオブジェクトを移動、変形する

一回ドローオブジェクトを作った後、それを移動したり、編集したりすることができるのが、ドローアプリケーションの特徴だ。てなわけで、その様子を調べてみよう。

大きさを変えるために、ドローオブジェクトを選択すると、その周りにノブが表示されるんだ。

周りの 8 箇所にノブが表示される。これをつかんで大きさを変えたり、まん中をつかんで移動させたりできるんだ。


ノブを描く

ドローオブジェクトを選択すると、周りにノブが描かれるけど、これはどこで描いてるのか?それは SKTGraphic の drawHandlesInView:drawHandleAtPoint:inView: だ。

drawHandleAtPoint:inView:

ここでは drwaHandleAtPoint:inView: を見てみよう。指定された point にノブを描くためのメソッドだ。

Sketch/DocumentMode.subproj/SKTGraphic.m
- (void)drawHandleAtPoint:(NSPoint)point inView:(SKTGraphicView *)view {
    NSRect handleRect;

    handleRect.origin.x = point.x - SKT_HALF_HANDLE_WIDTH + 1.0;
    handleRect.origin.y = point.y - SKT_HALF_HANDLE_WIDTH + 1.0;
    handleRect.size.width = SKT_HANDLE_WIDTH - 1.0;
    handleRect.size.height = SKT_HANDLE_WIDTH - 1.0;
    handleRect = [view centerScanRect:handleRect];
    [[NSColor controlDarkShadowColor] set];
    NSRectFill(handleRect);
    handleRect = NSOffsetRect(handleRect, -1.0, -1.0);
    [[NSColor knobColor] set];
    NSRectFill(handleRect);
}

要は、NSRectFill() を使って、2 回四角形を描いてるわけだ。これを、ドローオブジェクトの周りに 8 回繰り返せば、ノブが描かれるってわけだ。


Sketch に見るドローオブジェクトの移動、変形

では、コードを最初から見ていこう。スタートポイントは、お約束の SKTGraphicView の mouseDown: だ。

mouseDown:

mouseDown: の次は、selectAndTrackMouseWithEvent: に入ることになるんだ。

Sketch/SKTGraphicView.m
- (void)mouseDown:(NSEvent *)theEvent {
    Class theClass = [[SKTToolPaletteController sharedToolPaletteController] currentGraphicClass];

    ...

    if (theClass) {
        ...
    } else {
        [self selectAndTrackMouseWithEvent:theEvent];
    }
}

では次へ。


selectAndTrackMouseWithEvent:

このメソッドの中で、移動をするのか、変形をするのか分かれることになるぜ。

Sketch/SKTGraphicView.m
- (void)selectAndTrackMouseWithEvent:(NSEvent *)theEvent {
    ...

    curPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    graphic = [self graphicUnderPoint:curPoint];
    isSelected = (graphic ? [self graphicIsSelected:graphic] : NO);

    ...

    if (graphic) {
        // Add or remove this graphic from selection.
        if (extending) {
            ...
        } else {
            if (isSelected) {
                int knobHit = [graphic knobUnderPoint:curPoint];
                if (knobHit != NoKnob) {
                    [self trackKnob:knobHit 
                          ofGraphic:graphic 
                          withEvent:theEvent];
                    return;
                }
            }
            [self selectGraphic:graphic];
            isSelected = YES;
        }
    } else {
        [self rubberbandSelectWithEvent:theEvent];
        return;
    }

    if (isSelected) {
        [self moveSelectedGraphicsWithEvent:theEvent];
        return;
    }

    ...
}

まずは、変形の方からチェックが入る。変形とみなす条件は、

  • マウスの下にドローオブジェクトがある
  • そのオブジェクトが選択されている
  • マウスの下にノブがある(knobUnderPoint:

これだけの条件を満たすと、変形の処理が始まるぜ(trackKnob:ofGraphic:wtihEvent:)!

この条件のうち、マウスの下にノブが無い場合は、移動の処理に移るんだ(moveSelectedGraphicsWithEvent:)。


trackKnob:ofGraphic:withEvent:

変形するためのメソッドを見てみよう。ノブのトラックをトレースする trackKnob:ofGraphic:withEvent: だ。

Sketch/SKTGraphicView.m
- (void)trackKnob:(int)knob 
        ofGraphic:(SKTGraphic *)graphic 
        withEvent:(NSEvent *)theEvent 
{
    ...

    while (1) {
        theEvent = [[self window] 
             nextEventMatchingMask:
               (NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
        point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
        [self invalidateGraphic:graphic];
        if (snapsToGrid) {
            point.x = floor((point.x / spacing) + 0.5) * spacing;
            point.y = floor((point.y / spacing) + 0.5) * spacing;
        }
        knob = [graphic resizeByMovingKnob:knob toPoint:point];
        [self invalidateGraphic:graphic];
        if (echoToRulers) {
            [self continueEchoingMoveToRulers:[graphic bounds]];
        }
        if ([theEvent type] == NSLeftMouseUp) {
            break;
        }
    }

    ...
}

もう結構何度も見たような気がする、内部イベントループだ。マウスのドラッグと、ボタンアップを待っているね。マウスがドラッグされたら、ノブの位置と、どの座標に移動したか、という情報を resizeByMovingKnob:toPoint: で SKTGraphic に設定しているんだ。これでオブジェクトの大きさが変わるんだ。


moveSelectedGraphicsWithEvent:

変形するためのメソッドを見てみよう。ノブのトラックをトレースする trackKnob:ofGraphic:withEvent: だ。

Sketch/SKTGraphicView.m
- (void)moveSelectedGraphicsWithEvent:(NSEvent *)theEvent {
    ...

    while (1) {
        theEvent = [[self window] 
            nextEventMatchingMask:
              (NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
        curPoint = [self convertPoint:[theEvent locationInWindow] 
                             fromView:nil];
        if (!isMoving && 
            ((fabs(curPoint.x - lastPoint.x) >= 2.0) || 
             (fabs(curPoint.y - lastPoint.y) >= 2.0)))
        {
            isMoving = YES;
            [selGraphics 
                makeObjectsPerformSelector:
                    @selector(startBoundsManipulation)];
            _gvFlags.knobsHidden = YES;
        }
        if (isMoving) {
            ...
            if (!NSEqualPoints(lastPoint, curPoint)) {
                for (i=0; i<c; i++) {
                    graphic = [selGraphics objectAtIndex:i];
                    [self invalidateGraphic:graphic];
                    [graphic moveBy:NSMakePoint(
                                         curPoint.x - lastPoint.x, 
                                         curPoint.y - lastPoint.y)];
                    [self invalidateGraphic:graphic];
                    if (echoToRulers) {
                        [self continueEchoingMoveToRulers:
                                NSMakeRect(
                                    curPoint.x - selOriginOffset.x, 
                                    curPoint.y - selOriginOffset.y, 
                                    NSWidth(selBounds),
                                    NSHeight(selBounds))];
                    }
                    didMove = YES;
                }
                _pasteCascadeDelta.x += (curPoint.x - lastPoint.x);
                _pasteCascadeDelta.y += (curPoint.y - lastPoint.y);
            }
            lastPoint = curPoint;
        }
        if ([theEvent type] == NSLeftMouseUp) {
            break;
        }
    }

    if (echoToRulers)  {
        [self stopEchoingMoveToRulers];
    }
    if (isMoving) {
        [selGraphics 
            makeObjectsPerformSelector:
                @selector(stopBoundsManipulation)];
        _gvFlags.knobsHidden = NO;

        ...
    }
}

このメソッド、すげー長いよ。理解するのも一苦労。説明するのは難しすぎ。とりあえず、ポイントだけ押さえておこう。

  • 内部でイベントループを回し、マウスドラッグとボタンアップを待つ
  • マウスが動いて、ドローオブジェクトを動かしたと判定されたら、SKTGraphic の startBoundsManipulation を呼ぶ
  • さらに moveBy: も呼んで、ドローオブジェクトの境界を変更する
  • マウスを離したら、後片付け処理をして、SKTGraphic の stopBoundsManipulation を呼ぶ

っていう、こんな感じの流れね。


Home | Link | Download | Back Number | Speciall Issue

Sketch BP

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

HMDT