今回からメッセージ履歴画面にメッセージ履歴アクションボタンを表示できるようにカスタマイズしていくね。 | |
hact タグで作ったメッセージ履歴アクションを実行できるボタンを作るんだよね。 | |
※詳しくは §10.1参照。 | |
そう。それじゃ早速スクリプトを見てくね。 まずは追加したメンバ変数から。 |
今回は結構メンバ変数が増えてるね。 | |
とりあえず1つずつ見てこっか。 | |
うん。 | |
まず最初の enableLinks っていうメンバ変数は、 true にすると普通のメッセージ履歴画面と同じように、 メッセージ履歴アクションがリンクみたいな形式で表示されるようになるんだ。 | |
じゃあ enableLinks を false にしたらどーなるの? | |
false にすると、メッセージ履歴アクションが設定されてる部分がクリックできなくなるの。 あと、メッセージ履歴アクションが設定されてる部分にマウスカーソルを乗せても何も表示されなくなるよ。 |
|
それって、enableLinks を false にしたらメッセージ履歴アクションが使えなくなるってこと? | |
普通のメッセージ履歴画面の形式では使えなくなるね。 | |
え、どーいうコト? | |
追加したメンバ変数の中に enableActionButtons って変数があるでしょ。 | |
うん、あるね。 | |
これが true になってると、普通のメッセージ履歴画面のメッセージ履歴アクションの代わりに、 メッセージ履歴アクションボタンが表示されるようになるんだ。 | |
あ、それじゃあ enableLinks が false でも enableActionButtons が true だったらメッセージ履歴アクションが使えるんだ? | |
そういうこと。 じゃ次のメンバ変数にいくね。 |
|
次は actionButtonStorage っていう変数だね。 | |
これはメッセージ履歴アクションボタン用の画像のファイル名だね。 | |
メッセージ履歴アクションボタンのオブジェクトじゃなくて画像ファイル名なの? | |
メッセージ履歴アクションボタンって数が決まってるわけじゃないから、必要に応じて作るようにしてるんだ。 だから、最初はメッセージ履歴アクションボタンのオブジェクトは1つも無いの。 |
|
あ、そーなんだ。 | |
メッセージ履歴アクションボタンはいつ必要になるかわからないから、 とりあえずメッセージ履歴アクションボタン用の画像のファイル名をメンバ変数に設定しといて、 メッセージ履歴アクションボタンが必要になった時にそのファイル名を参照してボタンのオブジェクトを作れるようにしてるの。 | |
なるほどね。 | |
それじゃ次は actionButtons ね。 これがメッセージ履歴アクションボタンのオブジェクトだよ。 |
|
この変数って配列になってるの? | |
うん。 メッセージ履歴アクションは一つしか作れないわけじゃないからね。 |
|
確かにメッセージ履歴アクションって hact タグの数だけできるもんね。 じゃあメッセージ履歴アクションボタンを2つ以上作れるように配列にしてるんだね。 |
|
※ボタンの表示領域の制約上、1行の履歴メッセージに2つ以上メッセージ履歴アクションを設定してもボタンは1つしか表示されませんので、厳密には hact タグの数とメッセージ履歴アクションボタンの数が一致するとは限りません(メッセージ履歴ボタンの数は hact タグの数以下になります)。 | |
そういうこと。 じゃ最後は actions だね。 |
|
この変数も配列になってるみたいだけど…これって何に使う変数なの? | |
hact タグには exp っていう属性があるでしょ。 | |
えっと、exp 属性って確かメッセージ履歴アクションが設定されてる部分をクリックした時に実行されるTJS式だったよね? | |
そ。そのTJS式を記録しとくのが actions っていう配列なの。 メッセージ履歴アクションボタンが押された時に、そのボタンに対応するTJS式を実行するために使うんだ。 例えば actionButtons[0] のボタンが押されたら actions[0] のTJS式を実行する、みたいな感じだね。 |
|
ふぅん、そーなんだ。 | |
じゃこれで追加するメンバ変数は一通りチェックしたから、次はコンストラクタを見てくね。 |
追加してるのは太字になってる行とその上の行のコメントだけだよ。 | |
それって loadActionButtonImage ってメソッドを呼び出してる行のこと? | |
そう。 loadActionButtonImage メソッドはメッセージ履歴アクションボタン用の画像を指定するメソッドで、 スクリプトはこうなってるよ。 |
引数の storage はメッセージ履歴アクションボタン用の画像のファイル名ね。 | |
さっき actionButtonStorage っていうメンバ変数がメッセージ履歴アクションボタン用の画像のファイル名って言ってたから、 これに引数の storage を代入してるんだね。 | |
そだよ。 | |
あと、enableActionButtons を true にして、 enableLinks を false にしてるってことは、 メッセージ履歴アクションボタンを使えるようにして、普通のメッセージ履歴画面のメッセージ履歴アクションは使えないようにしてるってことだよね? | |
うん。 メッセージ履歴アクションボタンがあれば普通のメッセージ履歴画面のメッセージ履歴アクションは無くてもいいかなって思って。 ちなみに両方とも true にすると両方使えるようになるよ。 |
|
あ、そーなんだ。 | |
ExtendedHistoryLayer_config メソッドの中で actionButtonStorage に画像ファイル名が指定してあれば(actionButtonStorage が空文字列じゃなければ)ここで loadActionButtonImage メソッドが呼び出されるから、 メッセージ履歴アクションボタンが表示されるようになるわけね。 | |
※ExtendedHistoryLayer_config メソッドについては §10.2 参照。 | |
そっか。 えっと、コンストラクタが実行されても loadActionButtonImage メソッドが呼び出されるだけだから、 実際にメッセージ履歴画面のメッセージ履歴アクションボタンが作られるわけじゃないんだよね? |
|
ん。さっきも言ったけど、メッセージ履歴アクションボタンの数は予め決まってるわけじゃなくて、 どの部分の履歴メッセージを表示するかによって数が変わってくるから、必要に応じて作るようにしてるの。 | |
それってどこで作ってるの? | |
drawLine メソッドか updateButtonState メソッドが呼び出された時に、 必要に応じてメッセージ履歴アクションボタンを作ってるよ。 | |
drawLine メソッド…って履歴メッセージをレイヤに書き込むメソッドだったよね? | |
※drawLine メソッドについては §10.8 参照。 | |
そ。あと updateButtonState メソッドは本来「前ページ」ボタンと「次ページ」ボタンの表示を更新するためのメソッドだよ。 今作ってるメッセージ履歴画面には「前ページ」ボタンと「次ページ」ボタンを表示しないから、このメソッドでは何もやってないけどね。 |
|
そーいえば §10.9 で updateButtonState メソッドをオーバーライドして何もしないメソッドにしたんだったよね。 | |
今回はメッセージ履歴アクションボタンを使えるようにするために、drawLine メソッドをオーバーライドして、 あと updateButtonState メソッドの中身を書き換えてるんだ。 | |
そーなんだ。 | |
じゃまずは drawLine メソッドから見てくね。 |
<drawLine メソッド>
えっと、最初にスーパークラスの drawLine メソッドを呼び出して… それから enableActionButtons が true だったら drawActionButton ってメソッドを呼び出してるみたいだね。 |
|
スーパークラスの drawLine メソッドってのは、 つまりメッセージ履歴を表示するレイヤのクラス(HistoryLayer クラスね)の drawLine メソッドってことだから、 これを呼び出すと履歴メッセージがレイヤに表示されるわけね。 | |
drawActionButton メソッドって今までに出てきたことないよね。 これがメッセージ履歴アクションボタンを表示するメソッドなの? |
|
そう。drawActionButton メソッドは、drawLine メソッドで表示される履歴メッセージにアクションが設定されてたら、メッセージ履歴アクションボタンを表示するメソッドだよ。 | |
drawLine メソッドの中ではメッセージ履歴アクションボタンを作ってないみたいだから… drawActionButton メソッドの中でメッセージ履歴アクションボタンを作ってるの? |
|
うん。 drawActionButton メソッドの中身は後でチェックするとして、 先に updateButtonState メソッドの方を見てくね。 |
全体が if(enabledActionButtons) のブロックになってるから、 enabledActionButtons が true の時だけブロックの中が実行されるんだね。 | |
つまりメッセージ履歴アクションボタンを表示する設定にしてる時だけブロックの中身を実行して、 メッセージ履歴アクションボタンを表示しない設定にしてる時は何もしないってことだね。 | |
だね。 えっと、if ブロックの中にある clearActionButtons って今までなかったメソッドだよね? |
|
clearActionButtons メソッドは全部のメッセージ履歴アクションボタンと actions 配列の内容を消去するメソッドだよ。 まぁメッセージ履歴アクションボタンの方は、消去って言ってもメッセージ履歴アクションボタンのオブジェクトを無効化するわけじゃなくて、単に非表示にするだけなんだけどね。 |
それってつまり clearActionButtons メソッドが呼び出されると、 メッセージ履歴アクションボタンが全部見えなくなるってこと? | |
そ。で、その後 drawActionButton メソッドを呼び出して、新しくメッセージ履歴アクションボタンを表示してるの。 | |
ここって everypage が true か false かで違うスクリプトを実行してるみたいだけど? | |
ん、そーなんだけど、repageLine と dispLine はどっちも画面に表示できるメッセージ履歴の行数だから、 everypage が true でも false でもやってることは同じで、for ループの中で drawActionButton メソッドを呼び出すことで、画面に表示されてるメッセージ履歴を最初の行から順番にチェックしてって、 メッセージ履歴アクションが設定されてたらメッセージ履歴アクションボタンを表示してるの。 | |
ふーん、そーなんだ。 ところで、なんでわざわざメッセージ履歴アクションボタンを全部消して、その後またメッセージ履歴アクションボタンを表示してるの? |
|
updateButtonState メソッドは、メッセージ履歴の表示をスクロールしたり、 前のページや次のページのメッセージ履歴を表示する時とかに呼び出されるメソッドだから、 このメソッドが呼び出されたってことは、画面に表示されてる履歴メッセージの内容が変わったってことなのね。 | |
うん。 | |
だから、スクロールしたりページを切り替えたりする前の履歴メッセージに対して設定されてた履歴アクション用のボタンを全部消してから、 スクロールしたりページを切り替えたりした後の履歴メッセージに対して設定されてる履歴アクション用のボタンを作る必要があるわけ。 | |
あ、じゃあ消してたのは前に表示されてた履歴メッセージのメッセージ履歴アクションボタンで、 表示してたのは今表示されてる履歴メッセージのメッセージ履歴アクションボタンってことなんだね。 | |
そういうこと。 じゃあ次は drawActionButton メソッドを見てこっか。 |
まず、引数の n は、
今画面に表示されてる履歴メッセージの何行目のメッセージに対応するメッセージ履歴アクションボタンを表示するかを表してるんだ。 例えば、横書き表示の場合は n が 0 だと一番上の行のメッセージに対応するメッセージ履歴アクションボタンを表示するってことね。 |
|
じゃあ n が 1 だと上から2行目ってことだね。 | |
ん、そう。 | |
えっと、everypage が true だったら、最初に ai っていう変数に getActionInfo2 ってメソッドの戻り値を代入してて、 everypage が false だったら ai に getActionInfo ってメソッドの戻り値を代入してるよね? | |
getActionInfo2 メソッドと getActionInfo メソッドはどっちも履歴アクションの内容を取得するメソッドだよ。 “getActionInfo2(dispStart, n)”を実行すると、今表示してるページ(dispStart)の n 行目の履歴メッセージに対応する履歴アクションが取得できて、 “getActionInfo(dispStart + n)”を実行すると、今表示してる履歴メッセージの上から(縦書き表示の場合は右から) n 行目の履歴メッセージに対応する履歴アクションが取得できるの。 |
|
それって結局、everypage が true でも false でも、 今画面に表示されてる履歴メッセージの上から(縦書き表示の場合は右から) n 行目の履歴メッセージに対応する履歴アクションが取得できるってことだよね? | |
そういうことだね。 | |
getActionInfo2 メソッドとか getActionInfo メソッドもメッセージ履歴アクションボタン用に作ったメソッドなの? | |
んーん、これは普通のメッセージ履歴画面用レイヤのクラス(HistoryLayer クラス)のメソッドをそのまま使ってるだけだよ。 | |
あ、そーなんだ。 | |
それぞれのメソッドの詳細は省略させてもらうけど、getActionInfo2 メソッドと getActionInfo メソッドの戻り値はどっちも、その行の履歴メッセージにアクションが設定されてたらそのアクションの内容を表す辞書配列になって、 アクションが設定されてなかったら void になるんだ。 | |
そっか。だから ai が void じゃない時だけ if ブロックの中身を実行して、ai が void だったら何もしないんだね。 | |
そうそう。 | |
if ブロックの中で最初に prev_ai って変数に何か代入してるみたいだけど、 これ何を代入してるの? | |
あー、ちょっとここは式が複雑になってるからね。 えっと、この部分はつまりこういうことだね。 |
<prev_ai への代入の式と同じ意味のスクリプト>
上のスクリプトを1行で書くとこれになるの。 | |
えっと、n が 0 より大きかったらこの if ブロックの中を実行して、 n が 0 以下だったら prev_ai に void を代入してるんだね。 | |
ん。 | |
あと、n が 0 より大きい時は、 everypage が true だったら prev_ai に getActionInfo2(dispStart, n - 1) の戻り値を代入してて、 everypage が false だったら prev_ai に getActionInfo(dispStart + n - 1) の戻り値を代入してるね。 | |
ん、そうだね。 | |
なんかやってることがさっきと似てる気がするんだけど…? | |
取得してる履歴アクションの行がさっきと違ってるでしょ? | |
あ、さっきは n 番目の行だったけど、今度は (n - 1) 番目の行なんだね。 | |
そ。 | |
でも引数が n なんだから、n 番目の行のメッセージ履歴アクションボタンを表示するわけでしょ? (n - 1) 番目の行の履歴アクションなんて必要ないんじゃない? |
|
確かに直接 (n - 1) 行目の履歴アクションを使うわけじゃないよ。 | |
え、どーいうコト? | |
履歴アクションを設定してるメッセージが長いと、こんなふうに履歴アクションが2行以上続くことがあるよね。 |
<2行以上にわたるメッセージ履歴アクションの例(下図の例では3行の履歴メッセージに対して1つのアクションが設定されています)>
うん、確かにこうなることはあるよね。 | |
こういう時って… |
<すべての行にメッセージ履歴アクションボタンを表示した場合>
こんなふうに、全部の行にメッセージ履歴アクションボタンを表示するより… |
<最初の行だけにメッセージ履歴アクションボタンを表示した場合>
こうやって、最初の行だけにメッセージ履歴アクションボタンを表示した方がいいでしょ? | |
んー、確かに最初の行だけに表示した方が見やすいし使いやすそうだね。 | |
こういう時に最初の行にだけメッセージ履歴アクションボタンを表示するために、 (n - 1) 行目の履歴アクションをチェックしてるんだ。 | |
え、(n - 1) 行目の履歴アクションをチェックするだけで、 最初の行にだけメッセージ履歴アクションボタンを表示できるようになるの? | |
ん、できるよ。 ちょっとこの if ブロックの中が実行される条件を見てみて。 |
|
んーと…“if(prev_ai === void || ai[0].id != prev_ai[0].id)” だから、 prev_ai が void になってるか、 ai[0].id の値と prev_ai[0].id の値が違ってる時に if ブロックの中身が実行されるみたいだね。 |
|
まず、getActionInfo メソッドや getActionInfo2 メソッドは、 その行に履歴アクションが設定されてないと void を返すから、 prev_ai が void になるのは、 (n - 1) 番目の行に履歴アクションが何も設定されてない場合なんだ。 | |
えっと、今は n 番目の行にメッセージ履歴アクションボタンを表示しようとしてるんだから、 つまり1つ前の行に履歴アクションが設定されてないと prev_ai が void になるってこと? | |
そうそう。 で、あと ai[0].id と prev_ai[0].id の方なんだけど… |
|
これって ai と prev_ai は配列になってるってことだよね? | |
うん。 履歴アクションって1行に2つ以上設定することもできるでしょ。 |
|
そーだね。 | |
だから、最初に設定されてる履歴アクションが配列の最初の要素で、2番目に設定されてる履歴アクションがその次の要素で…って感じになってるの。 | |
そっか。配列にすることで、履歴アクションが複数設定されててもちゃんと全部の履歴アクションがわかるようになってるんだね。 | |
そういうこと。 で、この配列の要素は辞書配列になってて、その中に id っていう要素があるわけ。 |
|
id ってどんな要素なの? | |
履歴アクションを識別する ID 番号だよ。 | |
ID 番号? | |
ID 番号は履歴アクションを設定するごとに1ずつ増えていく番号だよ。 だから、シナリオの最初に書いてある hact タグの履歴アクションの ID 番号は 1 になって、2番目に書いてある hact タグの履歴アクションの ID 番号は 2 になって、3番目に書いてある hact タグの履歴アクションの ID 番号は 3 になって…って感じで、hact タグを1つ書くごとに1ずつ ID 番号の値が増えていくの。 |
|
※ID 番号はメッセージ履歴をクリアする(HistoryLayer クラスの clear メソッドを呼び出す)と 1 にリセットされます。 | |
へぇ…履歴アクションにそんな番号がついてたんだ。 でもそれって何に使うの? |
|
さっきの if ブロックの条件に “ai[0].id != prev_ai[0].id” っていう部分があったでしょ。 | |
うん。 | |
これはつまり、n 番目の行と (n - 1) 番目の行の履歴アクションの ID 番号が違うってことだから、 n 番目の行と (n - 1) 番目の行には別々の履歴アクションが設定されてるってことなのね。 | |
なるほど、確かにそーなるね。 | |
言い換えると、“ai[0].id != prev_ai[0].id” が成り立たないってことは、 n 番目の行と (n - 1) 番目の行に同じ履歴アクションが設定されてる(つまり、履歴アクションが2行以上続いてる)ってことだよね。 | |
あ、じゃあ prev_ai が void になってるか、 ai[0].id の値と prev_ai[0].id の値が違ってるってことは、 前の行に履歴アクションが設定されてないか、前の行と違う履歴アクションが設定されてるってことになるのかな? | |
そう。 その時だけメッセージ履歴アクションボタンを表示すれば、最初の行にだけメッセージ履歴アクションボタンが表示されるようになるの。 |
|
なるほどねー。 じゃあこの if ブロックの中でメッセージ履歴アクションボタンを表示してるんだ? |
|
そだよ。 じゃ if ブロックの中を見てこっか。 |
|
りょーかい。 | |
って言っても、メッセージ履歴アクションボタンを追加する addActionButton メソッドを呼び出してるだけだけどね。 | |
履歴メッセージが縦書き表示か横書き表示かで addActionButton メソッドを呼び出す時の引数が違ってるみたいだけど? | |
addActionButton メソッドには引数が3つあって、第1引数と第2引数がそれぞれメッセージ履歴アクションボタンを表示する位置の x 座標と
y 座標で、第3引数が履歴アクションになってるんだ。 縦書き表示か横書き表示かでボタンを表示する位置が違うから、引数も違ってるってワケ。 |
|
そーなんだ。 | |
ちなみに縦書き表示の場合は、 x 座標が (width - marginR - n * lineHeight - fontHeight) で y 座標が actionButtonMargin になるわけだけど、 これだとわかりにくいと思うから図にしてみるね。 | |
※marginR, actionButtonMargin については §10.2 参照。また、lineHeight, fontHeight については Config.tjs 参照。 |
<縦書き表示(verticalView が true)の時のメッセージ履歴アクションボタン配置の例>
上の図は右から2番目のメッセージ履歴アクションボタンの位置を表してる図だよ。 右から2番目のメッセージ履歴アクションボタンより右側にはメッセージが5行と空行(何も書いてない行)が1行あるから、 n が 6 になってるわけね。 |
|
えっと、x 座標の (width - marginR - n * lineHeight - fontHeight) ってのは図の黄色い矢印の長さになってるってことかな? | |
ん、黄色い矢印の長さはメッセージ履歴レイヤの幅(width)から marginR と (n * lineHeight) と fontHeight の長さを引いた分だからね。 | |
だよね。 | |
で、横書き表示の場合は x 座標が actionButtonMargin で y 座標が (n * lineHeight + controlHeight + marginT) なんだけど、 これもわかりにくいから図にしてみるね。 |
<横書き表示(verticalView が false)の時のメッセージ履歴アクションボタン配置の例>
この図は上から2番目のメッセージ履歴アクションボタンの位置を表してる図だよ。 上から2番目のメッセージ履歴アクションボタンより上にはメッセージが3行と空行が1行あるから、 n は 4 になってるよ。 |
|
※controlHeight, marginT については §10.2 参照。 | |
この図 controlHeight が無いんじゃない? | |
§10.2 でも言ったけど、controlHeight は『次ページ ≫』ボタンとかのボタンの高さなんだけど、
こういうボタンを使う代わりにスクロールバーを付けてるから、今回のカスタマイズでは controlHeight は 0 にしてるの。 だから図には描いてないんだ。 |
|
あ、そっか。そーいえばそんなこと言ってたよね。 | |
じゃ addActionButton メソッドの引数の話に戻るね。 あと残ってるのは第3引数だね。 |
|
第3引数は縦書きの時でも横書きの時でも ai[0].action になってるね。 履歴アクションだから縦書きの時でも横書きの時でもおんなじになるのはわかるけど、 この action ってどんな要素なの? |
|
hact タグには exp って属性があるよね。 | |
exp 属性って、履歴メッセージをクリックした時に実行する TJS 式だよね。 | |
そ。その TJS 式が action 要素に入ってるの。 | |
あ、そーなんだ。 | |
例えば、こんな hact タグを実行すると… |
action 要素は "kag.se[0].play(%['storage' => 'voice.wav'])" っていう文字列になるんだ。 ちなみにこの TJS 式は 0 番の効果音バッファを使って "voice.wav" っていうファイルを再生するって意味ね。 |
|
これって普通のメッセージ履歴画面だと、「こんにちは。」っていう部分をクリックしたら "voice.wav" が再生されるってことだよね? | |
ん、そうだよ。 この例だと特に問題ないと思うんだけど、action 要素が “TJS 式を表す文字列”になるってのが、 場合によってはちょっと厄介なことになっちゃったりするんだけどね。 |
|
え、それってどーいうコト? | |
ま、その辺はもうちょっと後で見ていくことにするね。 だいぶ長くなっちゃったから、今回はこれくらいにしとこっか。 addActionButton メソッドの中身は次回見てくことにするね。 |
|
んー、ちょっと気になるけど、まぁいっか。 うん、りょーかい。 |
|
それじゃ、また次回ね! |