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 和 UncheckedPaths
private 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));
}
}