unity editor文件数(支持勾选框)
使用的时候new一个box即可
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;[Serializable]
public class TGFileTreeViewBox<T> where T : OdinMenuEditorWindow
{public TGFileTreeView fileTreeView;private Vector2 scrollPosition;public TGFileTreeViewBox(bool ShowCheckboxes = true){fileTreeView = new TGFileTreeView(new List<string>(), ThisRepaint);fileTreeView.ShowCheckboxes = ShowCheckboxes;//fileTreeView = new TGFileTreeView(GetFilePathsTest(), ThisRepaint);fileTreeView.SelectAll();}public TGFileTreeViewBox(List<string> paths, bool ShowCheckboxes = true){//for (int i = 0; i < paths.Count; i++)//{// Logger.Log($"文件夹树视图:{paths[i]}");//}fileTreeView = new TGFileTreeView(paths, ThisRepaint);fileTreeView.ShowCheckboxes = ShowCheckboxes;//fileTreeView = new TGFileTreeView(GetFilePathsTest(), ThisRepaint);//fileTreeView.ToggleExpandAll(true);fileTreeView.SelectAll();}[OnInspectorGUI]private void DrawCustomInspector(){float contentHeight = fileTreeView.GetHeight();float maxHeight = Mathf.Min(contentHeight, 250f);scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Height(maxHeight));fileTreeView.DrawTreeView();//if (GUILayout.Button("打印选中路径"))//{// foreach (var path in fileTreeView.CheckedPaths)// {// Logger.Log($"选中了--{path}");// }//}//if (GUILayout.Button("打印未选中路径"))//{// foreach (var path in fileTreeView.UncheckedPaths)// {// Logger.Log($"未选中--{path}");// }//}//if (GUILayout.Button("打印选中文件路径"))//{// foreach (var path in fileTreeView.CheckedFilePaths)// {// Logger.Log($"选中文件了--{path}");// }//}GUILayout.EndScrollView();}// 刷新界面的方法public static void ThisRepaint(){var window = UnityEditor.EditorWindow.GetWindow<T>();window?.Repaint();}private static List<string> GetFilePathsTest(){// 直接定义路径数据return new List<string>{"Assets/_Test/sedan.prefab","Assets/_Test/mat/New Material.mat","Assets/_Test/mat/New Material 1.mat","Assets/_Test/mat/New Material 2.mat","Assets/_Test/mat/New Material 3.mat","Assets/_Test/New Material 1.mat","Assets/_Test/New Material 2.mat","Assets/_Test/New Material 3.mat","Assets/_Test/New Material 4.mat","Assets/_Test/New Material.mat","Assets/_Test/source/sedan.fbx","Assets/_Test/source/sedan 1.fbx","Assets/_Test/TestNull","Assets/_Test/textures/internal_ground_ao_texture.jpeg","Assets/_Test/textures/internal_ground_ao_texture 1.jpeg","Assets/_Test/textures/internal_ground_ao_texture 2.jpeg","Assets/_Test/textures/internal_ground_ao_texture 3.jpeg","Assets/_Test/textures/internal_ground_ao_texture 4.jpeg","Assets/_Test/textures/internal_ground_ao_texture 5.jpeg","Assets/_Test/textures/internal_ground_ao_texture 6.jpeg","Assets/_Test/textures/internal_ground_ao_texture 7.jpeg","Assets/_Test/textures/internal_ground_ao_texture 8.jpeg","Assets/_Test/textures/internal_ground_ao_texture 9.jpeg","Assets/_Test/textures/internal_ground_ao_texture 10.jpeg","Assets/_Test/textures/internal_ground_ao_texture.tga"};}
}
using Sirenix.OdinInspector;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;public class TGFileTreeView
{private List<string> allfilePaths = new List<string>(); // 传入的路径private List<string> filePaths = new List<string>(); // 当前有效路径,经过过滤private List<string> checkedPaths = new List<string>(); // 勾选的路径private List<string> uncheckedPaths = new List<string>(); // 未勾选的路径private List<string> checkedFilePaths = new List<string>(); // 选中的文件路径列表private Dictionary<string, bool> foldoutStates = new Dictionary<string, bool>();private Dictionary<string, bool> checkboxStates = new Dictionary<string, bool>();private string selectedFilePath = "";private Action repaintAction;private int treeHeight;private bool isExpandAll = true;public List<string> CheckedPaths => checkedPaths;public List<string> UncheckedPaths => uncheckedPaths;public List<string> AllPaths => allfilePaths; // 传入的路径列表public List<string> CurrentValidPaths => filePaths; // 当前有效路径public List<string> CheckedFilePaths //选中的文件路径列表{get{checkedFilePaths.Clear();foreach (var item in checkedPaths){if (Path.HasExtension(item)){checkedFilePaths.Add(item);}}return checkedFilePaths;}}public Action<string, bool> OnCheckStateChanged; // path 和状态public bool ShowCheckboxes = true; // 全局控制是否显示勾选框public TGFileTreeView(List<string> paths, System.Action repaintAction){allfilePaths = paths;this.repaintAction = repaintAction;InitializePaths();}// 初始化时计算有效路径,并根据勾选状态更新 CheckedPaths 和 UncheckedPathsprivate void InitializePaths(){// 过滤出有效路径filePaths = allfilePaths.Where(path => File.Exists(path) || Directory.Exists(path)).ToList();// 初始化勾选与取消勾选的路径checkedPaths = new List<string>();uncheckedPaths = new List<string>();foreach (var path in filePaths){checkboxStates[path] = false;uncheckedPaths.Add(path);}}public void DrawTreeView(){CheckForDeletedFiles();if (filePaths.Count == 0){EditorGUILayout.LabelField("没有可用的文件路径!!");return;}DrawFileTree(filePaths);}private void CheckForDeletedFiles(){filePaths.Clear();foreach (var path in allfilePaths){if (File.Exists(path) || Directory.Exists(path)){filePaths.Add(path);}}}private void DrawFileTree(List<string> paths){TGTreeNode root = BuildTree(paths);treeHeight = GetTotalHeight(root);DrawNode(root, 0);}private void DrawNode(TGTreeNode node, int indentLevel){EditorGUILayout.BeginHorizontal();if (!foldoutStates.ContainsKey(node.Path))foldoutStates[node.Path] = isExpandAll;if (!checkboxStates.ContainsKey(node.Path)){checkboxStates[node.Path] = false;UpdateCheckboxLists(node.Path, false);}float lineHeight = 20f;float indentX = indentLevel * 20f;float foldoutWidth = 14f;float spacingA = 20f; // 箭头和勾选框之间float toggleWidth = 18f;float spacingB = 4f; // 勾选框和图标之间float iconWidth = 20f;float spacingC = 4f; // 图标和文件名之间Rect lineRect = GUILayoutUtility.GetRect(0, lineHeight, GUILayout.ExpandWidth(true));float x = lineRect.x + indentX;// 折叠箭头if (node.IsFolder && node.Children.Count > 0){Rect foldoutRect = new Rect(x, lineRect.y + 3, foldoutWidth, 14f);foldoutStates[node.Path] = EditorGUI.Foldout(foldoutRect, foldoutStates[node.Path], GUIContent.none, false);x += foldoutWidth + spacingA;}else{x += foldoutWidth + spacingA;}// 勾选框if (ShowCheckboxes){// 勾选框Rect toggleRect = new Rect(x, lineRect.y + 1, toggleWidth, 18f);bool oldChecked = checkboxStates[node.Path];bool newChecked = GUI.Toggle(toggleRect, oldChecked, GUIContent.none);if (oldChecked != newChecked){checkboxStates[node.Path] = newChecked;UpdateCheckboxLists(node.Path, newChecked);}x += toggleWidth + spacingB;}else{x += spacingB; // 保留缩进对齐}// 图标Texture icon = GetIconForPath(node.Path, node.IsFolder);if (icon != null){Rect iconRect = new Rect(x, lineRect.y, iconWidth, 20f);GUI.DrawTexture(iconRect, icon);x += iconWidth + spacingC;}// 文件名Rect labelRect = new Rect(x, lineRect.y, lineRect.width - (x - lineRect.x), lineHeight);GUIContent labelContent = new GUIContent(GetFileName(node.Path));if (selectedFilePath == node.Path)EditorGUI.DrawRect(lineRect, new Color(0.24f, 0.49f, 0.90f, 0.3f));HandleClick(labelRect, node.Path);GUI.Label(labelRect, labelContent);EditorGUILayout.EndHorizontal();if (node.IsFolder && foldoutStates[node.Path]){foreach (var child in node.Children){DrawNode(child, indentLevel + 1);}}}private void UpdateCheckboxLists(string path, bool isChecked){checkboxStates[path] = isChecked;if (isChecked){if (!checkedPaths.Contains(path)) checkedPaths.Add(path);uncheckedPaths.Remove(path);}else{if (!uncheckedPaths.Contains(path)) uncheckedPaths.Add(path);checkedPaths.Remove(path);}// 更新子项状态var keys = checkboxStates.Keys.ToList();foreach (var kvp in keys){if (kvp != path && kvp.StartsWith(path + "/")){checkboxStates[kvp] = isChecked;if (isChecked){if (!checkedPaths.Contains(kvp)) checkedPaths.Add(kvp);uncheckedPaths.Remove(kvp);}else{if (!uncheckedPaths.Contains(kvp)) uncheckedPaths.Add(kvp);checkedPaths.Remove(kvp);}}}OnCheckStateChanged?.Invoke(path, isChecked);UpdateParentCheckboxStates(path);}private void UpdateParentCheckboxStates(string path){string parentPath = GetParentPath(path);if (string.IsNullOrEmpty(parentPath)) return;var childPaths = checkboxStates.Keys.Where(k => GetParentPath(k) == parentPath).ToList();bool anyChildChecked = childPaths.Any(k => checkboxStates.ContainsKey(k) && checkboxStates[k]);checkboxStates[parentPath] = anyChildChecked;if (anyChildChecked){if (!checkedPaths.Contains(parentPath)) checkedPaths.Add(parentPath);uncheckedPaths.Remove(parentPath);}else{if (!uncheckedPaths.Contains(parentPath)) uncheckedPaths.Add(parentPath);checkedPaths.Remove(parentPath);}UpdateParentCheckboxStates(parentPath);}private string GetParentPath(string path){if (string.IsNullOrEmpty(path)) return null;int lastSlashIndex = path.LastIndexOf('/');if (lastSlashIndex <= 0) return null;return path.Substring(0, lastSlashIndex);}private void HandleClick(Rect rect, string path){Event e = Event.current;if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition)){if (e.clickCount == 1){selectedFilePath = path;repaintAction?.Invoke();}else if (e.clickCount == 2){if (File.Exists(path) || Directory.Exists(path)){EditorUtility.FocusProjectWindow();UnityEngine.Object asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);if (asset != null){EditorGUIUtility.PingObject(asset);Selection.activeObject = asset;}}}e.Use();}}private TGTreeNode BuildTree(List<string> paths){TGTreeNode root = new TGTreeNode("Assets", true);foreach (string path in paths){string[] parts = path.Split('/');TGTreeNode current = root;for (int i = 1; i < parts.Length; i++){string part = parts[i];string fullPath = string.Join("/", parts, 0, i + 1);bool isFile = (i == parts.Length - 1) && Path.HasExtension(part);if (!current.HasChild(part)){current.Children.Add(new TGTreeNode(fullPath, !isFile));}current = current.GetChild(part);}}return root;}private string GetFileName(string path){return Path.GetFileName(path);}private int GetTotalHeight(TGTreeNode node){int totalHeight = 25;bool isFolded = !foldoutStates.ContainsKey(node.Path) || !foldoutStates[node.Path];if (node.IsFolder && !isFolded){foreach (var child in node.Children){totalHeight += GetTotalHeight(child);}}return totalHeight;}public int GetHeight(){return treeHeight;}public void ToggleExpandAll(bool _isExpandAll){isExpandAll = _isExpandAll;foldoutStates.Clear();repaintAction?.Invoke();}// 全选public void SelectAll(){foreach (var path in filePaths){if (!checkboxStates.ContainsKey(path)) continue;if (!checkboxStates[path]) // 只有未勾选的才修改{checkboxStates[path] = true;UpdateCheckboxLists(path, true);}}repaintAction?.Invoke(); // 更新界面}// 全不选public void DeselectAll(){foreach (var path in filePaths){if (!checkboxStates.ContainsKey(path)) continue;if (checkboxStates[path]) // 只有勾选的才修改{checkboxStates[path] = false;UpdateCheckboxLists(path, false);}}repaintAction?.Invoke(); // 更新界面}private Texture GetIconForPath(string path, bool isFolder){if (isFolder) return EditorGUIUtility.IconContent("Folder Icon").image;string extension = Path.GetExtension(path).ToLower();switch (extension){case ".prefab": return EditorGUIUtility.IconContent("Prefab Icon").image;case ".fbx":case ".obj":case ".blend": return EditorGUIUtility.IconContent("Mesh Icon").image;case ".mat": return EditorGUIUtility.IconContent("Material Icon").image;case ".shader":case ".compute": return EditorGUIUtility.IconContent("Shader Icon").image;case ".png":case ".jpg": return EditorGUIUtility.IconContent("Texture Icon").image;case ".anim": return EditorGUIUtility.IconContent("Animation Icon").image;case ".controller": return EditorGUIUtility.IconContent("AnimatorController Icon").image;case ".unity": return EditorGUIUtility.IconContent("SceneAsset Icon").image;case ".ttf":case ".otf": return EditorGUIUtility.IconContent("Font Icon").image;default: return EditorGUIUtility.IconContent("DefaultAsset Icon").image;}}
}public class TGTreeNode
{public string Path { get; private set; }public bool IsFolder { get; private set; }public List<TGTreeNode> Children { get; private set; }public TGTreeNode(string path, bool isFolder){Path = path;IsFolder = isFolder;Children = new List<TGTreeNode>();}public bool HasChild(string path){return Children.Exists(child => child.Path.EndsWith(path));}public TGTreeNode GetChild(string path){return Children.Find(child => child.Path.EndsWith(path));}
}