DTPab

DTPにまつわるあれこれ

SUIImageをclickイベントで差し替えられない

2ヶ月ぶりくらいの更新です。子供が夏休みの間、毎朝・毎晩お弁当作りなどの家事炊事に追われておりました。

さて、ここしばらくちょこちょことツイートしてますが、久しぶりにゼロからスクリプト開発しています。数年前に作った検索置換を連続で行うスクリプトを、設計から見直してゼロからまた組んでいる感じです。今日はSUIでつまづいたのでメモとして残しておきます。

先にポエム(ここに至るまでの経緯)、後に解決方法を書いたので、お急ぎの方は「原因と対策」まで飛ばしてください。

この記事での作業環境です。

  • InDesign 2020(15.1.3)
  • Visual Studio Code(1.60.0)
  • ExtendScript Debugger for VSCode(1.1.2)

SUIでSUIImageをボタン代わりに使う経緯

しばらく前にSUIの仕様が大きく変わって(CC2015くらいだったかな)、SUIのIconButtonに角丸の囲みが付くようになりました。これが良いか悪いかはさておき、IconButtonとして使った場合に見た目が非常にダサい。なのでボタンを画像(SUIImage)として用意して、インタラクションを独自に実装したほうが見栄えはいいのです。
具体的には、SUIImageを利用して画像を表示、イベントリスナーにclickイベントを登録し、画像がクリックされたら画像を差し替えます。これで「ボタンとして押した」風の動作を実装しようとしています。

SUIに画像を表示する

まず画像を2つ用意します。一方が押す前、もう一方が押した後の状態を示します。

f:id:uske_S:20210909110036p:plain
用意した画像

押す前の状態の画像をSUIダイアログに貼っつけます。コードは下記。

//@targetengine suiimagetest

var mySUIImage = {
    active: ScriptUI.newImage(File("ファイルパス")),
    deactive: ScriptUI.newImage(File("ファイルパス")),
};

var w = new Window("palette", "SUIImage test");
var btn = w.add('image', undefined, mySUIImage.deactive);
w.show();

クリックしたら画像を差し替える

ここに「クリックしたら画像を差し替える」処理を付け足します。

//@targetengine suiimagetest

var mySUIImage = {
    active: ScriptUI.newImage(File("ファイルパス")),
    deactive: ScriptUI.newImage(File("ファイルパス")),
};

var w = new Window("palette", "SUIImage test");
var btn = w.add('image', undefined, mySUIImage.deactive);

// ここから追記
btn.isActive = false;
btn.addEventListener('click', function() {
    if (!this.isActive) {
        this.image = mySUIImage.active;
        this.isActive = true;
    } else {
        this.image = mySUIImage.deactive;
        this.isActive = false;
    }
});
// 追記ここまで

w.show();

InDesignで呼び出してみるとこんな感じ。

f:id:uske_S:20210909111027g:plain
動作の様子

おーできてるできてる。

で、この方法だとまだ外部ファイル(画像のpngファイル)がスクリプトと別に必要になるので、これを文字列にして埋め込みます*1

同じ処理をファイルパスではなくFile.decode()で行う

//@targetengine suiimagetest

// ここを書き換えた
var mySUIImage = {
    active: ScriptUI.newImage(File.decode("%C2%89PNG%0D………")),
    deactive: ScriptUI.newImage(File.decode("%C2%89PNG%0D………")),
};

var w = new Window("palette", "SUIImage test");
var btn = w.add('image', undefined, mySUIImage.deactive);

btn.isActive = false;
btn.addEventListener('click', function() {
    if (!this.isActive) {
        this.image = mySUIImage.active;
        this.isActive = true;
    } else {
        this.image = mySUIImage.deactive;
        this.isActive = false;
    }
});

w.show();

よーしこれでできたっと!!

f:id:uske_S:20210909111602g:plain
PNGを埋め込んでみた結果
動かないじゃーーーん!

原因と対策

原因?

ブレークポイントでスクリプトを止めて、デバッグビューから変数を確認してみます。

f:id:uske_S:20210909112532p:plain
お分かりいただけただろうか…
本来であれば画像のアイコンあたりにimageというプロパティが存在するはずなのです*2
おそらくこれが原因でimageがうまく差し替わってない?

(ここにたどり着くまでに1時間近く溶かしています)

対策

ということで、じゃぁ事前にimageプロパティを生やせばいいんでしょ、と。

btn.addEventListener('click', function() {
    this.image = this.image; // ←ここ!
    if (!this.isActive) {
        this.image = mySUIImage.active;
        this.isActive = true;
    } else {
        this.image = mySUIImage.deactive;
        this.isActive = false;
    }
});

と、これだけではだめでした。ちゃんとこれはScriptUIImageですよ、というインスタンス化(ScriptUI.newImage()メソッド)が改めて必要だった。

(ここにたどり着くまでにさらに1時間弱溶かしています)

解決

これで実装できました。

//@targetengine suiimagetest

var mySUIImage = {
    active: ScriptUI.newImage(File.decode("%C2%89PNG%0D………")),
    deactive: ScriptUI.newImage(File.decode("%C2%89PNG%0D………")),
};

var w = new Window("palette", "SUIImage test");
var btn = w.add('image', undefined, mySUIImage.deactive);

btn.isActive = false;
btn.addEventListener('click', function() {
    this.image = ScriptUI.newImage(this.image); // ←おまじない
    if (!this.isActive) {
        this.image = mySUIImage.active;
        this.isActive = true;
    } else {
        this.image = mySUIImage.deactive;
        this.isActive = false;
    }
});

w.show();

f:id:uske_S:20210909114727g:plain
完成!

余談

ScriptUI.newImage()メソッドには省略可能な引数が3つあり(JavaScript Toolguide「ScriptUI classの項」を参照)、順に標準時(normal)、利用不可時(disable、省略可)、押下時(pressed、省略可)、ホバー時(rollover、省略可)です。
これらはSUIコントロールのIconButtonに画像を指定する際に有効です。
ただ、今回僕が途中で悩んだように、PNGファイルをテキストとして埋め込む方法ではうまくいきませんでした(今回のように何か抜け道はあるとは思いますが未検証)。

//@targetengine suiimagetest

var img = {
    active: File("ファイルパス"),
    deactive: File("ファイルパス"),
};

var w = new Window("palette", "SUIImage test");
var btn = w.add('iconbutton', undefined, ScriptUI.newImage(img.deactive, undefined, img.active, img.active));

w.show();

f:id:uske_S:20210909120259g:plain
IconButtonを使うと処理自体は楽だけど見た目が…

*1:PNGの文字列化方法はここでは割愛

*2:ちなみに僕はlaunch.jsonのexcludesプロパティは全部オフなのでundefinedの変数・プロパティも全部表示させています