【Untiy】エディタ拡張でフォルダを階層表示してトグルで選択する

投稿者: | 2023-04-17

エディタ拡張でフォルダを折り畳んで階層表示するで、フォルダの横にトグルを表示して、トグルで選択したフォルダのパスだけを表示してみます。

エディタウィンドウを作る

フォルダのパスを検索して階層構造にして表示するのは、前の記事と同じですが、今回はトグルも表示するので、フォルダのパスを表すクラスに、トグルで選択されているかどうかのbool型のフィールドを作りました。

  1. public class PathData
  2. {
  3. // 子のパスデータのリスト
  4. public List<PathData> children { get; } = new List<PathData>();
  5.  
  6. // 文字列のパス
  7. public string Value { get; private set; }
  8.  
  9. // コンストラクタ
  10. public PathData(string value)
  11. {
  12. Value = value;
  13. }
  14.  
  15. // 折りたたみが開いているかどうか
  16. public bool foldout;
  17.  
  18. // トグルで選択されているかどうか
  19. public bool toggle;
  20. }

このフィールドの値をGUILayout.Toggleメソッドの引数に渡して、同じ変数で戻り値を受け取ります。

  1. using (new EditorGUILayout.HorizontalScope())
  2. {
  3. // トグルを表示
  4. rootPath.toggle = GUILayout.Toggle(rootPath.toggle, "");
  5.  
  6. // Assetsフォルダのフォールドアウトを表示
  7. rootPath.foldout = EditorGUILayout.Foldout(rootPath.foldout, rootPath.Value);
  8.  
  9. GUILayout.FlexibleSpace();
  10.  
  11. }

Assetsフォルダだけでなく、それ以下のすべてのフォルダのパスを表示するための、ラベルや折りたたみの横にも表示します。GUILayoutOptionなどを使って表示位置を調節しています。

  1.  
  2. // ラベルフィールドのスタイルを調整
  3. labelStyle = new GUIStyle(GUI.skin.label);
  4. labelStyle.padding.left = 15;
  5. // ----------
  6. // さらに子がなければラベルで表示
  7. if (path.children[i].children.Count == 0)
  8. {
  9. using (new EditorGUILayout.HorizontalScope())
  10. {
  11. // トグルを表示
  12. path.children[i].toggle = GUILayout.Toggle(path.children[i].toggle, "", GUILayout.Width(15f));
  13. // ラベルを表示
  14. EditorGUILayout.LabelField(path.children[i].Value, labelStyle);
  15. }
  16.  
  17. }
  18. // さらに子があれば
  19. else
  20. {
  21. using (new EditorGUILayout.HorizontalScope())
  22. {
  23. // トグルを表示
  24. path.children[i].toggle = GUILayout.Toggle(path.children[i].toggle, "", GUILayout.Width(15f));
  25. // 折りたたみを表示
  26. path.children[i].foldout = EditorGUILayout.Foldout(path.children[i].foldout, path.children[i].Value);
  27. }
  28.  
  29. // ...
  30. }

トグルや折りたたみはスクロールビューの中に表示します。スクロールビューが終わるとその下に、「キャンセル」ボタンと「決定」ボタンを表示しています。キャンセルボタンを押すとこのエディターウィンドウを閉じます。

  1. using (new EditorGUILayout.HorizontalScope())
  2. {
  3. GUILayout.FlexibleSpace();
  4.  
  5. // キャンセルボタンを押す
  6. if (GUILayout.Button("キャンセル", GUILayout.Width(100f)))
  7. {
  8. // ウィンドウを閉じる
  9. Close();
  10. }

決定ボタンを押すと、トグルがtrueになっているパスのみのリストを作って、その内容を別のエディタウィンドウに並べて表示します。

  1. if (GUILayout.Button("決定", GUILayout.Width(100f)))
  2. {
  3. // パスのリスト
  4. var pathsList = new List();
  5. // Assetsフォルダのトグルがオンならパスをリストに入れる。
  6. if (rootPath.toggle) pathsList.Add(rootPath.Value);
  7. // トグルがオンになっているパスを探してリストに入れる。
  8. GetSelectedPathsList(rootPath, pathsList);
  9. // トグルがオンのパスがあれば
  10. if (pathsList.Count > 0)
  11. {
  12. // 別のウィンドウにパスを表示
  13. TestDisplayPaths.Create(pathsList);
  14. }
  15. }
  16. }

トグルがオンかどうかを確認するために、階層構造に連なっているインスタンスをすべて走査します。そのために再度、再帰処理をしています。

  1. void GetSelectedPathsList(PathData path, List<string> pathsList)
  2. {
  3. // 子がなければ終了
  4. if (path.children.Count == 0) return;
  5.  
  6. // すべての子を処理
  7. for(int i = 0; i < path.children.Count; i++)
  8. {
  9. // トグルがオンならリストに追加
  10. if (path.children[i].toggle) pathsList.Add(path.children[i].Value);
  11.  
  12. // さらに下の子を処理
  13. GetSelectedPathsList(path.children[i], pathsList);
  14. }
  15. }

このメソッドの引数にルートのパスインスタンスと、リストを渡します。引数のパスに子がなければ終了しますが、あればすべての子を一つ一つ見ていって、トグルがオンになっていればリストに追加します。そして、それらのパスをさらに同じメソッドの引数に渡しています。

リストに要素があれば、別のウィンドウを表示して、リスト内のパスを縦に並べて表示します。ウィンドウを表示するための静的メソッドを作って、その引数にリストを渡します。

  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEditor;
  4.  
  5. public class TestDisplayPaths : EditorWindow
  6. {
  7. List<string> pathsList;
  8.  
  9. // ウィンドウを表示
  10. public static void Create(List<string> pathsList)
  11. {
  12. var w = EditorWindow.GetWindow<TestDisplayPaths>();
  13.  
  14. w.pathsList = pathsList;
  15. }
  16.  
  17. private void OnGUI()
  18. {
  19. if (pathsList == null || pathsList.Count <= 0) return;
  20.  
  21. // リストの要素を一つ一つ処理
  22. for(int i = 0; i < pathsList.Count; i++)
  23. {
  24. // ラベルで表示
  25. GUILayout.Label(pathsList[i]);
  26. }
  27. }
  28.  
  29. }

選択したフォルダを表示

メインメニューからエディタウィンドウを表示できます。

「フォルダを検索」ボタンを押すと、Assetsフォルダとその中にあるフォルダが折りたたみで階層表示されます。フォルダの左にはトグルがついています。

トグルを適当に選択して「決定」ボタンをおしてみます。

すると、別ウィンドウが開き選択したパスの文字列が縦に並んで表示されます。

選択したものと表示されたものが同じであることがわかります。

さらに別のパスを選択して決定ボタンを押すと、表示ウィンドウに追加されています。

これで、フォルダを検索して階層表示し、選択したフォイルのパスだけのリストを作ることができました。

スクリプト

  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEditor;
  4.  
  5. public class SelectFolders : EditorWindow
  6. {
  7. // エディタウィンドウを表示する
  8. [MenuItem("Window/Test/SelectFolders")]
  9. public static void ShowDialog()
  10. {
  11. EditorWindow.GetWindow<SelectFolders>();
  12. }
  13.  
  14. // パスを取得する
  15. void GetSubFoldersData(PathData parent)
  16. {
  17. if (parent.Value == "") return;
  18.  
  19. // サブディレクトリのパスをすべて取得
  20. var childrenValues = AssetDatabase.GetSubFolders(parent.Value);
  21.  
  22. // なければ終了
  23. if (childrenValues.Length == 0) return;
  24.  
  25. // あれば一つずつ処理
  26. foreach(var v in childrenValues)
  27. {
  28. // サブディレクトリのパスのデータを作る
  29. var child = new PathData(v);
  30.  
  31. // 現在のパスの子リストに追加
  32. parent.children.Add(child);
  33.  
  34. // さらに下の階層のフォルダのパスを取得
  35. GetSubFoldersData(child);
  36. }
  37. }
  38.  
  39. PathData rootPath;
  40. Vector2 scrollPosition;
  41. GUIStyle labelStyle;
  42.  
  43. private void OnGUI()
  44. {
  45. // フォルダを検索ボタンを押す
  46. if (GUILayout.Button("フォルダを検索"))
  47. {
  48. // ルートのパスのデータを作る
  49. rootPath = new PathData("Assets");
  50.  
  51. // 下の階層のフォルダのパスを取得
  52. GetSubFoldersData(rootPath);
  53.  
  54. // ラベルフィールドのスタイルを調整
  55. labelStyle = new GUIStyle(GUI.skin.label);
  56. labelStyle.padding.left = 15;
  57.  
  58. }
  59.  
  60. // パスが取得済み
  61. if (rootPath != null)
  62. {
  63. // クリアボタンを表示
  64. if (GUILayout.Button("クリア"))
  65. {
  66. rootPath = null;
  67. return;
  68. }
  69.  
  70. // スクロールビューを表示
  71. using (var scroll = new GUILayout.ScrollViewScope(scrollPosition, GUILayout.MinHeight(0f)))
  72. {
  73. scrollPosition = scroll.scrollPosition;
  74.  
  75. // 横に並べる
  76. using (new EditorGUILayout.HorizontalScope())
  77. {
  78. // トグルを表示
  79. rootPath.toggle = GUILayout.Toggle(rootPath.toggle, "");
  80.  
  81. // Assetsフォルダのフォールドアウトを表示
  82. rootPath.foldout = EditorGUILayout.Foldout(rootPath.foldout, rootPath.Value);
  83.  
  84. GUILayout.FlexibleSpace();
  85.  
  86. }
  87.  
  88. // 現在のインデントレベルを保存
  89. int currentIndentLevel = EditorGUI.indentLevel;
  90.  
  91. // Assetsフォルダの折りたたみが開いているとき
  92. if (rootPath.foldout)
  93. {
  94.  
  95. // 下の階層のフォルダのパスを表示する
  96. ShowPaths(rootPath);
  97.  
  98. // 最後にインデントレベルを戻す。
  99. EditorGUI.indentLevel = currentIndentLevel;
  100.  
  101. }
  102. }
  103.  
  104. GUILayout.FlexibleSpace();
  105.  
  106. // ボタンを横に並べる
  107. using (new EditorGUILayout.HorizontalScope())
  108. {
  109. GUILayout.FlexibleSpace();
  110.  
  111. // キャンセルボタンを押す
  112. if (GUILayout.Button("キャンセル", GUILayout.Width(100f)))
  113. {
  114. // ウィンドウを閉じる
  115. Close();
  116. }
  117. // 決定ボタンを押す
  118. if (GUILayout.Button("決定", GUILayout.Width(100f)))
  119. {
  120. // パスのリスト
  121. var pathsList = new List();
  122. // Assetsフォルダのトグルがオンならパスをリストに入れる。
  123. if (rootPath.toggle) pathsList.Add(rootPath.Value);
  124. // トグルがオンになっているパスを探してリストに入れる。
  125. GetSelectedPathsList(rootPath, pathsList);
  126. // トグルがオンのパスがあれば
  127. if (pathsList.Count > 0)
  128. {
  129. // 別のウィンドウにパスを表示
  130. TestDisplayPaths.Create(pathsList);
  131. }
  132. }
  133. }
  134. }
  135. }
  136. // トグルがオンになっているパスだけのリストを取得
  137. void GetSelectedPathsList(PathData path, List<string> pathsList)
  138. {
  139. // 子がなければ終了
  140. if (path.children.Count == 0) return;
  141. // すべての子を処理
  142. for(int i = 0; i < path.children.Count; i++)
  143. {
  144. // トグルがオンならリストに追加
  145. if (path.children[i].toggle) pathsList.Add(path.children[i].Value);
  146. // さらに下の子を処理
  147. GetSelectedPathsList(path.children[i], pathsList);
  148. }
  149. }
  150. // パスを表示
  151. void ShowPaths(PathData path)
  152. {
  153. // 現在のパスに子がないときは終了
  154. if (path.children.Count == 0) return;
  155. // インデントレベルを一つ上げる
  156. EditorGUI.indentLevel++;
  157. // 現在のインデントレベルを保存
  158. int currentIndentLevel = EditorGUI.indentLevel;
  159. // パスの子を一つずつ処理
  160. for (int i = 0; i < path.children.Count; i++)
  161. {
  162. // さらに子がなければラベルで表示
  163. if (path.children[i].children.Count == 0)
  164. {
  165. using (new EditorGUILayout.HorizontalScope())
  166. {
  167. // トグルを表示
  168. path.children[i].toggle = GUILayout.Toggle(path.children[i].toggle, "", GUILayout.Width(15f));
  169. // ラベルを表示
  170. EditorGUILayout.LabelField(path.children[i].Value, labelStyle);
  171. }
  172. }
  173. // さらに子があれば
  174. else
  175. {
  176. using (new EditorGUILayout.HorizontalScope())
  177. {
  178. // トグルを表示
  179. path.children[i].toggle = GUILayout.Toggle(path.children[i].toggle, "", GUILayout.Width(15f));
  180. // 折りたたみを表示
  181. path.children[i].foldout = EditorGUILayout.Foldout(path.children[i].foldout, path.children[i].Value);
  182. }
  183. // 開いているとき
  184. if (path.children[i].foldout)
  185. {
  186. // さらに子のパスを表示
  187. ShowPaths(path.children[i]);
  188. // インデントレベルを戻す
  189. EditorGUI.indentLevel = currentIndentLevel;
  190. }
  191. }
  192. }
  193. }
  194. }

コメントを残す

メールアドレスが公開されることはありません。