提交GAS 打算做一个帧同步的GAS

This commit is contained in:
DESKTOP-5RP3AKU\Jisol
2024-10-18 03:16:09 +08:00
parent b0a2e4a900
commit d9b0c78827
726 changed files with 76601 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
#if UNITY_EDITOR
namespace GAS.Editor.General
{
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using GAS.Runtime;
public class ArraySetFromChoicesAsset<T>
{
private List<T> choices;
private string[] choiceNames;
private CustomReorderableList<T> _reorderableList;
string _title;
public ArraySetFromChoicesAsset(T[] initData,List<T> choices,string title,Action<int,T> onEdit)
{
this.choices = choices;
ChoiceNamesCache();
_title = title;
initData = initData ?? new T[0];
_reorderableList =
CustomReorderableList<T>.Create(
initData ,
DrawListHeader,
onEdit,
ItemGUIDraw,
null,
null);
}
void ChoiceNamesCache()
{
switch (this.choices)
{
case List<string> stringChoices:
choiceNames = stringChoices.ToArray();
break;
case List<ScriptableObject> soChoices:
{
choiceNames=new string[soChoices.Count];
for (var i = 0; i < soChoices.Count; i++)
{
var path = AssetDatabase.GetAssetPath(soChoices[i]);
choiceNames[i] = path;
}
break;
}
case List<GameplayTag> tagChoices:
{
choiceNames=new string[tagChoices.Count];
for (var i = 0; i < tagChoices.Count; i++)
{
choiceNames[i] = tagChoices[i].Name;
}
break;
}
default:
{
choiceNames=new string[choices.Count];
for (var i = 0; i < choices.Count; i++)
{
choiceNames[i] = $"{i}:{choices[i].ToString()}";
}
break;
}
}
}
void DrawListHeader (Rect rect)
{
EditorGUI.LabelField(rect, _title);
}
void ItemGUIDraw(Rect rect, T item, int index)
{
var indexOfItem = choices.IndexOf(item);
indexOfItem = Mathf.Clamp(indexOfItem, 0, choices.Count - 1);
indexOfItem = EditorGUI.Popup(
new Rect(rect.x, rect.y, 180, EditorGUIUtility.singleLineHeight),
indexOfItem,
choiceNames);
T newItem = choices[indexOfItem];
_reorderableList.UpdateItem(index, newItem);
}
public void OnGUI()
{
_reorderableList.OnGUI();
}
public List<T> GetItemList()
{
return _reorderableList.GetItemList();
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e2840d9545404cf1b2ef3898d67c3ab4
timeCreated: 1703835297

View File

@@ -0,0 +1,143 @@
#if UNITY_EDITOR
namespace GAS.Editor.General
{
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
public class CustomReorderableList<T>
{
private List<T> _itemList = new List<T>();
private ReorderableList reorderableList;
private ReorderableList.HeaderCallbackDelegate _drawListHeader;
private Action<int, T> _onEdit;
private Action<Rect,T,int> _itemGUIDraw;
private ReorderableList.ElementHeightCallbackDelegate _getElementHeight;
ReorderableList.AddCallbackDelegate _onAddListItem;
public CustomReorderableList(
List<T> itemList ,
ReorderableList.HeaderCallbackDelegate drawListHeader,
Action<int,T> onEdit,
Action<Rect,T,int> itemGUIDraw,
ReorderableList.ElementHeightCallbackDelegate getElementHeight,
ReorderableList.AddCallbackDelegate onAddListItem)
{
_itemList = itemList;
_onEdit = onEdit;
_itemGUIDraw = itemGUIDraw;
_getElementHeight = getElementHeight;
_onAddListItem = onAddListItem;
_drawListHeader = drawListHeader;
OnEnable();
}
public CustomReorderableList(
T[] itemList ,
ReorderableList.HeaderCallbackDelegate drawListHeader,
Action<int,T> onEdit,
Action<Rect,T,int> itemGUIDraw,
ReorderableList.ElementHeightCallbackDelegate getElementHeight,
ReorderableList.AddCallbackDelegate onAddListItem)
{
_itemList = new List<T>();
foreach (var t in itemList)
{
_itemList.Add(t);
}
_onEdit = onEdit;
_itemGUIDraw = itemGUIDraw;
_getElementHeight = getElementHeight;
_onAddListItem = onAddListItem;
_drawListHeader = drawListHeader;
OnEnable();
}
void OnEnable()
{
reorderableList = new ReorderableList(_itemList, typeof(T), true, true, true, true);
if (_drawListHeader != null)
reorderableList.drawHeaderCallback += _drawListHeader;
else
reorderableList.drawHeaderCallback += DrawListHeader;
reorderableList.drawElementCallback += DrawListElement;
if (_getElementHeight != null) reorderableList.elementHeightCallback += _getElementHeight;
reorderableList.onAddCallback += AddListItem;
}
public void OnGUI()
{
reorderableList.DoLayoutList();
}
private void DrawListHeader(Rect rect)
{
EditorGUI.LabelField(rect, $"{typeof(T).Name} List");
}
private void DrawListElement(Rect rect, int index, bool isActive, bool isFocused)
{
var element = _itemList[index];
rect.y += 2;
_itemGUIDraw?.Invoke(rect,element,index);
if (_onEdit != null)
{
if (GUI.Button(new Rect(rect.x + rect.width - 60, rect.y, 60, EditorGUIUtility.singleLineHeight),
"Edit"))
ShowEditWindow(index);
}
}
private void AddListItem(ReorderableList list)
{
_itemList.Add(default);
_onAddListItem?.Invoke(list);
}
private void ShowEditWindow(int index)
{
_onEdit.Invoke(index,_itemList[index]);
}
public static CustomReorderableList<T> Create(
List<T> itemList ,
ReorderableList.HeaderCallbackDelegate drawListHeader,
Action<int,T> onEdit,
Action<Rect,T,int> itemGUIDraw,
ReorderableList.ElementHeightCallbackDelegate getElementHeight,
ReorderableList.AddCallbackDelegate onAddListItem)
{
return new CustomReorderableList<T>(itemList,drawListHeader,onEdit,itemGUIDraw,getElementHeight,onAddListItem);
}
public static CustomReorderableList<T> Create(
T[] itemList ,
ReorderableList.HeaderCallbackDelegate drawListHeader,
Action<int,T> onEdit,
Action<Rect,T,int> itemGUIDraw,
ReorderableList.ElementHeightCallbackDelegate getElementHeight,
ReorderableList.AddCallbackDelegate onAddListItem)
{
itemList = itemList ?? new T[0];
return new CustomReorderableList<T>(itemList,drawListHeader,onEdit,itemGUIDraw,getElementHeight,onAddListItem);
}
public void UpdateItem(int index, T item)
{
_itemList[index] = item;
}
public List<T> GetItemList()
{
return _itemList;
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cb84daf054af495ead8c39640ed5c89b
timeCreated: 1703748761

View File

@@ -0,0 +1,47 @@
#if UNITY_EDITOR
namespace GAS.Editor.General
{
using System;
using System.Collections.Generic;
public class DirectoryInfo
{
public string RootDirectory { get; }
public DirectoryInfo(string rootMenuName, string rootDirectory, string directory, Type assetType, bool isRoot)
{
RootDirectory = rootDirectory;
Directory = directory;
SubDirectory = new List<string>();
AssetType = assetType;
Root = isRoot;
var dirs = directory.Replace("\\", "/");
MenuName = dirs.Replace(RootDirectory + '/', "");
MenuName = $"{rootMenuName}/{MenuName}";
GetAllSubDir(Directory, SubDirectory);
}
public bool Root { get; }
public string MenuName { get; }
public Type AssetType { get; }
public string Directory { get; }
public List<string> SubDirectory { get; }
private void GetAllSubDir(string path, List<string> subDirs)
{
var dirs = System.IO.Directory.GetDirectories(path);
foreach (var dir in dirs)
{
subDirs.Add(dir);
GetAllSubDir(dir, subDirs);
}
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3305306f05c144e18bc1fb2aa2f5784c
timeCreated: 1704186389

View File

@@ -0,0 +1,90 @@
#if UNITY_EDITOR
namespace GAS.Editor
{
using System.Reflection;
using UnityEditor;
using UnityEngine;
public static class DockUtilities
{
public enum DockPosition
{
Left,
Top,
Right,
Bottom
}
private static Vector2 GetFakeMousePosition(EditorWindow wnd, DockPosition position)
{
Vector2 mousePosition = Vector2.zero;
// The 20 is required to make the docking work.
// Smaller values might not work when faking the mouse position.
switch (position)
{
case DockPosition.Left: mousePosition = new Vector2(20, wnd.position.size.y / 2); break;
case DockPosition.Top: mousePosition = new Vector2(wnd.position.size.x / 2, 20); break;
case DockPosition.Right: mousePosition = new Vector2(wnd.position.size.x - 20, wnd.position.size.y / 2); break;
case DockPosition.Bottom: mousePosition = new Vector2(wnd.position.size.x / 2, wnd.position.size.y - 20); break;
}
return new Vector2(wnd.position.x + mousePosition.x, wnd.position.y + mousePosition.y);
}
/// <summary>
/// Docks the "docked" window to the "anchor" window at the given position
/// </summary>
public static void DockWindow(this EditorWindow anchor, EditorWindow docked, DockPosition position)
{
var anchorParent = GetParentOf(anchor);
SetDragSource(anchorParent, GetParentOf(docked));
PerformDrop(GetWindowOf(anchorParent), docked, GetFakeMousePosition(anchor, position));
}
static object GetParentOf(object target)
{
var field = target.GetType().GetField("m_Parent", BindingFlags.Instance | BindingFlags.NonPublic);
return field.GetValue(target);
}
static object GetWindowOf(object target)
{
var property = target.GetType().GetProperty("window", BindingFlags.Instance | BindingFlags.Public);
return property.GetValue(target, null);
}
static void SetDragSource(object target, object source)
{
var field = target.GetType().GetField("s_OriginalDragSource", BindingFlags.Static | BindingFlags.NonPublic);
field.SetValue(null, source);
}
static void PerformDrop(object window, EditorWindow child, Vector2 screenPoint)
{
var rootSplitViewProperty = window.GetType().GetProperty("rootSplitView", BindingFlags.Instance | BindingFlags.Public);
object rootSplitView = rootSplitViewProperty.GetValue(window, null);
var dragMethod = rootSplitView.GetType().GetMethod("DragOver", BindingFlags.Instance | BindingFlags.Public);
var dropMethod = rootSplitView.GetType().GetMethod("PerformDrop", BindingFlags.Instance | BindingFlags.Public);
var dropInfo = dragMethod.Invoke(rootSplitView, new object[] { child, screenPoint });
dropMethod.Invoke(rootSplitView, new object[] { child, dropInfo, screenPoint });
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9059b568000240debb0aa94f5591af53
timeCreated: 1708244074

View File

@@ -0,0 +1,148 @@
#if UNITY_EDITOR
namespace GAS.Editor
{
using UnityEditor;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEngine;
public static class EditorUtil
{
public static bool IsValidClassName(string input)
{
// 使用正则表达式匹配规则
// 类名必须以字母、下划线或@开头,并且后续可以是字母、下划线、@或数字
var pattern = @"^[a-zA-Z_@][a-zA-Z_@0-9]*$";
// 使用 Regex.IsMatch 方法进行匹配
return Regex.IsMatch(input, pattern);
}
public static string MakeValidIdentifier(string name)
{
// Replace '.' with '_'
name = name.Replace('.', '_');
// If starts with a digit, add '_' at the beginning
if (char.IsDigit(name[0])) name = "_" + name;
// Ensure the identifier is valid
return string.Join("",
name.Split(
new[]
{
' ', '-', '.', ':', ',', '!', '?', '#', '$', '%', '^', '&', '*', '(', ')', '[', ']', '{', '}',
'/', '\\', '|'
}, StringSplitOptions.RemoveEmptyEntries));
}
/// <summary>
/// Keep the string list unique.
/// </summary>
/// <param name="inputList"></param>
/// <returns></returns>
public static List<string> RemoveDuplicates(List<string> inputList)
{
HashSet<string> seen = new HashSet<string>();
List<string> uniqueList = new List<string>();
foreach (string value in inputList)
{
if (!seen.Contains(value))
{
seen.Add(value);
uniqueList.Add(value);
}
}
return uniqueList;
}
public static List<Type> FindAllTypesInheritingFrom(Type baseType,Type ban = null)
{
List<Type> results = new List<Type>();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
var types = assembly.GetTypes();
foreach (var type in types)
{
if (type.IsSubclassOf(baseType) && !type.IsAbstract)
{
if(ban == null || !type.IsSubclassOf(ban))
results.Add(type);
}
}
}
catch (ReflectionTypeLoadException)
{
continue;
}
}
return results;
}
public static List<Type> GetScriptableObjectTypes(Type baseType)
{
List<Type> scriptableObjectTypes = new List<Type>();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
var types = assembly.GetTypes();
foreach (var type in types)
{
if (type.IsSubclassOf(baseType) && type.IsDefined(typeof(CreateAssetMenuAttribute), false))
{
scriptableObjectTypes.Add(type);
}
}
}
catch (ReflectionTypeLoadException)
{
continue;
}
}
return scriptableObjectTypes;
}
public static void Separator()
{
GUILayout.Space(5);
GUILayout.Box("------------------------------------------------------------------------------------------------------------");
GUILayout.Space(5);
}
public static List<T> FindAssetsByType<T>(string folderPath) where T : ScriptableObject
{
List<T> assets = new List<T>();
string[] guids = AssetDatabase.FindAssets("", new string[] { folderPath });
foreach (string guid in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
T asset = AssetDatabase.LoadAssetAtPath<T>(assetPath);
if (asset != null)
{
assets.Add(asset);
}
}
return assets;
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a5a7695f4ec649ddb1c3ff7e94ce087c
timeCreated: 1703128552

View File

@@ -0,0 +1,45 @@
#if UNITY_EDITOR
namespace GAS.Editor.General
{
using UnityEditor;
using UnityEngine;
public class GeneralAssetFloatInspector : EditorWindow
{
private Object _asset;
private UnityEditor.Editor _floatInspector;
public static void Open(Object asset)
{
GeneralAssetFloatInspector window = GetWindow<GeneralAssetFloatInspector>("Custom Editor");
window.Initialize(asset);
window.Show();
}
void Initialize(Object asset)
{
_asset = asset;
if (_asset != null)
{
// 创建一个 Editor 并指定要编辑的资源或对象
_floatInspector = UnityEditor.Editor.CreateEditor(_asset);
}
}
private void OnGUI()
{
if (_floatInspector != null)
{
_floatInspector.OnInspectorGUI();
}
}
private void OnDestroy()
{
if (_floatInspector != null)
{
DestroyImmediate(_floatInspector);
}
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 47d11f3dd7df4b57a21eee56329efde0
timeCreated: 1703823292

View File

@@ -0,0 +1,70 @@
using System;
using System.IO;
#if UNITY_EDITOR
namespace GAS.Editor
{
public sealed class IndentedWriter : IDisposable
{
private StreamWriter _streamWriter;
public IndentedWriter(StreamWriter streamWriter, int indent = 0, int spaces = 4)
{
_streamWriter = streamWriter;
Indent = indent;
Spaces = spaces;
}
public int Spaces { get; set; }
public int Indent { get; set; }
public string GetIndentation()
{
return new string(' ', Math.Max(0, Indent * Spaces));
}
public void Write(string text)
{
if (string.IsNullOrWhiteSpace(text))
_streamWriter.Write(text);
else
_streamWriter.Write(GetIndentation() + text);
}
public void WriteLine(string line = null, bool ignoreIndent = false)
{
if (string.IsNullOrWhiteSpace(line))
{
_streamWriter.WriteLine();
}
else
{
if (ignoreIndent)
{
_streamWriter.WriteLine(line);
}
else
{
_streamWriter.WriteLine(GetIndentation() + line);
}
}
}
private bool _isDisposed;
public void Dispose()
{
if (!_isDisposed)
{
_isDisposed = true;
_streamWriter.Dispose();
_streamWriter = null;
}
GC.SuppressFinalize(this);
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 716f228d275f4854b4f4333ea421456c
timeCreated: 1711437755

View File

@@ -0,0 +1,12 @@
#if UNITY_EDITOR
namespace GAS.Editor
{
using Sirenix.OdinInspector.Editor;
public class OdinEditorWithoutHeader:OdinEditor
{
protected override void OnHeaderGUI()
{
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 86e334186c1d4a06a26ead33d3280da1
timeCreated: 1710399644

View File

@@ -0,0 +1,95 @@
#if UNITY_EDITOR
namespace GAS.Editor
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using UnityEditor;
using UnityEngine;
public static class ScriptableObjectCreator
{
public static void ShowDialog<T>(string defaultDestinationPath, Action<T> onScritpableObjectCreated = null)
where T : ScriptableObject
{
var selector = new ScriptableObjectSelector<T>(defaultDestinationPath, onScritpableObjectCreated);
if (selector.SelectionTree.EnumerateTree().Count() == 1)
{
// If there is only one scriptable object to choose from in the selector, then
// we'll automatically select it and confirm the selection.
selector.SelectionTree.EnumerateTree().First().Select();
selector.SelectionTree.Selection.ConfirmSelection();
}
else
{
// Else, we'll open up the selector in a popup and let the user choose.
selector.ShowInPopup(300);
}
}
// Here is the actual ScriptableObjectSelector which inherits from OdinSelector.
// You can learn more about those in the documentation: http://sirenix.net/odininspector/documentation/sirenix/odininspector/editor/odinselector(t)
// This one builds a menu-tree of all types that inherit from T, and when the selection is confirmed, it then prompts the user
// with a dialog to save the newly created scriptable object.
private class ScriptableObjectSelector<T> : OdinSelector<Type> where T : ScriptableObject
{
private Action<T> onScritpableObjectCreated;
private string defaultDestinationPath;
public ScriptableObjectSelector(string defaultDestinationPath, Action<T> onScritpableObjectCreated = null)
{
this.onScritpableObjectCreated = onScritpableObjectCreated;
this.defaultDestinationPath = defaultDestinationPath;
this.SelectionConfirmed += this.ShowSaveFileDialog;
}
protected override void BuildSelectionTree(OdinMenuTree tree)
{
var scriptableObjectTypes = AssemblyUtilities.GetTypes(AssemblyCategory.ProjectSpecific)
.Where(x => x.IsClass && !x.IsAbstract && x.InheritsFrom(typeof(T)));
tree.Selection.SupportsMultiSelect = false;
tree.Config.DrawSearchToolbar = true;
tree.Config.SelectMenuItemsOnMouseDown = true;
tree.AddRange(scriptableObjectTypes, x => x.GetNiceName())
.AddThumbnailIcons();
}
private void ShowSaveFileDialog(IEnumerable<Type> selection)
{
var obj = ScriptableObject.CreateInstance(selection.FirstOrDefault()) as T;
string dest = this.defaultDestinationPath.TrimEnd('/');
if (!Directory.Exists(dest))
{
Directory.CreateDirectory(dest);
AssetDatabase.Refresh();
}
dest = EditorUtility.SaveFilePanel("Save object as", dest, "New " + typeof(T).Name, "asset");
if (!string.IsNullOrEmpty(dest) && PathUtilities.TryMakeRelative(Path.GetDirectoryName(Application.dataPath), dest, out dest))
{
AssetDatabase.CreateAsset(obj, dest);
AssetDatabase.Refresh();
if (this.onScritpableObjectCreated != null)
{
this.onScritpableObjectCreated(obj);
}
}
else
{
UnityEngine.Object.DestroyImmediate(obj);
}
}
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 73dc71a16c204bdc9e28adaefc1ab27f
timeCreated: 1704189775

View File

@@ -0,0 +1,62 @@
#if UNITY_EDITOR
namespace GAS.Editor.General
{
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class ScriptableObjectReorderableList<T> where T : ScriptableObject
{
private CustomReorderableList<T> _reorderableList;
private string _title;
public ScriptableObjectReorderableList(T[] initData,string title)
{
_title = title;
initData = initData ?? new T[0];
_reorderableList =
CustomReorderableList<T>.Create(
initData ,
DrawListHeader,
OnEdit,
ItemGUIDraw,
null,
null);
}
void OnEdit(int index, T item)
{
if (item == null)
{
EditorUtility.DisplayDialog("Warning", $"{typeof(T).Name} is null", "OK");
return;
}
GeneralAssetFloatInspector.Open(item);
}
void DrawListHeader (Rect rect)
{
EditorGUI.LabelField(rect, _title);
}
void ItemGUIDraw(Rect rect, T item, int index)
{
item = EditorGUI.ObjectField(new Rect(rect.x, rect.y,
200, EditorGUIUtility.singleLineHeight), item, typeof(T),
false) as T;
_reorderableList.UpdateItem(index, item);
}
public void OnGUI()
{
_reorderableList.OnGUI();
}
public List<T> GetItemList()
{
return _reorderableList.GetItemList();
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 75b207c326b54f1d979a1163b04fc13c
timeCreated: 1703837998

View File

@@ -0,0 +1,52 @@
using System.Linq;
using GAS.General.Validation;
using UnityEditor;
using UnityEngine;
namespace GAS.Editor
{
public static class ScriptingDefineSymbolsHelper
{
public static void Add(string define)
{
if (!Validations.IsValidVariableName(define))
{
Debug.LogError($@"Add scripting define symbol error: ""{define}"" is not a valid variable name!");
return;
}
var group = EditorUserBuildSettings.selectedBuildTargetGroup;
var definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
var defines = definesString.Split(';');
if (defines.Contains(define))
{
Debug.Log($@"Add scripting define symbol failed: ""{define}"" already exists!");
return;
}
var newDefinesString = string.Join(";", defines.Append(define));
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, newDefinesString);
}
public static void Remove(string define)
{
if (string.IsNullOrWhiteSpace(define))
{
Debug.LogError($@"Remove scripting define symbol error: ""{define}"" is null or empty!");
return;
}
var group = EditorUserBuildSettings.selectedBuildTargetGroup;
var definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
var defines = definesString.Split(';');
if (!defines.Contains(define))
{
Debug.Log($@"Remove scripting define symbol failed: ""{define}"" does not exist!");
return;
}
var newDefinesString = string.Join(";", defines.Where(d => d != define));
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, newDefinesString);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b9055976068442439bd3318d8b42a345
timeCreated: 1718347417

View File

@@ -0,0 +1,93 @@
using System;
using GAS.General.Validation;
using UnityEditor;
using UnityEngine;
namespace GAS.Editor.General
{
public class StringEditWindow : EditorWindow
{
private string _tip;
private string _initialString;
private string _editedString = "";
private ValidationDelegate _validator;
private Action<string> _callback;
private bool focusInputField = false;
public static void OpenWindow(string tip, string initialString, ValidationDelegate validator,
Action<string> callback, string title = "Input")
{
var window = GetWindow<StringEditWindow>();
window.Init(tip, initialString, validator, callback);
window.titleContent = new GUIContent(title);
window.ShowModalUtility();
}
public static void OpenWindow(string tip, string initialString, Action<string> callback,
string title = "Input")
{
OpenWindow(tip, initialString, null, callback, title);
}
private void OnGUI()
{
const string ctrlName = "InputField";
if (!focusInputField)
{
GUI.SetNextControlName(ctrlName);
}
_editedString = EditorGUILayout.TextField($"{_tip}:", _editedString);
// Focus the input field
if (!focusInputField)
{
focusInputField = true;
EditorGUI.FocusTextInControl(ctrlName);
}
EditorGUILayout.Space();
if (GUILayout.Button("Save"))
{
Save();
}
}
private void Save()
{
if (!string.IsNullOrWhiteSpace(_initialString) && (_initialString != _editedString))
{
if (!EditorUtility.DisplayDialog("Warning",
$"Are you sure to save the changes?\n\n {_initialString} => {_editedString}", "Yes", "No"))
{
return;
}
}
if (_validator != null)
{
var validationResult = _validator.Invoke(_editedString);
if (!validationResult.IsValid)
{
EditorUtility.DisplayDialog("Validation Failed", validationResult.Message, "OK");
return;
}
}
_callback?.Invoke(_editedString);
Close();
}
private void Init(string tip, string initialString, ValidationDelegate validator,
Action<string> callback)
{
_tip = tip;
_initialString = initialString;
_editedString = initialString;
_validator = validator;
_callback = callback;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a90c56b3129253245bd5989d0024df3d
timeCreated: 1702285611

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b84e44e848a2cc54a9f86e88d1219512
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,65 @@
#if UNITY_EDITOR
namespace UnityEditor.TreeDataModel
{
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class ExTreeElement
{
[SerializeField] private int _id;
[SerializeField] private string _name;
[SerializeField] private int _depth;
[NonSerialized] private List<ExTreeElement> _children;
[NonSerialized] private ExTreeElement _parent;
public ExTreeElement()
{
}
public ExTreeElement(string name, int depth, int id)
{
_name = name;
_id = id;
_depth = depth;
}
public int Depth
{
get => _depth;
set => _depth = value;
}
public ExTreeElement Parent
{
get => _parent;
set => _parent = value;
}
public List<ExTreeElement> Children
{
get => _children;
set => _children = value;
}
public bool HasChildren => Children != null && Children.Count > 0;
public string Name
{
get => _name;
set => _name = value;
}
public int ID
{
get => _id;
set => _id = value;
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 69be32fe4d27dde489209c5885c1e5dc
timeCreated: 1472024155
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,171 @@
#if UNITY_EDITOR
namespace UnityEditor.TreeDataModel
{
using System;
using System.Collections.Generic;
// TreeElementUtility and TreeElement are useful helper classes for backend tree data structures.
// See tests at the bottom for examples of how to use.
public static class ExTreeElementUtility
{
public static void TreeToList<T>(T root, IList<T> result) where T : ExTreeElement
{
if (result == null)
throw new NullReferenceException("The input 'IList<T> result' list is null");
result.Clear();
Stack<T> stack = new Stack<T>();
stack.Push(root);
while (stack.Count > 0)
{
T current = stack.Pop();
result.Add(current);
if (current.Children != null && current.Children.Count > 0)
{
for (int i = current.Children.Count - 1; i >= 0; i--)
{
stack.Push((T)current.Children[i]);
}
}
}
}
// Returns the root of the tree parsed from the list (always the first element).
// Important: the first item and is required to have a depth value of -1.
// The rest of the items should have depth >= 0.
public static T ListToTree<T>(IList<T> list) where T : ExTreeElement
{
// Validate input
ValidateDepthValues (list);
// Clear old states
foreach (var element in list)
{
element.Parent = null;
element.Children = null;
}
// Set child and parent references using depth info
for (int parentIndex = 0; parentIndex < list.Count; parentIndex++)
{
var parent = list[parentIndex];
bool alreadyHasValidChildren = parent.Children != null;
if (alreadyHasValidChildren)
continue;
int parentDepth = parent.Depth;
int childCount = 0;
// Count children based depth value, we are looking at children until it's the same depth as this object
for (int i = parentIndex + 1; i < list.Count; i++)
{
if (list[i].Depth == parentDepth + 1)
childCount++;
if (list[i].Depth <= parentDepth)
break;
}
// Fill child array
List<ExTreeElement> childList = null;
if (childCount != 0)
{
childList = new List<ExTreeElement>(childCount); // Allocate once
childCount = 0;
for (int i = parentIndex + 1; i < list.Count; i++)
{
if (list[i].Depth == parentDepth + 1)
{
list[i].Parent = parent;
childList.Add(list[i]);
childCount++;
}
if (list[i].Depth <= parentDepth)
break;
}
}
parent.Children = childList;
}
return list[0];
}
// Check state of input list
public static void ValidateDepthValues<T>(IList<T> list) where T : ExTreeElement
{
if (list.Count == 0)
throw new ArgumentException("list should have items, count is 0, check before calling ValidateDepthValues", "list");
if (list[0].Depth != -1)
throw new ArgumentException("list item at index 0 should have a depth of -1 (since this should be the hidden root of the tree). Depth is: " + list[0].Depth, "list");
for (int i = 0; i < list.Count - 1; i++)
{
int depth = list[i].Depth;
int nextDepth = list[i + 1].Depth;
if (nextDepth > depth && nextDepth - depth > 1)
throw new ArgumentException(string.Format("Invalid depth info in input list. Depth cannot increase more than 1 per row. Index {0} has depth {1} while index {2} has depth {3}", i, depth, i + 1, nextDepth));
}
for (int i = 1; i < list.Count; ++i)
if (list[i].Depth < 0)
throw new ArgumentException("Invalid depth value for item at index " + i + ". Only the first item (the root) should have depth below 0.");
if (list.Count > 1 && list[1].Depth != 0)
throw new ArgumentException("Input list item at index 1 is assumed to have a depth of 0", "list");
}
// For updating depth values below any given element e.g after reparenting elements
public static void UpdateDepthValues<T>(T root) where T : ExTreeElement
{
if (root == null)
throw new ArgumentNullException("root", "The root is null");
if (!root.HasChildren)
return;
Stack<ExTreeElement> stack = new Stack<ExTreeElement>();
stack.Push(root);
while (stack.Count > 0)
{
ExTreeElement current = stack.Pop();
if (current.Children != null)
{
foreach (var child in current.Children)
{
child.Depth = current.Depth + 1;
stack.Push(child);
}
}
}
}
// Returns true if there is an ancestor of child in the elements list
static bool IsChildOf<T>(T child, IList<T> elements) where T : ExTreeElement
{
while (child != null)
{
child = (T)child.Parent;
if (elements.Contains(child))
return true;
}
return false;
}
public static IList<T> FindCommonAncestorsWithinList<T>(IList<T> elements) where T : ExTreeElement
{
if (elements.Count == 1)
return new List<T>(elements);
List<T> result = new List<T>(elements);
result.RemoveAll(g => IsChildOf(g, elements));
return result;
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fd65e8f324e17a344a97ddcf5a8d89d2
timeCreated: 1471616285
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,238 @@
#if UNITY_EDITOR
namespace UnityEditor.TreeDataModel
{
using System;
using System.Collections.Generic;
using System.Linq;
// The TreeModel is a utility class working on a list of serializable TreeElements where the order and the depth of each TreeElement define
// the tree structure. Note that the TreeModel itself is not serializable (in Unity we are currently limited to serializing lists/arrays) but the
// input list is.
// The tree representation (parent and children references) are then build internally using TreeElementUtility.ListToTree (using depth
// values of the elements).
// The first element of the input list is required to have depth == -1 (the hiddenroot) and the rest to have
// depth >= 0 (otherwise an exception will be thrown)
public class ExTreeModel<T> where T : ExTreeElement
{
IList<T> m_Data;
int m_MaxID;
public T Root { get; private set; }
public event Action modelChanged;
public int NumberOfDataElements => m_Data.Count;
public ExTreeModel (IList<T> data)
{
SetData (data);
}
public T Find (int id)
{
return m_Data.FirstOrDefault (element => element.ID == id);
}
public void SetData (IList<T> data)
{
Init (data);
}
void Init (IList<T> data)
{
m_Data = data ?? throw new ArgumentNullException("data", "Input data is null. Ensure input is a non-null list.");
if (m_Data.Count > 0)
{
Root = ExTreeElementUtility.ListToTree(data);
}
else
{
T root = Activator.CreateInstance(typeof(T), "Root", -1, 0) as T;
AddRoot(root);
Root = root;
}
m_MaxID = m_Data.Max(e => e.ID);
}
public int GenerateUniqueID ()
{
return ++m_MaxID;
}
public IList<int> GetAncestors (int id)
{
var parents = new List<int>();
ExTreeElement T = Find(id);
if (T != null)
{
while (T.Parent != null)
{
parents.Add(T.Parent.ID);
T = T.Parent;
}
}
return parents;
}
public IList<int> GetDescendantsThatHaveChildren (int id)
{
T searchFromThis = Find(id);
if (searchFromThis != null)
{
return GetParentsBelowStackBased(searchFromThis);
}
return new List<int>();
}
IList<int> GetParentsBelowStackBased(ExTreeElement searchFromThis)
{
Stack<ExTreeElement> stack = new Stack<ExTreeElement>();
stack.Push(searchFromThis);
var parentsBelow = new List<int>();
while (stack.Count > 0)
{
ExTreeElement current = stack.Pop();
if (current.HasChildren)
{
parentsBelow.Add(current.ID);
foreach (var T in current.Children)
{
stack.Push(T);
}
}
}
return parentsBelow;
}
public void RemoveElements (IList<int> elementIDs)
{
IList<T> elements = m_Data.Where (element => elementIDs.Contains (element.ID)).ToArray ();
RemoveElements (elements);
}
public void RemoveElements (IList<T> elements)
{
foreach (var element in elements)
if (element == Root)
throw new ArgumentException("It is not allowed to remove the root element");
var commonAncestors = ExTreeElementUtility.FindCommonAncestorsWithinList (elements);
foreach (var element in commonAncestors)
{
element.Parent.Children.Remove (element);
element.Parent = null;
}
ExTreeElementUtility.TreeToList(Root, m_Data);
Changed();
}
public void AddElements (IList<T> elements, ExTreeElement parent, int insertPosition)
{
if (elements == null)
throw new ArgumentNullException("elements", "elements is null");
if (elements.Count == 0)
throw new ArgumentNullException("elements", "elements Count is 0: nothing to add");
if (parent == null)
throw new ArgumentNullException("parent", "parent is null");
if (parent.Children == null)
parent.Children = new List<ExTreeElement>();
parent.Children.InsertRange(insertPosition, elements.Cast<ExTreeElement> ());
foreach (var element in elements)
{
element.Parent = parent;
element.Depth = parent.Depth + 1;
ExTreeElementUtility.UpdateDepthValues(element);
}
ExTreeElementUtility.TreeToList(Root, m_Data);
Changed();
}
public void AddRoot (T root)
{
if (root == null)
throw new ArgumentNullException("root", "root is null");
if (m_Data == null)
throw new InvalidOperationException("Internal Error: data list is null");
if (m_Data.Count != 0)
throw new InvalidOperationException("AddRoot is only allowed on empty data list");
root.Name = "Root";
root.ID = GenerateUniqueID ();
root.Depth = -1;
m_Data.Add (root);
}
public void AddElement (T element, ExTreeElement parent, int insertPosition)
{
if (element == null)
throw new ArgumentNullException("element", "element is null");
if (parent == null)
throw new ArgumentNullException("parent", "parent is null");
if (parent.Children == null)
parent.Children = new List<ExTreeElement> ();
parent.Children.Insert (insertPosition, element);
element.Parent = parent;
ExTreeElementUtility.UpdateDepthValues(parent);
ExTreeElementUtility.TreeToList(Root, m_Data);
Changed ();
}
public void MoveElements(ExTreeElement parentElement, int insertionIndex, List<ExTreeElement> elements)
{
if (insertionIndex < 0)
throw new ArgumentException("Invalid input: insertionIndex is -1, client needs to decide what index elements should be reparented at");
// Invalid reparenting input
if (parentElement == null)
return;
// We are moving items so we adjust the insertion index to accomodate that any items above the insertion index is removed before inserting
if (insertionIndex > 0)
insertionIndex -= parentElement.Children.GetRange(0, insertionIndex).Count(elements.Contains);
// Remove draggedItems from their parents
foreach (var draggedItem in elements)
{
draggedItem.Parent.Children.Remove(draggedItem); // remove from old parent
draggedItem.Parent = parentElement; // set new parent
}
if (parentElement.Children == null)
parentElement.Children = new List<ExTreeElement>();
// Insert dragged items under new parent
parentElement.Children.InsertRange(insertionIndex, elements);
ExTreeElementUtility.UpdateDepthValues (Root);
ExTreeElementUtility.TreeToList (Root, m_Data);
Changed ();
}
void Changed ()
{
if (modelChanged != null)
modelChanged ();
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6f9fab1cf2636a6439c644bf08108abb
timeCreated: 1472122507
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,232 @@
#if UNITY_EDITOR
namespace UnityEditor.TreeDataModel
{
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using Object = UnityEngine.Object;
internal class TreeViewItem<T> : TreeViewItem where T : ExTreeElement
{
public TreeViewItem(int id, int depth, string displayName, T data) : base(id, depth, displayName)
{
this.data = data;
}
public T data { get; set; }
}
internal class TreeViewWithTreeModel<T> : TreeView where T : ExTreeElement
{
// Dragging
//-----------
private const string k_GenericDragID = "GenericDragColumnDragging";
private readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
public TreeViewWithTreeModel(TreeViewState state, ExTreeModel<T> model) : base(state)
{
Init(model);
}
public TreeViewWithTreeModel(TreeViewState state, MultiColumnHeader multiColumnHeader, ExTreeModel<T> model)
: base(state, multiColumnHeader)
{
Init(model);
}
public ExTreeModel<T> ExTreeModel { get; private set; }
public event Action treeChanged;
public event Action<IList<TreeViewItem>> beforeDroppingDraggedItems;
private void Init(ExTreeModel<T> model)
{
ExTreeModel = model;
ExTreeModel.modelChanged += ModelChanged;
}
private void ModelChanged()
{
if (treeChanged != null)
treeChanged();
Reload();
}
protected override TreeViewItem BuildRoot()
{
var depthForHiddenRoot = -1;
return new TreeViewItem<T>(ExTreeModel.Root.ID, depthForHiddenRoot, ExTreeModel.Root.Name, ExTreeModel.Root);
}
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
{
if (ExTreeModel.Root == null) Debug.LogError("tree model root is null. did you call SetData()?");
m_Rows.Clear();
if (!string.IsNullOrEmpty(searchString))
{
Search(ExTreeModel.Root, searchString, m_Rows);
}
else
{
if (ExTreeModel.Root.HasChildren)
AddChildrenRecursive(ExTreeModel.Root, 0, m_Rows);
}
// We still need to setup the child parent information for the rows since this
// information is used by the TreeView internal logic (navigation, dragging etc)
SetupParentsAndChildrenFromDepths(root, m_Rows);
return m_Rows;
}
private void AddChildrenRecursive(T parent, int depth, IList<TreeViewItem> newRows)
{
foreach (T child in parent.Children)
{
var item = new TreeViewItem<T>(child.ID, depth, child.Name, child);
newRows.Add(item);
if (child.HasChildren)
{
if (IsExpanded(child.ID))
AddChildrenRecursive(child, depth + 1, newRows);
else
item.children = CreateChildListForCollapsedParent();
}
}
}
private void Search(T searchFromThis, string search, List<TreeViewItem> result)
{
if (string.IsNullOrEmpty(search))
throw new ArgumentException("Invalid search: cannot be null or empty", "search");
const int kItemDepth = 0; // tree is flattened when searching
var stack = new Stack<T>();
foreach (var element in searchFromThis.Children)
stack.Push((T)element);
while (stack.Count > 0)
{
var current = stack.Pop();
// Matches search?
if (current.Name.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0)
result.Add(new TreeViewItem<T>(current.ID, kItemDepth, current.Name, current));
if (current.Children != null && current.Children.Count > 0)
foreach (var element in current.Children)
stack.Push((T)element);
}
SortSearchResult(result);
}
protected virtual void SortSearchResult(List<TreeViewItem> rows)
{
rows.Sort((x, y) =>
EditorUtility.NaturalCompare(x.displayName,
y.displayName)); // sort by displayName by default, can be overriden for multicolumn solutions
}
protected override IList<int> GetAncestors(int id)
{
return ExTreeModel.GetAncestors(id);
}
protected override IList<int> GetDescendantsThatHaveChildren(int id)
{
return ExTreeModel.GetDescendantsThatHaveChildren(id);
}
protected override bool CanStartDrag(CanStartDragArgs args)
{
return true;
}
protected override void SetupDragAndDrop(SetupDragAndDropArgs args)
{
if (hasSearch)
return;
DragAndDrop.PrepareStartDrag();
var draggedRows = GetRows().Where(item => args.draggedItemIDs.Contains(item.id)).ToList();
DragAndDrop.SetGenericData(k_GenericDragID, draggedRows);
DragAndDrop.objectReferences = new Object[] { }; // this IS required for dragging to work
var title = draggedRows.Count == 1 ? draggedRows[0].displayName : "< Multiple >";
DragAndDrop.StartDrag(title);
}
protected override DragAndDropVisualMode HandleDragAndDrop(DragAndDropArgs args)
{
// Check if we can handle the current drag data (could be dragged in from other areas/windows in the editor)
var draggedRows = DragAndDrop.GetGenericData(k_GenericDragID) as List<TreeViewItem>;
if (draggedRows == null)
return DragAndDropVisualMode.None;
// Parent item is null when dragging outside any tree view items.
switch (args.dragAndDropPosition)
{
case DragAndDropPosition.UponItem:
case DragAndDropPosition.BetweenItems:
{
var validDrag = ValidDrag(args.parentItem, draggedRows);
if (args.performDrop && validDrag)
{
var parentData = ((TreeViewItem<T>)args.parentItem).data;
OnDropDraggedElementsAtIndex(draggedRows, parentData,
args.insertAtIndex == -1 ? 0 : args.insertAtIndex);
}
return validDrag ? DragAndDropVisualMode.Move : DragAndDropVisualMode.None;
}
case DragAndDropPosition.OutsideItems:
{
if (args.performDrop)
OnDropDraggedElementsAtIndex(draggedRows, ExTreeModel.Root, ExTreeModel.Root.Children.Count);
return DragAndDropVisualMode.Move;
}
default:
Debug.LogError("Unhandled enum " + args.dragAndDropPosition);
return DragAndDropVisualMode.None;
}
}
public virtual void OnDropDraggedElementsAtIndex(List<TreeViewItem> draggedRows, T parent, int insertIndex)
{
if (beforeDroppingDraggedItems != null)
beforeDroppingDraggedItems(draggedRows);
var draggedElements = new List<ExTreeElement>();
foreach (var x in draggedRows)
draggedElements.Add(((TreeViewItem<T>)x).data);
var selectedIDs = draggedElements.Select(x => x.ID).ToArray();
ExTreeModel.MoveElements(parent, insertIndex, draggedElements);
SetSelection(selectedIDs, TreeViewSelectionOptions.RevealAndFrame);
}
private bool ValidDrag(TreeViewItem parent, List<TreeViewItem> draggedItems)
{
var currentParent = parent;
while (currentParent != null)
{
if (draggedItems.Contains(currentParent))
return false;
currentParent = currentParent.parent;
}
return true;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2e2a183e6c386ab4686c62e7d53acafb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 37dd96f4b0304fff9b928b5a1e787174
timeCreated: 1708583340

View File

@@ -0,0 +1,13 @@
#if UNITY_EDITOR
namespace GAS.Editor.General.VisualElement
{
using UnityEngine.UIElements;
public class SplitView : TwoPaneSplitView
{
public new class UxmlFactory : UxmlFactory<SplitView, UxmlTraits>
{
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cb9caff7a0fc468dac3f71760bd49d28
timeCreated: 1708583352