Photoshop UXP への移行について 後編
はじめに
テクニカルアーティスト室所属の dockurage です。 当室では、Maya、Unity、Photoshop といったソフトウェアにおける業務効率化のためにプラグインを作成しています。 本記事では私が担当していた Photoshop Extension の UXP 移行についてのお話をしたいと思います。 以前より Photoshop のプラグイン開発では CEP(Common Extensibility Platform)を使用していましたが、次世代の開発プラットフォームである UXP(Unified Extensibility Platform)に移行を行いました。UXPの導入方法については以下の記事で紹介しておりますので、あわせてご覧ください。
今回は実際に UXP 開発でつまづいた点をお話します。
Photoshop の状態を更新する場合は core.executeAsModal が必須
Photoshop の状態を更新するような処理 (レイヤー名を変えるなど) を行う場合は、executeAsModal のスコープ内に実装しないといけない決まりがあります。スコープ外で実行するとエラーダイアログが表示されて動作しません。 ※ 状態を取得する系の処理 (レイヤー名を取得するなど) の場合は executeAsModal なしでも動作するようでした。
import { action, core } from "photoshop";
core.executeAsModal(async () => {
// ここに Photoshop の状態を更新する処理を書く必要があります
const command = {
_obj: "set",
_target: [{
_enum: "ordinal",
_ref: "layer",
_value: "targetEnum"
}],
to: {
_obj: "layer",
name: "リネーム"
}
};
await action.batchPlay([command], {});
}, {commandName: "test"});
Spectrum UXP Widgets
UXP での UI 制作方法は、標準の HTML 要素、組み込みの Spectrum UXP Widgets、および Spectrum Web Components の 3つがあります。 組み込みの Spectrum UXP Widgets と Spectrum Web Components の違いは、どちらも Spectrum という Adobe が提供するオープンソースのガイドラインで実装されたコンポーネント群ですが、前者は Photoshop にあらかじめ組み込まれているもので、後者は package.json で追加できるオープンソースのWebコンポーネントとのことです。 Spectrum Web Components は魅力的でしたが、移行作業中 UXP v7.0 にアップデートされた時点でベータ版として追加されたため採用は見送りました。
標準 HTML | Spectrum UXP Widgets | Spectrum Web Components (Beta) | |
---|---|---|---|
HTML / CSS / Javascript で実装する。手間はかかるが必要な機能を実装することができる | あらかじめ Photoshop に組み込まれているコンポーネントを使用する。拡張できないことはないがUIの細かい挙動などできないケースもをある | package.json で必要なコンポーネントを個別にインストールして利用する。組み込み Spectrum UXP ウィジェットよりも種類が豊富。 |
Spectrum UXP Widgets は HTML のように実装を行います。
<sp-body>
<sp-button onClick={clickHandler} />
</sp-body>
Spectrum UXP Widgets を使用していて気になったことがあります。コンポーネントによって属性の実装状況が違います。例えば JavaScript で addEventListner を使ってイベントを追加することは可能な onChange などが属性で指定できないことがあり、動作していると思い込んで動作していなかったというケースがありました。 そのため Spectrum UXP Widgets の全コンポーネントをラップした React コンポーネントを自作し、そういったつまづきを減らすようにしています。このラッパーのおかげでコンポーネントの拡張もしやすくはなったので、初期に手間はあるもののやってよかったと思っています。
ラッパークラスの実装例
import React, { forwardRef, useRef } from "react";
const ActionButton = forwardRef((props, ref) => {
if (!ref) {
ref = useRef(null);
}
return (
<sp-action-button
ref={ref}
class={props.className}
disabled={props.disabled}
quiet={props.quiet}
size={props.size}
onClick={props.onClick}
>
{props.children}
</sp-action-button>
);
});
export default ActionButton;
実際に利用した例
sp-action-button を React コンポーネントとして利用する形になります。
import React from "react";
import SP from "../lib/spectrum";
export const SampleButton = () => {
const onClickHandler = e => {};
return (
<div>
<SP.ActionButton onClick={onClickHandler}>
<SP.Icon name="ui:More" slot="icon"></SP.Icon>
</SP.ActionButton>
</div>
)
};
useEffect を使用した onChange の追加実装例
UXP の sp-menu に onChange イベントを実装する場合は、useEffect と addEventListener にコールバック関数を登録すると対応できます。この方法で DOM の属性に onChange を追加することができました。
import React, { forwardRef, useEffect, useRef } from "react";
const Menu = forwardRef((props, ref) => {
if (!ref) {
ref = useRef(null);
}
useEffect(() => {
const dispatchChangeHandler = e => props.onChange && props.onChange(e);
ref.current && ref.current.addEventListener(”change”, dispatchChangeHandler);
return () => {
ref.current && ref.current.removeEventListener(”change”, dispatchChangeHandler);
};
}, [props.onChange]);
return (
<sp-menu
ref={ref}
class={props.className}
slot={props.slot === ”options” ? ”options” : undefined}
selectedIndex={props.selectedIndex}
size={props.size}
onClick={props.onClick}
>
{props.children}
</sp-menu>
);
});
export default Menu;
実際に利用した例
import React from "react";
import SP from "../lib/spectrum";
export const SampleMenu = () => {
const onClickHandler = e => {};
return (
<div>
<SP.Menu onChange={onChangeHandler}>
<SP.MenuItem>Home</SP.MenuItem>
<SP.MenuItem>Config</SP.MenuItem>
</SP.Menu>
</div>
)
};