今天的练习重点是TreeView的使用,用TreeView的方式做一个工具,设想该工具包含两个功能,查看内置GUIStyle和查看内置Icon,这次只完成了查看内置GUIStyle的功能。
工具截图
MTreeView&MTreeViewItem
继承自TreeView和TreViewItem,是这两个对象的定制封装,添加了泛型约束,简化了子结构添加和TreeViewItem的声明。
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace S.Editor
{
public class MTreeViewItem<T> : TreeViewItem
{
public T data { get; protected set; }
public MTreeViewItem(T data,int id,int depth=0)
{
this.data = data;
this.id = id;
this.depth = depth;
}
}
public abstract class MTreeView<T> : TreeView
{
private int nextItemIndex;
protected TreeViewItem root { get; private set; }
protected List<TreeViewItem> items { get; private set; }
public MTreeView(TreeViewState state) : base(state)=>OnInit();
public MTreeView(TreeViewState state, MultiColumnHeader multiColumnHeader) : base(state, multiColumnHeader)=>OnInit();
protected virtual void OnInit()
{
root=new TreeViewItem(-1,-1);
nextItemIndex = 0;
if (items==null)items=new List<TreeViewItem>();
else items?.Clear();
}
public void InitTreeViewItems(IEnumerable<MTreeViewItem<T>> mItems,bool reload=true)
{
if (items==null)items=new List<TreeViewItem>();
else items.Clear();
int count = mItems == null ? 0 : mItems.Count();
if (count>0)
{
foreach (var item in mItems)items.Add(item);
}
if (reload)Reload();
}
public void AddTreeViewItem(MTreeViewItem<T> item,bool reload=true)
{
items.Add(item);
if (reload)Reload();
}
public MTreeViewItem<T> AddTreeViewItem(T item,bool reload=true)
{
MTreeViewItem<T> treeViewItem= new MTreeViewItem<T>(item,GetIndex());
items.Add(treeViewItem);
if (reload)Reload();
return treeViewItem;
}
protected override TreeViewItem BuildRoot()
{
SetupParentsAndChildrenFromDepths(root,items);
return root;
}
protected override void RowGUI(RowGUIArgs args)
{
MTreeViewItem<T> item = args.item as MTreeViewItem<T>;
if (multiColumnHeader == null)//单列
{
CellGUI(args.rowRect,item,0);
}
else//多列
{
int visibleColumns = args.GetNumVisibleColumns();//可见列数
if (visibleColumns>0)
{
for (int i = 0; i < visibleColumns; i++)CellGUI(args.GetCellRect(i),item,i);
}
}
base.RowGUI(args);
}
protected abstract void CellGUI(Rect rect, MTreeViewItem<T> item, int cowIndex);
public int GetIndex()
{
int callBackIndex = nextItemIndex;
nextItemIndex++;
return callBackIndex;
}
protected override void SingleClickedItem(int id)
{
TreeViewItem item = FindItem(id, root);
if (item == null) return;
MTreeViewItem<T> m_item = FindItem(id, root) as MTreeViewItem<T>;
OnClickItem(m_item);
}
protected MTreeViewItem<T> FindMTreeViewItem(int id)
{
TreeViewItem item = FindItem(id, root);
if (item == null) return null;
return item as MTreeViewItem<T>;
}
protected virtual void OnClickItem(MTreeViewItem<T> item)
{
}
/// <summary>
/// 拷贝字符串到剪贴板
/// </summary>
protected void CopyString(string value)
{
TextEditor textEditor = new TextEditor();
textEditor.text = value;
textEditor.OnFocus();
textEditor.Copy();
}
}
}
EditorToolsWindows
工具窗口入口,继承自MEditorWindow,关于MEditorWindow可参考编辑器拓展项目一
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace S.Editor
{
public class EditorToolsWindows : MEditorWindow
{
private string[] toolbarArray;
private int toolbarIndex;
private TreeViewState treeViewState_guiStyle;
private MultiColumnHeader multiColumnHeader_guiStyle;
private GUIStylesTreeView guiStyleTreeView;
private GUIStyle[] customStyles;
private static EditorToolsWindows window;
[MenuItem("Tools/Unity工具")]
static void OpenToolsWindow()
{
window=GetWindow<EditorToolsWindows>();
window.Show();
}
private void OnEnable()
{
toolbarArray = GetToolbarArray();
treeViewState_guiStyle=new TreeViewState();
multiColumnHeader_guiStyle=CreateMultiColumnHeader();
guiStyleTreeView = new GUIStylesTreeView(treeViewState_guiStyle,multiColumnHeader_guiStyle);
}
protected override void OnBodyGUI()
{
toolbarIndex = GUILayout.Toolbar(toolbarIndex, toolbarArray);
switch (toolbarIndex)
{
case 0:
DrawGUIStyles();
break;
case 1:
break;
}
}
void DrawGUIStyles()
{
if (customStyles==null)
{
customStyles = GUI.skin.customStyles;
foreach (GUIStyle style in customStyles)guiStyleTreeView.AddTreeViewItem(style,false);
guiStyleTreeView.Reload();
}
guiStyleTreeView.OnGUI(new Rect(0,25,window.position.width,window.position.height));
}
public MultiColumnHeader CreateMultiColumnHeader()
{
MultiColumnHeaderState.Column[] columns =
{
new MultiColumnHeaderState.Column()
{
headerContent = new GUIContent("GUIStyleName","GUI样式名字"),
width = 200,
minWidth = 100,
maxWidth = 300,
},
new MultiColumnHeaderState.Column()
{
headerContent = new GUIContent("GUIStyle","GUI样式"),
width = 300,
minWidth = 200,
maxWidth = 400
},
new MultiColumnHeaderState.Column()
{
headerContent = new GUIContent("",""),
width = 70,
maxWidth = 90,
minWidth = 50
},
};
MultiColumnHeaderState state = new MultiColumnHeaderState(columns);
MultiColumnHeader header = new MultiColumnHeader(state);
return header;
}
string[] GetToolbarArray()
{
return new string[2] {"内置GUIStyle", "内置Icon"};
}
}
}
GUIStylesTreeView
继承自MTreeView,约束泛型为GUIStyle,这个TreeView负责展示内置的GUIStyle信息
using System;
using System.Collections.Generic;
using S.Editor;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
public class GUIStylesTreeView : MTreeView<GUIStyle>
{
private MTreeViewItem<GUIStyle> selectedItem;
private Vector2 infoScrollPos;
private int leftBtnIndex;
private GUIContent leftBtnContent;
private GUIContent textColorContent;
private SearchField searchField;
public GUIStylesTreeView(TreeViewState state, MultiColumnHeader multiColumnHeader) : base(state, multiColumnHeader)
{
}
protected override void OnInit()
{
base.OnInit();
rowHeight = 30;
showBorder = true;
showAlternatingRowBackgrounds = true;
useScrollView = true;
leftBtnContent=new GUIContent();
leftBtnContent.image = EditorGUIUtility.IconContent("Audio Mixer").image;
textColorContent=new GUIContent("TextColor");
searchField=new SearchField();
}
protected override bool DoesItemMatchSearch(TreeViewItem item, string search)
{
MTreeViewItem<GUIStyle> mItem = item as MTreeViewItem<GUIStyle>;
return mItem.data.name.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0;
}
private Rect viewAreaRect;
private Rect infoAreaRect;
private Rect searchAreaRect;
public override void OnGUI(Rect rect)
{
searchAreaRect=new Rect(rect.x+rect.width*0.5f,rect.y,rect.width*0.5f,20);
searchString=searchField.OnGUI(searchAreaRect,searchString);
viewAreaRect=new Rect(rect.x,rect.y+20,rect.width,rect.height*0.7f);
infoAreaRect=new Rect(rect.x,rect.y+viewAreaRect.height+searchAreaRect.height,rect.width,rect.height-viewAreaRect.height-searchAreaRect.height);
base.OnGUI(viewAreaRect);
OnInfoGUI(infoAreaRect);
}
void OnInfoGUI(Rect rect)
{
Rect leftRect = new Rect(infoAreaRect.x,infoAreaRect.y,80,infoAreaRect.height);
GUILayout.BeginArea(leftRect,"","grey_border");
OnLeftInfoGUI();
GUILayout.EndArea();
Rect rightRect = new Rect(infoAreaRect.x+leftRect.width,infoAreaRect.y,rect.width-leftRect.width,infoAreaRect.height);
GUILayout.BeginArea(rightRect,"","grey_border");
OnRightInfoGUI();
GUILayout.EndArea();
}
void OnLeftInfoGUI()
{
if (leftBtnIndex == 0) EditorGUILayout.BeginHorizontal("InsertionMarker");
else EditorGUILayout.BeginHorizontal();
leftBtnContent.text = "详情";
if (GUILayout.Button(leftBtnContent,EditorStyles.label))
{
leftBtnIndex = 0;
}
EditorGUILayout.EndHorizontal();
if (leftBtnIndex == 1) EditorGUILayout.BeginHorizontal("InsertionMarker");
else EditorGUILayout.BeginHorizontal();
leftBtnContent.text = "预览";
if (GUILayout.Button(leftBtnContent,EditorStyles.label))
{
leftBtnIndex = 1;
}
EditorGUILayout.EndHorizontal();
}
void OnRightInfoGUI()
{
if (selectedItem==null) return;
if (leftBtnIndex==0)
{
infoScrollPos=EditorGUILayout.BeginScrollView(infoScrollPos);
EditorGUILayout.LabelField($"Name: {selectedItem.data.name}");
EditorGUILayout.LabelField($"Alignment: {selectedItem.data.alignment}");
EditorGUILayout.LabelField($"Font: {selectedItem.data.font?.name}");
EditorGUILayout.LabelField($"FontSize: {selectedItem.data.fontSize}");
EditorGUILayout.LabelField($"FontStyle: {selectedItem.data.fontStyle}");
EditorGUILayout.ColorField(textColorContent,selectedItem.data.normal.textColor,false,false,false);
EditorGUILayout.LabelField($"ContentOffset: {selectedItem.data.contentOffset}");
EditorGUILayout.LabelField($"FixedHeight: {selectedItem.data.fixedHeight}");
EditorGUILayout.LabelField($"FixedWidth: {selectedItem.data.fixedWidth}");
EditorGUILayout.LabelField($"LineHeight: {selectedItem.data.lineHeight}");
EditorGUILayout.EndScrollView();
}
else
{
EditorGUILayout.LabelField("",selectedItem.data);
}
}
protected override void CellGUI(Rect rect, MTreeViewItem<GUIStyle> item, int cowIndex)
{
switch (cowIndex)
{
case 0:
GUI.Label(rect,item.data.name);
break;
case 1:
rect.y += 5;
GUIStyle style = item.data;
GUIStyle st = new GUIStyle(style);
float scale = 20/st.fixedHeight;
st.fixedHeight *= scale;
st.fixedWidth *= scale;
GUI.Box(rect,"",st);
break;
case 2:
rect.height = 20;
rect.y += 5;
if (GUI.Button(rect,"Copy"))CopyString(item.data.name);
break;
}
}
protected override void SelectionChanged(IList<int> selectedIds)
{
infoScrollPos = default;
int id = selectedIds[0];
var item = FindMTreeViewItem(id);
selectedItem = item;
}
}