Unityの最新UIシステム「UI Toolkit」でランタイムUIを作成する方法まとめ

# はじめに
本記事では、Unityの最新のUIシステムである「**UI Toolkit**」を用いてランタイムUIを作成する手法について解説します。
はじめにUnityのランタイムUIのいける「UI Toolkit」の位置付けについて軽く触れた後、UI作成・ランタイムでの使用のための基本的な手順を解説します。実践的なテクニックや注意点なども適宜解説しています。
コードを再現しやすいように、チュートリアル形式で解説を行っていますが、内容ごとに章単位で独立に参照できるような形にしています。

Unityのバージョンは `2022.2.4f1` を使用します。

## 目次
- [UI Toolkitについて](#ui-toolkitについて)
- [ランタイムUIを作成する方法](#ランタイムuiを作成する方法)
- [ランタイムUIの基本操作](#ランタイムuiの基本操作)
  - [スクリプトからインスタンス化する方法](#スクリプトからインスタンス化する方法)
  - [表示テキストやボタンのイベントを設定する方法](#表示テキストやボタンのイベントを設定する方法)
  - [サイズや位置を操作する方法](#サイズや位置を操作する方法)
  - [リストを作成する方法](#リストを作成する方法)
- [実践的なテクニック](#実践的なテクニック)
  - [親要素に子要素としてAddする](#親要素に子要素としてaddする)
  - [ListViewの使い勝手をよくする小技](#listviewの使い勝手をよくする小技)
  - [ドラッグ&ドロップで操作する方法](#ドラッグドロップで操作する方法)

# UI Toolkitについて
2023年3月現在、Unityが提供しているUIシステムには「**UI Toolkit**」, 「**Unity UI (uGUI)**」, 「**IMGUI**」 の3種類があります。
「UI Toolkit」は一番新しいシステムで、HTML/CSSライクなUIの設計手法や、パフォーマンス性能、UIの可搬性などの多くの強力なメリットがあります。
ただし、「UI Toolkit」は開発途上のため、2023年の3月現在ではランタイムにおいてはまだ「Unity UI (uGUI)」に対する代替手段の扱いです。しかし、将来的には「UI Toolkit」がランタイムにおいても公式に推奨のシステムになる方針[^1]のようです。


:::note info
「UI Toolkit」は元々「UIElements」という名称のエディタ限定の機能でしたが、2020年にランタイムでも使える機能としてアップグレードされた際に「UI Toolkit」と[改称されました](https://blog.unity.com/technology/unity-2020-1-beta-is-now-available-for-feedback)。
:::

また、本記事では「UI Toolkit」を実際に使用するための知識を紹介していますが、「UI Toolkit」のシステムの概要や現在の立ち位置を把握したい場合は、以下の資料が参考になると思います。
### 参考
[公式ドキュメント: UnityのUIシステムの比較](https://docs.unity3d.com/ja/2023.1/Manual/UI-system-compare.html)
[公式のサンプルゲーム](https://assetstore.unity.com/packages/essentials/tutorial-projects/ui-toolkit-sample-dragon-crashers-231178?locale=ja-JP)
[UI ToolKitを導入して効率よくUIを構築する](https://forpro.unity3d.jp/unity_pro_tips/2022/04/21/3629/)
[Unity UI Toolkit is better than expected](https://prographers.com/blog/unity-ui-toolkit-is-better-than-expected) (※英文)
# ランタイムUIを作成する方法
「UI Toolkit」のUIアセットであるUXMLを作成するには、[UI Builder](https://docs.unity3d.com/ja/2023.1/Manual/UIBuilder.html) を利用するのが一般的です。

:::note info
【UI Builderとは】
UI Builderは「UI Toolkit」用のWYSIWYGエディタです。「UI Toolkit」ではUXML/USSというHTML/CSSライクな形式でUIの内容を記述することでUIを表現しますが、UI Builderを使用することで直接UXMLを記述することなく直感的にUIを作成できます。細かく調整するためにはある程度HTMLとCSSの記法やルールを理解する必要があります。
:::

### 準備: 空のプロジェクトを作成する
準備として、「2D」タイプの空のプロジェクトをUnityで作成します。プロジェクト名は何でも良いです。

### UI Builder によるUI作成
まずは試しにUI Builderを使って簡単な[^2]UIを作成してみます。

#### UIアセット (UXML) の作成
まずはメニューバーから「Window->UI Toolkit -> UI Builder」とクリックすると、UI Builderのウィンドウが立ち上がります。
ウィンドウが開いたら、
1. 左下の「Library」から「VisualElement」（画像赤色部分）をウィンドウ右側のViewport（画像水色部分）にドラッグ&ドロップして追加します。見た目が透明なので分かりにくいですが、画像黄色部分でも反映を確認できます。
2. 「Libarary」の「Button」（画像赤色部分）を選択して、同じようにドラッグ&ドロップで追加します。
3. 画像黄色部分のように、「Hierarchy」でVisualElementの配下にButtonが配置されいることを確認します。
![スクリーンショット 2023-03-09 042521 - Copy.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/20907b88-12b8-0b2d-7164-14823fa33a74.jpeg)

4. 最後に、Ctrl + S (Winの場合) で保存するとファイル名の入力が求められるので、 `SimpleButton` というファイル名で「Assets/UI」に保存します。（「Assets/UI」は新規ディレクトリとして作成します。）

:::note info
【Visual Elementについて】
「UI Toolkit」では、全てのUIの構成要素はVisual Elementから成ります。
ランタイムにおいては、UIDocumentを介してGameObjectにrootVisualElementがアタッチされ、各UIの構成要素は、rootVisualElementを根とした、VisualElmentをノードとする木構造として表現されます。
:::
#### 作成したUIをSceneに設置する

1. Hierarchyウィンドウ内で右クリックし、「UIToolkit->UIDocument」とクリックすると、UIDocumentが設置されます。[^3]名前は適当に `MyUIDocument`としておきます。
2. `MyUIDocument` をクリックし、Inspectorウィンドウから `Source Asset` のフィールドに先程作成した `SimpleButton.uxml` をドラッグ&ドロップしてセットします。下の画像のようにセットされていることを確認します。
![スクリーンショット 2023-03-06 025550.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/6954d6a8-d486-e02f-47e7-ee84fba60270.png)

3. ゲームビューに先程作成したUIが表示されていることを確認できます。
![スクリーンショット 2023-03-06 041939.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/f405d892-d2df-c562-ed0a-7bce11213111.png)

#### UIを編集する
次に、UIのレイアウトを変更してみます。UnityのProjectウィンドウから、先程保存した `SimpleButton.uxml` を開くと再びUI Builderが立ち上がります。
まずはUI Builderの「Hierarchy」から 、「Visual Element」をクリックします。すると、右側に「Inspector」が表示されます。各項目はそのVisual Element要素の各属性を指定するものです。

:::note info
CSSのように Style Shhet (USS) を用いて属性を指定することも可能ですが、今回はUXMLに直接記述します。USSについては[公式ドキュメント](https://docs.unity3d.com/ja/2023.1/Manual/UIE-USS.html)を参照して下さい。
:::
(1) Inspector View から、「BackGround->Color」を編集し、RGBAで適当な色[^4]を指定します。ViewPortに反映されるのが確認できます。
![スクリーンショット 2023-03-07 114437.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/06137b1d-169f-772c-35fa-2a15fa87f2d9.png)

(2) 続けて、「Boarder->Width」の値と「Boarder->Radius」の値を10にセットします。さらに、「Boarder->Color」にも適当な色を指定します。
(3) この時点でUnityのGameViewを確認すると、指定した背景や外枠が確認できます。現状は下の画像のようにゲーム画面全体を基準に表示されています。

<details><summary>画像を表示</summary><div>

![スクリーンショット 2023-03-07 121142.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/884c9d65-7787-5949-2d71-67ad91bc19e1.png)
</div></details>

(4) もう少し手を加えてみます。UI Builderに戻り、「Library」のLabelを「Hierarchy」または「ViewPort」にドラッグ&ドロップして、Label要素を追加します。階層構造がButtonと並列になっているか注意して下さい。

<details><summary>画像を表示</summary><div>

![スクリーンショット 2023-03-07 122658.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/fa12de02-523c-a545-792c-95d1dc3ca72a.jpeg)
</div></details>

(5) Hierarchyから `Visual Element` を選択し、「Flex -> Grow」の値に0をセットします。さらに、「Align -> Align Items」に「center」をセットし、「Align -> Align Self」に「Flex Start」をセットします。
(6) 最後に、「Hierarchy」から `Label` を選択し、「Attributes -> Text」の値に「Simple Button Here」と入力します。
(7) この時点でUnityのゲームビューを確認すると、画面左上に局所的に表示されていることを確認できます。
![スクリーンショット 2023-03-07 123638.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/ca041220-52d0-a2eb-c0e7-3f629d75cf5f.jpeg)

:::note info
UIの属性は基本的にHTML準拠のため、ここでは各属性の意味や詳細などには触れていません。もしHTMLやCSSの記法に詳しくなければ、[こちらの記事](https://blog.unity.com/ja/engine-platform/ui-toolkit-at-runtime-get-the-breakdown)がおすすめです。
:::

#### 次のステップへの準備
UIの作り方を学んだので、次はランタイムのスクリプトからの生成・イベントの設定・移動などの基本操作について解説していきます。ここでは、Scriptで操作するための下準備を行います。

(1) SimpleButton.uxmlをUI Builder で開き、それぞれの要素の名前を画像のように変更します。（後ほどScriptから要素単位でアクセスするため）
![スクリーンショット 2023-03-07 133559.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/3a382ebc-af63-e6fe-ef6e-51c102af2d1d.jpeg)
(2) Hierarchyウィンドウの `MyUIDocument` を、Projectウィンドウの `Assets/UI` フォルダ内に ドラッグ&ドロップ して、Prefab化します。

<details><summary>画像を表示</summary><div>

![スクリーンショット 2023-03-07 135118.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/8c286958-d825-e916-4a51-cbf583c7831a.jpeg)
</div></details>

(3) Prefab化したら、Sceneに元々設置していたオブジェクトは一旦削除します。
![スクリーンショット 2023-03-07 135501.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/46a4a2c5-f2ed-45df-b4f4-2399d7f639ca.jpeg)

::: note info
 UIDocumentをSceneにセットしたままMonoBehaviourをアタッチして制御しても良いですが、Prefabを利用するケースも実用上は多いので、ここではScriptからPrefabをInstantiateする方針を採用しています。
:::

# ランタイムUIの基本操作
「UI Toolkit」のランタイムUIにおける主要な基本操作を解説します。
この章では、UIアセット (UXML) として前の章で作成した `MyUIDocument.prefab` を使用することを想定しています。
![スクリーンショット 2023-03-07 155254.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/22417fdc-2d21-1bb3-1a23-be73a525e059.jpeg)

## スクリプトからインスタンス化する方法
「UI Toolkit」は、UIDocumentをPrefab化することで、動的にScene内に生成できます。

::: note info
【UIDocumentについて】
UIDocumentはUIを構成するVisualElementの親要素をGameObjectとしてSceneの画面にアタッチする役割を持っています。したがって、SceneにUIを追加する場合は、UIDocumentをインスタンス化するか、もしくは[既存のVisualElementに子要素として追加](#親要素に子要素としてaddする)する必要があります。
:::

##### チュートリアル
実際にUIDocumentはUIをインスタンス化するUIControllerクラスを作成してみます。
(1) Hierarchyウィンドウを右クリックし、「Create Empty」を選択します。生成されたGameObjectに適当に `UIController` と命名します。
(2) Projectウィンドウで`Assets/Scripts` のフォルダを作成し、Unityからフォルダ内を右クリックして「Create->C# Script」から`UIController.cs` を作成します。
(3) 以下のスクリプトを作成します。
```UIController.cs
public class UIController : MonoBehaviour
{
    [SerializeField] GameObject sampleButtonUI;
    private UIDocument _uiDocument;

    void Start()
    {
        // PrefabからUIを生成
        var buttonObject = Instantiate(sampleButtonUI);
        
        // UIDocumentの参照を保存
        _uiDocument = buttonObject.GetComponent<UIDocument>();
    }
}
``` 
(4) UIControllerのInspectorウィンドウから、`sampleButtonUI`のフィールドにUIDocumentのPrefabをセットします。
![スクリーンショット 2023-03-07 192036.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/632b9271-4b08-12ea-7020-e2399a2cedd5.jpeg)
(5) Sceneを再生すると、PrefabとしてセットしたUIが表示されていることが確認できます。
![スクリーンショット 2023-03-07 192211.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/80613de0-8550-3f62-cd2e-09e519f106cb.jpeg)


## 表示テキストやボタンのイベントを設定する方法
UIの表示テキストやボタンのクリックイベントをセットするには、まずUIDocumentの`rootVisualElement`から、`Q<T>(...)`関数でボタンのVisualElementにアクセスします。
ボタンのクリックイベントをセットする場合は、以下のようにButtonの`clicked`にサブスクライブします。

``` C#
var buttonElement = _uiDocument.rootVisualElement.Q<Button>("Button1"); // 引数にVisualElementの名前を指定
buttonElement.clicked += () => { /* クリック時の処理 */ };
```

##### チュートリアル
上で作成した `UIController` クラスの `Start()` 関数の末尾に以下の行を追加してみましょう。
``` UIController.cs
var buttonElement = _uiDocument.rootVisualElement.Q<Button>("Button1"); // 引数にVisualElementの名前を指定
buttonElement.clicked += () =>
{
    var labelElement = _uiDocument.rootVisualElement.Q<Label>("Label1");
    labelElement.text = "Clicked!";
};
```
ボタンのクリック時に、同様の手法でUIの`Label1`のテキストを変更するようにしています。実行すると、以下のようになります。
![2-3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/ca76ef5d-581b-c09f-8fdd-827a6590d965.gif)


## サイズや位置を操作する方法
UIのサイズや位置の変更もUIのVisualElementにアクセスして行います。
サイズや位置の変更を行いたい場合は、UXMLにベースとなるVisualElementを一つ配置し、残りの要素はそのVisualElementの配下に設置するのが良いでしょう。

::: note info
`uiDocument.rootVisualElement`はPanel Settings依存のVisualElementで、UIではなく(デフォルトでは)スクリーン全体にマッチしているので、通常はこちらを操作することはありません。
:::

サイズや位置の変更は以下のようなコードで実現できます。
```C#
// 操作対象のVisualElementを取得 (※ rootVisualElementは基本的に不適当)
var baseElement = _uiDocument.rootVisualElement.Q<VisualElement>("Base"); // 引数にVisualElementの名前を指定

// UI のサイズ変更
baseElement.style.width = 200; // UIの横幅を変更
baseElement.style.height = 200; // UIの縦幅を変更

// UIの位置変更
baseElement.transform.position = new Vector3(200f, 100f, 0); // UIの座標を変更
```

##### チュートリアル
先程設定したボタンイベントの処理部分で、UIサイズ変更と、ランダムな座標への移動を行ってみます。
``` UIController.cs
buttonElement.clicked += () =>
{
    // UIのBaseのVisual Elementを取得
    var baseElement = _uiDocument.rootVisualElement.Q<VisualElement>("Base");
    // UIのサイズを変更
    baseElement.style.width = 150;
    baseElement.style.height = 150;
    // UIの位置を変更
    baseElement.transform.position = GetRandomScreenPosition();
};
```
すると、以下のような、ボタンを押すとランダムな位置に移動するモーダルが実現できます。
![2-4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/4f2d6c2b-1b8f-a735-6ddd-a2f9760327d8.gif)

## リストを作成する方法
「UI Toolkit」のリストの実装であるListViewを利用してリストUIを作成する方法を解説します。
リストは使う頻度が多い＆扱い方がやや複雑なので、個別の項目で取り上げています。

::: note info
ここではベーシックな方法を紹介していますが、後ほど[楽をする方法](#listviewの使い勝手をよくする小技)も紹介しています。
また、そもそもListViewの代替手段として、[直接親要素にAddする方法](#親要素に子要素としてaddする)もあります。
:::

#### リスト作成に必要なもの
リストを作成する際はListViewの以下の3つのプロパティからリスト内容の情報を渡します。(必須のもののみ記載)
- `makeItem`: リスト要素となるVisualElementを生成するファクトリを渡す
- `bindItem`: リストの各要素を操作するための関数を渡す
- `itemSource`: リスト要素の内容を既定するViewModel的なものを渡す。リストなら中身の型や値は何でも良い。


##### チュートリアル
(1) 次のような、内部にリストを含むUIアセット (UXML) を作成します。
<details><summary>画像を表示</summary><div>

![スクリーンショット 2023-03-09 042249.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/063bcaac-a181-7cbb-4d7e-64dcb32e6424.jpeg)
こちらは [UIを作成する方法](#uiを作成する方法) で作成したUIから、Buttonの部分をList Viewに変更し、UIのサイズを適当に調整したものです。
</div></details>

(2) 作成したUIをUIDocumentにアタッチします。以前の手順で作成したMyUIDocumentのSourceAssetを差し替えた前提で進めます。
![スクリーンショット 2023-03-09 031356.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/c73102a8-1afb-e605-d6e0-9cb83b40e397.jpeg)
(3) `UIController.cs` の `Start()` も以下のように書き換えます。

```UIController.cs
void Start()
{
    // PrefabからUIを生成
    var buttonObject = Instantiate(sampleButtonUI);
    
    // UIDocumentの参照を保存
    _uiDocument = buttonObject.GetComponent<UIDocument>();

    // リスト内容を作成する
    ListView listViewElement = _uiDocument.rootVisualElement.Q<ListView>("List1"); // 引数にVisualElementの名前を指定
    listViewElement.makeItem = () => new Label(); // ここではラベルを要素として生成する（VisualElementなら何でも可）
    listViewElement.bindItem = (item, i) => { ((Label) item).text = $"{i}th element"; }; // リストの各要素を操作する
    listViewElement.itemsSource = Enumerable.Range(0, 5).ToList(); // itemsSourceの要素数がListViewの要素数になる。このケースでは中身は何でも良い
}
```

(4) 画像のようなリストが生成されます。
![スクリーンショット 2023-03-09 033053.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/cd4cdf7d-84b0-e064-b983-da45410022f3.jpeg)

### （おまけ1）縦幅を調整する
残念ながら、ListViewでは各リスト要素の `height` は反映されないので、高さを調整するには`fixedItemHeight` で指定する必要があります。使うケースは多いです。
```C#
listViewElement.fixedItemHeight = 50;
```
![スクリーンショット 2023-03-09 035007.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/2bf9d923-b192-59ef-1158-bffba456c193.jpeg)

### （おまけ2）アセットからVisualElementをロードする
`makeItem` から、アセット (UXML) からロードしたVisualElementを渡すこともできます。

(1) 以下のように`UIController.cs`のコードを編集します。

<details><summary>コードを表示</summary><div>

```UIController.cs
public class UIController : MonoBehaviour
{
    [SerializeField] GameObject sampleListUI;
    [SerializeField] VisualTreeAsset listElementAsset;
    private UIDocument _uiDocument;

    void Start()
    {
        // PrefabからUIを生成
        var buttonObject = Instantiate(sampleListUI);
        
        // UIDocumentの参照を保存
        _uiDocument = buttonObject.GetComponent<UIDocument>();

        // リスト内容を作成する
        ListView listViewElement = _uiDocument.rootVisualElement.Q<ListView>("List1"); // 引数にVisualElementの名前を指定
        listViewElement.makeItem = () => listElementAsset.Instantiate(); // リスト要素となるVisualElementを生成するファクトリを渡す
        listViewElement.bindItem = (item, i) => { item.Q<Label>("Label1").text = $"{i}th element "; }; // リストの各要素を操作する
        listViewElement.itemsSource = Enumerable.Range(0, 2).ToList(); // itemsSourceの要素数がListViewの要素数になる。このケースでは中身は何でも良い
        listViewElement.fixedItemHeight = 120; // リスト要素の縦幅を指定する
    }
}
```
</div></details>

(2) 先程作成した`SimpleButton.uxml`をInspectorウィンドウから `UIController` に渡します。
![スクリーンショット 2023-03-09 040319.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/f6e78ca9-b40e-5cd2-edf2-d124268b1bfb.jpeg)

(3) 実行すると、UXMLからロードしたUIが要素として表示されています。
![スクリーンショット 2023-03-09 035753.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/a710d6e5-aad5-c1d0-29e4-6656461266ec.jpeg)

### （おまけ3）実践的な使い方
ListViewの素のインタフェースはやや冗長で使いにくいので、ListViewの作成処理を関数化・クラス化などして使うのが良いでしょう。
また、少し工夫すると複数のVisualElementを要素として同じリスト上に並べることもできます。
その辺りの話を、この下の [ListViewの使い勝手をよくする小技](#listviewの使い勝手をよくする小技) の節で解説しています。


# 実践的なテクニック
ここでは実践的・応用的なテクニックを紹介していきます

## 親要素に子要素としてAddする
VisualElementに対して `Add()` 関数を用いることで、別のVisualElementを子要素として追加することができます。
```C#
parentElement.Add(chiledElement); // chiledElementを子要素として追加
```

#### 実装例
(1) 以下のような空のVisualElementを含むUIを作成し、UIDocumentとしてSceneに設置します。
<details><summary>画像を表示</summary><div>

![スクリーンショット 2023-03-12 004839.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/89a741d6-72df-d6d0-9f92-1be343501260.jpeg)
</div></details>

(2) UIDocumentに以下のスクリプトをアタッチして、要素を動的に追加します。
```UIController.cs
public class UIController : MonoBehaviour
{
    private UIDocument uiDocument => GetComponent<UIDocument>();

    void Start()
    {
        // リスト内容を作成する
        VisualElement containerElement = uiDocument.rootVisualElement.Q<VisualElement>("Container1"); // 引数にVisualElementの名前を指定
        var smallText = new Label("Hello");
        containerElement.Add(smallText); // ラベルを子要素として追加
        var largeText = new Label("Hello!");
        largeText.style.fontSize = 50;
        containerElement.Add(largeText); // 大きいラベルを子要素として追加
        var button = new Button(() => { Debug.Log("Hello world!");});
        button.text = "Say Hello";
        containerElement.Add(button); // ボタンを子要素として追加
    }
}
```
(3) 以下のように、`Container1` の子要素としてラベルやボタンが配置されていることが分かります。[こちら](#おまけ2アセットからvisualelementをロードする)で説明した方法で自作のUXMLもVisualElmentとして生成・配置することができます。
![スクリーンショット 2023-03-12 005623.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/def71d17-1401-c91e-9edd-bc7e1fe487dc.jpeg)

## ListViewの使い勝手をよくする小技
ListViewの`makeItem`, `itemSource`, `bindItem` を使ってセットするのは扱いづらい、端的に言えばそれぞれの要素が密結合かつ冗長です。

`Add` を使った小技ですが、以下のような拡張メソッドを定義しておけば、`listView.SetListContent(visualElements);` といった具合に、ListViewに直接リスト要素の（しかも任意のタイプな）VisualElementを渡せます。
```ListViewUtils.cs
public static class ListViewUtils {
    public static void SetListContent(this ListView listView, IEnumerable<VisualElement> visualElements)
    {
        var elementList = visualElements.ToList();
        listView.makeItem = () => new VisualElement(); // 空のVisualElementを噛ませる
        listView.itemsSource = elementList;
        listView.bindItem = (item, i) => { item.Add(elementList[i]); }; // 空のVisualElementにAddする
    }
}
```
::: note info
【拡張メソッドの詳細】
空のVisualElementをリストの要素として、そこに受け取ったVisualElementを子要素として`Add`しています。
:::

#### 実装例
実装例は以下のようになります。
```C#
// (1) リスト要素のリストを生成する
var listElements = new VisualElement[]
{
    new Label("hello"),
    new Label("hello!"),
    new Button() {text = "hello world"}
};
// (2) リストにセットする
ListView listViewElement = _uiDocument.rootVisualElement.Q<ListView>("List1"); // 中身をセットしたいListViewを取得
listViewElement.SetListContent(listElements); // リストの要素をセット
listViewElement.fixedItemHeight = 50; // リスト要素の縦幅をセット
```

上のコードを実行すると以下のような表示になります。`Label`と`Button`が同じリストに共存しています。

![スクリーンショット 2023-03-12 013755.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/ae94e74a-9dd6-5d71-909c-e917caf70c3d.jpeg)

## ドラッグ&ドロップで操作する方法
VisualElementをドラッグ＆ドロップで移動させる方法を説明します。

### 1. Manipulator を用いる方法
まず、Scene上に以下のUIを持つUIDocumentが存在すると想定します。
![スクリーンショット 2023-03-12 172435.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/cbdab488-350d-74c7-a77c-ded6c71e7b61.jpeg)
同じオブジェクトに次のMonoBehaviourを追加します。
```DraggableUIBehaviour.cs
public class DraggableUIBehaviour : MonoBehaviour
{
    VisualElement frameElement => GetComponent<UIDocument>().rootVisualElement.Q<VisualElement>("Frame");

    void Start()
    {
        frameElement.AddManipulator(new DragManipulator());
    }
}
```

<details><summary>DragManipulatorのソースコードはこちらです</summary><div>

```DragManipulator.cs
public class DragManipulator : IManipulator
{
    private VisualElement _targetElement;
    private bool _isPointerDown;

    public VisualElement target
    {
        get => _targetElement;
        set
        {
            if (value == null) throw new ArgumentNullException();
            else if (_targetElement == value) return;

            if (_targetElement != null) RemoveEvents(_targetElement);
            _targetElement = value;
            AddEvents(_targetElement);
        }
    }


    private void AddEvents(VisualElement targetElement)
    {
        targetElement.RegisterCallback<PointerMoveEvent>(OnPointerMove);
        targetElement.RegisterCallback<PointerDownEvent>(OnPointerDown);
        targetElement.RegisterCallback<PointerUpEvent>(OnPointerUp);
    }

    private void RemoveEvents(VisualElement targetElement)
    {
        targetElement.UnregisterCallback<PointerMoveEvent>(OnPointerMove);
        targetElement.UnregisterCallback<PointerDownEvent>(OnPointerDown);
        targetElement.UnregisterCallback<PointerUpEvent>(OnPointerUp);
    }
    
    private void OnPointerDown(PointerDownEvent ev)
    {
        _targetElement.CapturePointer(ev.pointerId); // ポインタをキャプチャしてUI範囲外のポインタイベントも取得するようにする
        _isPointerDown = true;
    }

    private void OnPointerUp(PointerUpEvent ev)
    {
        _targetElement.ReleasePointer(ev.pointerId); // ポインタを開放
        _isPointerDown = false;
    }

    private void OnPointerMove(PointerMoveEvent ev)
    {
        if (_isPointerDown)
            target.transform.position += ev.deltaPosition;
    }
}
```
</div></details>

上で作成した`DragManipulator`は `IManimpulator`を継承することで、任意のVisualElementに`AddManipulator`を使ってアタッチすることができます。

![3-1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/68ce0eb5-70ce-6549-6eb8-5174299d8e3c.gif)

この方法は手軽でおすすめですが、デメリットとして`DragManipulator`の内部で`CapturePointer`を行う[^5]ため、他のオブジェクトでマウスイベントを取得できなくなります。例えば、フレーム部分のみドラッグ&ドロップできるようにしたい、というようなケースだとこちらの方法は使えないので、その場合は次の方法を使用します。

### 2. 手動で入力イベントを処理する方法
複数のオブジェクトから入力イベントを処理する場合などは、MonoBehaviourの`Update()`から入力イベントを直接処理します。

以下のような関数でマウス位置である`Input.mousePosition`が該当のVisualElement上にあるのかどうか判定します。VisualElementの座標系ではY軸方向が下方向を正としているので、座標を補正しています。

```C#
public static bool IsOnScreenPoint(this VisualElement visualElement, Vector2 screenPoint)
{
    var visualElementPositionY = Screen.height - screenPoint.y; // Y座標は方向が逆になる
    return visualElement.worldBound.Contains(new Vector2(screenPoint.x, visualElementPositionY));
}p
```

以下が実装例です。
#### 実装例
<details><summary> DraggableFramedUIBehaviour.cs</summary><div>

```DraggableFramedUIBehaviour.cs
public class DraggableFramedUIBehaviour : MonoBehaviour
{
    private VisualElement _frameElement;
    private VisualElement _innerContentElement;
    private readonly DragState _dragState = new DragState();

    void Start()
    {
        _frameElement = GetComponent<UIDocument>().rootVisualElement.Q<VisualElement>("Frame");
        _innerContentElement = GetComponent<UIDocument>().rootVisualElement.Q<VisualElement>("InnerContent");
        
        
        // モーダルの中身を適当に作成
        var container = GetComponent<UIDocument>().rootVisualElement.Q<VisualElement>("Container1");
        container.Add(new Label("Only framed area"));
        container.Add(new Label("is draggable !"));
        container.Add(new Label());
        // クリックすると元の位置に戻るボタン
        container.Add(new Button(() => { _frameElement.transform.position = Vector3.zero; }) {text = "Reset Position"!});
    }

    void Update()
    {
        // Frame上かつInnerContent上ではない場所でクリックされた場合
        if (_frameElement.IsOnScreenPoint(Input.mousePosition)
            && !_innerContentElement.IsOnScreenPoint(Input.mousePosition)
            && Input.GetMouseButtonDown(0))
        {
            _dragState.StartDrag();
        }
        
        if (Input.GetMouseButtonUp(0))
        {
            _dragState.FinishDrag();
        }
        
        if (_dragState.state == DragState.State.Drag)
        {
            var diff = _dragState.UpdateDragPos(Input.mousePosition);
            _frameElement.transform.position += new Vector3(diff.x, -diff.y); // Y座標は方向が逆になる
        }
    }
}

```
</div></details>


<details><summary> その他のクラス </summary><div>

```C#
/// <summary> ドラッグ状態の管理用のクラス </summary>
public class DragState
{
    public enum State
    {
        Idle,
        Drag,
    }

    public State state = State.Idle;
    private Vector2? _previousFramePos;

    public void StartDrag()
    {
        state = State.Drag;
        _previousFramePos = null;
    }
        
    public void FinishDrag()
    {
        state = State.Idle;
        _previousFramePos = null;
    }

    public Vector2 UpdateDragPos(Vector2 currentPos)
    {
        if (state == State.Idle) return Vector2.zero;

        if (_previousFramePos != null)
        {
            var diff = currentPos - _previousFramePos;
            _previousFramePos = currentPos;
            return diff.Value;
        }
        else
        {
            _previousFramePos = currentPos;
            return Vector2.zero;
        }
    }
}

public static class VisualElementUtil
{
    public static bool IsOnScreenPoint(this VisualElement visualElement, Vector2 screenPoint)
    {
        var visualElementPositionY = Screen.height - screenPoint.y; // Y座標は方向が逆になる
        return visualElement.worldBound.Contains(new Vector2(screenPoint.x, visualElementPositionY));
    }
}
```
</div></details>


こちらの実装例では、枠の薄オレンジ色の部分をクリックした場合にのみ反応します。
![3-2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606045/94b602f2-8c92-6c65-b067-384bfe54ac74.gif)


[^1]: https://docs.unity3d.com/ja/2023.1/Manual/UIElements.html
[^2]: （特にデザイン的に）複雑なUIの例は[公式のサンプルゲーム](https://assetstore.unity.com/packages/essentials/tutorial-projects/ui-toolkit-sample-dragon-crashers-231178?locale=ja-JP)が参考になります。
[^3]: このとき、`Assets/UI Toolkit`のフォルダと共に、Panel SettingsアセットがUnityにより自動で作成・アタッチされます。
[^4]: A（不透明度）の値の指定漏れにご注意下さい。
[^5]: `CapturePointer`しないと、ポインタを素早く動かすとうまく追従できません。 (次のQiitaの記事が参考になります: https://qiita.com/ohbashunsuke/items/7692af357328998ac9ca)