8.13 ドラッガブルシステムボタン(その2)

今回はドラッガブルシステムボタン用のプラグインを作ってくね。
はーい。
プラグインの方も基本的に普通のシステムボタンをベースにすればいいから、 ExSystemButtonPlugin クラスを継承して、ドラッガブルボタン用に DraggableSystemButtonPlugin クラスを作っていくことにするね。
まず、いつものようにコンストラクタとデストラクタとメンバ変数から。
ExSystemButtonPlugin クラスについては §5.8§5.9§5.10 参照。

DraggableSystemButtonPlugin クラスのコンストラクタ・デストラクタ・メンバ変数>

class DraggableSystemButtonPlugin extends ExSystemButtonPlugin
{
    var x = [464, 496, 528, 560]; // 初期 x 位置
    var y = [400, 368, 400, 368]; // 初期 y 位置

    function DraggableSystemButtonPlugin()
    {
        // スーパークラスのコンストラクタを呼び出します
        super.ExSystemButtonPlugin();
    }

    function finalize()
    {
        // スーパークラスのデストラクタを呼び出します
        super.finalize();
    }
}

スーパークラスのコンストラクタとデストラクタを呼び出してるだけみたいだね。
ん、そうだよ。
メンバ変数は… xy っていう配列があるけど、 xy って ExSystemButtonPlugin クラスの時は配列じゃなかったよね?
ExSystemButtonPlugin クラスの xy は1つ目のボタンの位置を表す数値だったよね。
DraggableSystemButtonPlugin クラスの xy は1つ目のボタンの位置じゃないの?
DraggableSystemButtonPlugin クラスの xy もボタンの位置なんだけど、 1つ目のボタンだけじゃなくて、全部のボタンの位置を指定できるようにしてるんだ。
ってことは、最初のボタンの位置が(464,400) で、 2つ目のボタンの位置が (496,368) …って感じになってるのかな?
そうそう。
ExSystemButtonPlugin クラスだとボタンは縦に並べてたけど、 こうすればボタンを自由な位置に表示できるからね。
なるほど、それってちょっと便利だね。
んじゃ次は ExSystemButtonPlugin クラスのメソッドをオーバーライドしていくね。
まずコンストラクタで最初に呼び出される createButtons メソッドから見ていくね。

createButtons メソッド>

function createButtons(parent, array)
{
    var obj;

    // ボタン 0 (メッセージスキップ)
    array.add(obj = new DraggableSystemButtonLayer(kag, parent, onSkipButtonClick, onButtonDragged));
    obj.loadImages('btn_skip'); // メッセージスキップボタン用画像を読み込みます

    // ボタン 1 (オートモード)
    array.add(obj = new DraggableSystemButtonLayer(kag, parent, onAutoModeButtonClick, onButtonDragged));
    obj.loadImages('btn_auto'); // オートモードボタン用画像を読み込みます

    // ボタン 2 (メッセージ履歴表示)
    array.add(obj = new DraggableSystemButtonLayer(kag, parent, onShowHistoryButtonClick, onButtonDragged));
    obj.loadImages('btn_history'); // メッセージ履歴表示ボタン用画像を読み込みます

    // ボタン 3 (メッセージウィンドウ消去)
    array.add(obj = new DraggableSystemButtonLayer(kag, parent, onHideMessageButtonClick, onButtonDragged));
    obj.loadImages('btn_hide'); // メッセージウィンドウ消去ボタン用画像を読み込みます
}

作るボタンの種類は違ってるみたいだけど、 ExSystemButtonPlugin クラスの createButtons メソッドとそんなに変わらないね。
ん、基本的に違ってるのはボタンとして DraggableSystemButtonLayer クラスのオブジェクトを作ってるってトコだけ。
今回はドラッガブルシステムボタンだもんね。
えっと、onButtonDragged ってゆーのが、ボタンをドラッグした時に呼び出されるメソッドなんだよね?
そうだよ。
onButtonDragged メソッドはこんな感じ。

onButtonDragged メソッド>

function onButtonDragged(dx, dy)
{
    // すべてのボタンを dx, dy だけ移動します
    moveButtons(dx, dy);
}

moveButtons っていうメソッドを呼び出してるだけみたいだけど… ExSystemButtonPlugin クラスにはそんなメソッド無かったよね?
ん、moveButtons メソッドは今回新しく作るメソッドで、 全部のボタンを一度に移動させるためのメソッドなんだ。
スクリプトはこんな感じね。

moveButtons メソッド>

function moveButtons(dx, dy)
{
    // 全てのボタンを横方向に dx ピクセル、縦方向に dy ピクセル移動します
    var count = x.count;
    for(var i=0;i<count;i++)
    {
        x[i] += dx;
        y[i] += dy;
        foreButtons[i].setPos(x[i], y[i]);
        backButtons[i].setPos(x[i], y[i]);
    }
}

setPos メソッドを呼び出したりしてるから、ボタンの位置は設定してるみたいだけど…?
とりあえず最初から見てくね。
まず引数の dxdy がどうなってるかはわかるよね?
onButtonDragged メソッドの引数の dxdy でしょ?
いや、それはそーなんだけど、 dxdy がそれぞれどんな値になってるかってこと。
あっ、そーいうことね。
え〜と…確か dxdy はボタンがそれぞれ横方向と縦方向に何ピクセル移動したかを表してるんだったよね。
ん、そうだね。
じゃ for ループの条件は?
i0 から count より小さい間ループする、ってことだよね。
countx.count になってるけど、この x ってメンバ変数の x
そうだよ。
count プロパティはわかるよね?
※配列の count プロパティについては §1.14 参照。
配列の要素が何個あるかってことだよね。
そうそう。
ってことは、x.countx の要素の数だから 4 ってことになるよね。
つまりボタンの数、ってことね。
そっか、確かにそうなるね。
でもボタンの数なら foreButtons.count とかの方がわかりやすくない?
もちろんそうしてもいいよ。
x.county.countforeButtons.countbackButtons.count は全部ボタンの数になってるから、どれを使っても OK。
それじゃ for ブロックの中身の方を見てくね。
んーと、まず x[i]y[i] にそれぞれ dxdy を足してるね。
dxdy は移動した距離になってるから、 足した後の x[i]y[i] は移動後のボタンの位置になってるのかな?
ん、そういうことになるね。
あとは、setPos メソッドで表画面と裏画面のボタンを (x[i],y[i]) の位置に設定してるね。
setPos メソッドについては §3.2 参照。
これでボタンが移動後の位置に表示されるよね。
で、ドラッグしてる間は onButtonDragged メソッドが繰り返し呼び出され続けて、 onButtonDragged メソッドが呼び出されるたびに moveButtons メソッドが呼び出されてボタンの位置が更新されるから、 ドラッグの動きに合わせてボタンが移動していくってワケ。
なるほどね。
じゃ、あと3つオーバーライドしなくちゃいけないメソッドがあるから、 それぞれ見ていくことにするね。
おっけー。
まず1つ目は realign メソッド。
realign メソッドって、確かボタンを並べて表示するメソッドだったよね?
SystemButtonPlugin クラスの realign メソッドについては §5.8ExSystemButtonPlugin クラスの realign メソッドについては §5.10 参照。
SystemButtonPlugin クラスの realign メソッドだとボタンを横に並べてて、 ExSystemButtonPlugin クラスの realign メソッドだとボタンを縦に並べてたよね。
だね。
ドラッガブルシステムボタンの場合は xy の配列の中身をチェックすればボタンの位置が判るから、 realign メソッドはこんなふうにちょっと簡単に書けるんだ。

realign メソッド>

function realign()
{
    // ボタンの再配置
    var count = foreButtons.count;
    for(var i = 0;i < count; i++)
    {
        var obj;

        obj = backButtons[i];
        obj.setPos(x[i], y[i]);
        obj.absolute = 2000000-3; // 重ね合わせ順序はメッセージ履歴よりも奥

        obj = foreButtons[i];
        obj.setPos(x[i], y[i]);
        obj.absolute = 2000000-3; // 重ね合わせ順序はメッセージ履歴よりも奥
    }
}

確かに btn_x とか btn_y とかの変数がなくなってちょっとシンプルになってるみたいだね。
SystemButtonPlugin クラスとかの時は1つ目のボタンの位置しかメンバ変数に保存されてなかったから、 2つ目以降のボタンの位置は1つ目のボタンの位置から計算しなくちゃいけなかったわけだけど、 今回は位置を計算する必要はないからね。
setPos(x[i], y[i]) でいいんだね。
そ。absolute プロパティの設定は変わってないから、後は問題ないよね?
うん。
んじゃ次いくね。
2つ目は setOptions メソッド。
SystemButtonPlugin クラスの setOptions メソッドについては §5.9 参照。 また、ExSystemButtonPlugin クラスの setOptions メソッドは SystemButtonPlugin クラスと同じです。
それってボタンの位置を設定したり、表示/非表示を切り替えたりするメソッドだったよね?
そだよ。
ドラッガブルシステムボタンの場合は realign メソッドじゃなくて moveButtons メソッドでボタンの位置を設定するから、こんな感じになるんだ。

setOptions メソッド>

function setOptions(elm)
{
    // オプションを設定
    setObjProp(foreButtons, 'visible', foreSeen = +elm.forevisible) if elm.forevisible !== void;
    setObjProp(backButtons, 'visible', backSeen = +elm.backvisible) if elm.backvisible !== void;
    if(elm.left !== void || elm.top !== void)
    {
        // 表示位置の変更
        moveButtons(elm.left !== void ? +elm.left - x[0] : 0, elm.top !== void ? +elm.top - y[0] : 0);
    }
}

最初の visible を設定してるとこは SystemButtonPlugin クラスの setOptions メソッドとおんなじだから、位置を設定してるとこだけ見てくね。
えっと、まず if の条件が elm.left !== void || elm.top !== void になってるのは、 lefttop のどっちかの属性が指定されてたら moveButtons メソッドを呼び出してボタンの位置を設定するためなんだよね?
lefttop のどっちかの属性が指定されてる時だけじゃなくて、両方指定されてる時もね。
あ、そか。両方 void じゃない時も条件式は真になるもんね。
う〜ん…なんかボタンの位置を設定してるだけにしては moveButtons メソッドの引数がややこしい気がするんだけど…
moveButtons メソッドの引数は「ボタンの位置」じゃなくて「ボタンが移動した距離」になってて、
left 属性や top 属性に指定されてる値をそのまま使うわけにはいかないからね。
それでややこしくなってるの?
ん、まぁね。
とりあえず第1引数から見ていこっか。
りょーかい。
条件演算子はわかるよね?
※条件演算子については §1.20 参照。
第1引数は elm.leftvoid じゃなかったら +elm.left - x[0] になって、void だったら 0 になるんだよね。
ん、そう。
まず elm.leftvoid の時に引数が 0 になるってのはわかるでしょ?
え? えっと、elm.leftvoid ってことは left 属性が指定されてないってことだから、 横方向には動かさなくてもいいってことだよね。
moveButtons メソッドの第1引数が 0 ってことは、横方向の移動距離が 0 ってことだから…
あ、全然移動しないってことだね。
そ。で、left 属性は1つ目のボタンの横方向の位置(left プロパティの値ね)を指定する属性だから、x[0] の値が +elm.left になればいいワケ。
じゃあ、その時の移動距離が +elm.left - x[0] になるってコト?
(設定前の位置)+(移動距離)=(設定後の位置)だから、 (移動距離)=(設定後の位置)−(設定前の位置)、 つまり dx = +elm.left - x[0] になるでしょ。

<ボタンの移動距離の計算>

なるほど、確かにそうなってるね。
縦方向も同じように考えて、top プロパティが指定されてれば moveButtons メソッドの第2引数は +elm.top - y[0]、指定されてなければ 0 になるわけね。これは OK?
うん、おっけーだよ。
ん、それじゃ3つ目のメソッド、onStore メソッドを見ていくね。
SystemButtonPlugin クラスの onStore メソッドについては §5.9 参照。 また、ExSystemButtonPlugin クラスの onStore メソッドは SystemButtonPlugin クラスと同じです。

onStore メソッド>

function onStore(f, elm)
{
    // 栞を保存するとき
    var dic = f.exSystemButtons = %[];
        // f.exSystemButtons に辞書配列を作成
    dic.foreVisible = foreSeen;
    dic.backVisible = backSeen;
    dic.left = x[0];
    dic.top = y[0];
        // 各情報を辞書配列に記録
}

SystemButtonPlugin クラスの onStore メソッドと違ってるのは… dic.leftdic.top に代入してる値だけ、かな?
そ。ドラッガブルシステムボタンプラグインでは xy を配列にしてるから、 1つ目のボタンの位置は (x[0],y[0]) になるよね。
dic.leftdic.top には1つ目のボタンの位置を保存しとくから、 それぞれ x[0]y[0] を代入してるんだね。
そういうこと。他は変わってないからこれで OK かな?
うん。
…あ、そーいえば onStore メソッドはオーバーライドするのに onRestore メソッドはオーバーライドしなくていいの?
SystemButtonPlugin クラスの onRestore メソッドは、 システムボタンの情報がセーブデータに保存されてる時と保存されてない時で別々な処理をしてたよね。
保存されてたら setOptions メソッドを呼び出してシステムボタンの状態を復元して、 保存されてなかったら setObjProp メソッドを呼び出してボタンを非表示にしてたよね。
まずデータが保存されてなかった場合はボタンを非表示にするだけだから、 ドラッガブルシステムボタンの場合もおんなじでいいよね。
うん、そだね。
データが保存されてる場合は setOptions メソッドが呼び出されるわけだけど、 ドラッガブルシステムボタンプラグインの setOptions メソッドの引数の辞書配列には普通のシステムボタンプラグインの時と同じ属性が指定できるようになってるでしょ。
確かにどっちとも forevisiblebackvisiblelefttop の4つの属性が指定できるようになってるね。
forevisiblebackvisible 属性については何も変えてないし、 lefttop 属性は普通のシステムボタンプラグインの時と同じように1つ目のボタンの位置を保存してるから、 setOptions メソッドは普通のシステムボタンプラグインの時と同じように呼び出せるわけね。
うん。
だから、結局ドラッガブルシステムボタンプラグインの onRestore メソッドは普通のシステムボタンプラグインの onRestore と全く同じで OK ってこと。
あ、それでオーバーライドしなくてもいーんだ。
そういうこと。
それじゃ最後にドラッガブルシステムボタンの設定をするための dragsysbtopt マクロを作っとくね。
まぁこれも普通のシステムボタンプラグインの時とほとんど同じなんだけどね。

dragsysbtopt マクロ>

[macro name="dragsysbtopt"]
[eval exp="draggablesystembutton_object.setOptions(mp)"]
; mp がマクロに渡された属性を示す辞書配列オブジェクト
[endmacro]

ちなみに draggablesystembutton_object ってのは DraggableSystemButtonPlugin クラスのオブジェクトで、 DraggableSystemButtonPlugin クラスを定義した後にこんなふうに作ってるから。

DraggableSystemButtonPlugin クラスのオブジェクト作成>

kag.addPlugin(global.draggablesystembutton_object = new DraggableSystemButtonPlugin(kag));
    // プラグインオブジェクトを作成し、登録する

ホントだ。マクロと変数の名前以外はおんなじだね。
さっきも言ったけど、setOptions メソッドの呼び出し方は普通のシステムボタンプラグインの時と同じだからね。
それじゃ、これでドラッガブルシステムボタンプラグインは完成したから、実行してみよっか。
うん!
first.ks はとりあえずこんな感じで。

first.ks の中身>

; プラグインを読み込みます
[call storage="exsystembutton.ks"]
[call storage="DraggableSystemButton.ks"]

; ボタンを表示状態にします
[dragsysbtopt forevisible=true backvisible=true]

*label|

このシステムボタンはドラッグして動かせるので、好きな位置にボタンを置くことができます。[p][cm]

;以下適当にメッセージを表示してシステムボタンの動作を確認してください。

ドラッガブルシステムボタンは普通のシステムボタンのクラスを継承して作ってるから、 最初に exsystembutton.ks を読み込むのを忘れないようにしてね。
じゃ必要なファイルはここに置いとくから実行してみて。
りょ〜かい!

<実行結果>

うん、ドラッグしたらボタンが動かせるし、普通にボタンを押すこともできるね!
ん、ちゃんと作れたみたいだね。
…あ。
ん?
これってなんかヘンじゃない?

ほら、マウスカーソルは HIST ボタンの上にあるのに AUTO ボタンの色が変わってるよ?
あー、ホントだ。
んー…これは hitThreshold プロパティの設定が原因だね。
hitThreshold プロパティについては §7.2 参照。
hitThreshold プロパティ?
…って確か 256 にするとマウス系のイベントが起こらなくなるとかゆーのだよね?
そ。もうちょっとちゃんと言うと、画像の不透明度が hitThreshold プロパティに設定した値以上になってる部分に対してだけマウス系のイベントが発生するの。
ところで、DraggableSystemButtonLayer クラスは ButtonLayer クラスを継承して作ってるよね。
Layer クラス → KAGLayer クラス → ButtonLayer クラス → SystemButtonLayer クラス → ExSystemButtonLayer クラス → DraggableSystemButtonLayer クラスの順に継承しています。
え? えっと、そーだったっけ?
そうだよ。で、ButtonLayer クラスのコンストラクタはこうなってるの。

ButtonLayer クラスのコンストラクタ(system フォルダにある ButtonLayer.tjs より抜粋)>

function ButtonLayer(win, par)
{
    super.KAGLayer(win, par);

    if(typeof win.cursorPointed !== "undefined")
        cursor = win.cursorPointed;

    hitType = htMask;
    hitThreshold = 0;
    focusable = true; // フォーカスを得られる
}

あ、なんか hitThreshold プロパティの値が 0 に設定されてるね。
hitThreshold の値が 0 ってことは、 システムボタンのレイヤ上のどこでもマウス系のイベントが発生するってことだから、 こんなふうに AUTO ボタンの透明な部分でもマウス系のメソッドが呼び出されて、 AUTO ボタンの色が変わっちゃうんだ。

※HIST ボタンよりも AUTO ボタンの方が手前に表示されています。

へぇ、そうなんだ…
ねぇ、これってなんとかならないの?
んー、そうだね…
一番カンタンなのは DraggableSystemButtonLayer クラスのコンストラクタhitThreshold プロパティを 0 より大きい値に設定する方法かな。

DraggableSystemButtonLayer クラスのコンストラクタ(修正版)>

function DraggableSystemButtonLayer(window, parent, clickfunc, dragfunc)
{
    // スーパークラスのコンストラクタを呼び出します
    super.ExSystemButtonLayer(window, parent, clickfunc);

    // 完全に透明な部分ではマウス系のイベントが発生しないようにします
    hitThreshold = 1;

    // ドラッグ中に呼び出されるメソッドへの参照を onDragFunction にセットしておきます
    onDragFunction = dragfunc;
}

hitThreshold1 に設定してるね。
こうすればボタン画像の完全に透明な部分ではマウス系のイベントが発生しなくなるから、 今度はうまくいくと思うよ。
ってワケで、もっかい実行してみて。
うん、おっけー。

<実行結果>

ホントだ。今度はちゃんと HIST ボタンの色が変わってるね!
ん、これで大丈夫だね。
さて、それじゃこれでドラッガブルシステムボタンの話はおしまい。
えっと、これで第8章は終わりなんだよね?
うん、そうだよ。
次は何するの?
第9章はレイヤ関係で今まで使ったことないメソッドを色々使ってみようと思うんだ。
それってどんなメソッドなの?
例えば画像を拡大・縮小したり回転したりするメソッドかな。
あ、そんなメソッドもあるんだ。
うん、まぁ回転とかはちょっと使い方が難しかったりするけどね。
え、そうなの?
ま、その辺はちゃんと説明するつもりだよ。
ってワケで、次の章もがんばってついてきてね!


前へ | TOP | 次へ