【Unity】App UIの入門としてDrag and Dropのサンプルコードを読む【App UI】

# 自己紹介
とある事情で様々な方とお会いしてUnityモチベーションえぐい大学生。
これについても許可が出れば記事にします！

# 本記事の趣旨
App UIに入門するにあたって、サンプルコードを読もうと思ったので、ついでに記事を書く。
全体の概要を掴みつつ、少し機能に触れていこうと思う。

もととなるUI Toolkitに関しては触れないこととする。

# App UIとは
突如として界隈でちょこっと有名になったUI Toolkitベースの公式UIフレームワーク。
ランタイム・エディター拡張ともに対応しており、テーマも複数あるなど今までのUnityとは思えない雰囲気。

https://docs.unity3d.com/Packages/com.unity.dt.app-ui@1.0/manual/index.html

## Drag and Dropとは
App UIのサンプルプロジェクトの１つ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3083521/38101d4f-c0ce-2c36-75d2-742cca429434.png)
このような見た目でItem0を除いて全ての要素がドラッグアンドドロップできるUIを実現している。

# サンプルコードを読む
始める前に少し言わせてほしい。なぜ`DragAndDropSampleScript`の`Start`メソッドはコンストラクタではいけないのだ…
## GridView
まず最初に、目につくのがGridViewというVisual Elementだろう。
これは「項目リスト」「表示項目の見た目を制御するメソッド」「実際の値を入れるメソッド」の３つを持つ。

https://docs.unity3d.com/Packages/com.unity.dt.app-ui@1.0/api/Unity.AppUI.UI.GridView.html

### 今回のサンプルでは
今回のサンプルでは以下のような関数が使用されていた。
```csharp:サンプルの「表示項目の見た目を制御するメソッド」
    static VisualElement MakeItem()
    {
        Debug.Log("Make");
        var item = new VisualElement();
        item.AddToClassList("dnd-item");
        var text = new Text();
        text.AddToClassList("dnd-item__label");
        item.Add(text);
        return item;
    }
```
```csharp:サンプルの「実際の値を入れるメソッド」
    void BindSrcItem(VisualElement el, int idx)
    {
        Debug.Log("Bind");
        var item = (Text)el.ElementAt(0);
        item.text = (string)m_SrcList.itemsSource[idx];
    }
```
### その他の機能
ドラッグの検知や複数個同時選択などの機能があるようだ。
ドラッグに関しては`dragCanceled`というイベントがあったがどうあがいても実行されなかった。

### 気になったところ
ドラッグアンドドロップするたびに全てのVisual Elementが再描画され上記２つのメソッドも呼び出されていた。
私もUI Toolkitがそこまで詳しくないためどこの仕様なのか分からないが、ちょっと気になった。

また、ドラッグアンドドロップで移動したのち選択が残るという挙動に違和感を覚えた。

## DropZone
こちらはDropする挙動を設定できるVisualElementのようだ。
Dropされようなとき（`dragStarted`）Dropされたとき（`dropped`）Dropされた後処理（`dragEnded`）がある。

https://docs.unity3d.com/Packages/com.unity.dt.app-ui@1.0/api/Unity.AppUI.UI.DropZone.html

## 今回のサンプルでは
```csharp:Dropされたときのイベント
    void OnDropped(IEnumerable<object> objects)
    {
        var selection = objects.Where(o => o is string).Cast<string>().ToList();
        
        if (selection.Count == 0)
            return;
        
        // Add dropped items to the destination list
        var dstItems = new List<string>(m_DstList.itemsSource?.Cast<string>() ?? Enumerable.Empty<string>());
        dstItems.AddRange(selection);
        m_DstList.itemsSource = dstItems;
        
        // Remove dropped items from the source list
        var srcItems = new List<string>(m_SrcList.itemsSource?.Cast<string>() ?? Enumerable.Empty<string>());
        foreach (var item in selection)
        {
            srcItems.Remove(item);
        }
        m_SrcList.itemsSource = srcItems;
        
        // Hide the dropzone if the destination list is not empty
        m_Dropzone.visibleIndicator = m_DstList.itemsSource != null || m_DstList.itemsSource.Count == 0;
    }
```
```csharp:DropZoneの上に要素が来た時（=Dropされそうなとき）のイベント
    void OnEditorDragStarted()
    {
        // Accept every path from the editor
        m_Dropzone.visibleIndicator = true;
        m_Dropzone.state = DropZoneState.AcceptDrag;
    }
```
```csharp:Dropされた後処理（このメソッドはイベント専用ではなくほかにも呼び出されてる）
    void Reset()
    {
        m_Dropzone.state = DropZoneState.Default;
        m_Dropzone.visibleIndicator = m_DstList.itemsSource == null || m_DstList.itemsSource.Count == 0;
        SetDraggedObjects(null);
    }
```

# さいごに
App UIなんもわからんという状態からこの記事を書いているので全然深く掘れなかったのですが、なんでこんな書き方してるんだろうと思うとこともちらほらあったり。
もうちょっと洗練されたコードも書けそうな気がしたので、今後機会があればまた記事にしたいと思います。

今回はDropZoneがあるということが既に結構大きい発見でした。
App UIの開発はイベントを登録していく形でとてもWeb風だなと、流石UI Toolkitや！！