Knob をつかんだ時の処理を加える。
ソース:RubberBand-3.zip
さてどう実装するか。Knobはつかんだ位置によって処理が微妙に異なる。
例えば左上のKnobを左上方向へ移動させた場合、Origin の移動+ Sizeの拡大となる。
一方、右下の Knobを右下へ移動すると Originは動かず、Sizeの拡大だけとなる。
移動量を dx, dy とすると、最初のケースは次のように表せる。
x' = x + dx
y' = y + dy
w' = w - dx
h' = h - dy
このケースでは dx < 0 dy < 0
x' = x + 0
y' = y + 0
w' = w + dx
h' = h + dy
このケースでは dx > 0, dy > 0。
実はこれらは行列として表せる。
2番目はこう。
これはアフィン変換と呼ばれる行列演算。
Knobの位置毎に Origin と Size の行列が定義できるから、これをパラメータ化すれば処理自体は1種類で済む。dx と dy は可変だが、行列で必要なのはその符号なのでパラメータとしては -1, 0, 1 として表せる。プログラムではパラメータを構造体で定義する。
typedef struct _ResizeRule {
CGFloat x, y, w, h;
} ResizeRule;
Knob毎のパラメータを用意してやる。
ResizeRule rules[8] = {
{1, 1,-1,-1}, // TOP_LEFT
{0, 1, 0,-1}, // TOP_MIDDLE
{0, 1, 1,-1}, // TOP_RIGHT
{1, 0,-1, 0}, // MIDDLE_LEFT
{0, 0, 1, 0}, // MIDDLE_RIGHT
{1, 0,-1, 1}, // BOTTOM_LEFT
{0, 0, 0, 1}, // BOTTOM_MIDDLE
{0, 0, 1, 1} // BOTTOM_RIGHT
};
rules のインデックス値と、Knobのenum値を合わせておくのを忘れずに。
パラメータが準備できたので後はこれを使う処理を実装するだけ。
- (void)mouseDown:(NSEvent*)event
{
NSPoint pp, cp;
cp = [self convertPoint:[event locationInWindow] fromView:nil];
int knob_type = [self knobAtPoint:cp];
CGFloat dx, dy;
if (knob_type != KNOB_NON) {
while ([event type] != NSLeftMouseUp) {
pp = cp;
event = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
cp = [self convertPoint:[event locationInWindow] fromView:nil];
dx = cp.x - pp.x;
dy = cp.y - pp.y;
ResizeRule rule = rules[knob_type];
_rect.origin.x += dx * rule.x;
_rect.origin.y += dy * rule.y;
_rect.size.width += dx * rule.w;
_rect.size.height += dy * rule.h;
[self setNeedsDisplay:YES];
}
}
}
これだけ。ずいぶんスッキリと実装できた。
- - - - -
Sketch より短くできた、なんて喜んでいたら、いくつか問題が見つかった。
次回はこれを直そう。