mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-09-27 02:36:14 +00:00
提交GAS 打算做一个帧同步的GAS
This commit is contained in:
105
JEX_GAS/Assets/GAS/Editor/General/ArraySetFromChoicesAsset.cs
Normal file
105
JEX_GAS/Assets/GAS/Editor/General/ArraySetFromChoicesAsset.cs
Normal 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
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2840d9545404cf1b2ef3898d67c3ab4
|
||||
timeCreated: 1703835297
|
143
JEX_GAS/Assets/GAS/Editor/General/CustomReorderableList.cs
Normal file
143
JEX_GAS/Assets/GAS/Editor/General/CustomReorderableList.cs
Normal 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
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb84daf054af495ead8c39640ed5c89b
|
||||
timeCreated: 1703748761
|
47
JEX_GAS/Assets/GAS/Editor/General/DirectoryInfo.cs
Normal file
47
JEX_GAS/Assets/GAS/Editor/General/DirectoryInfo.cs
Normal 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
|
3
JEX_GAS/Assets/GAS/Editor/General/DirectoryInfo.cs.meta
Normal file
3
JEX_GAS/Assets/GAS/Editor/General/DirectoryInfo.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3305306f05c144e18bc1fb2aa2f5784c
|
||||
timeCreated: 1704186389
|
90
JEX_GAS/Assets/GAS/Editor/General/DockUtilities.cs
Normal file
90
JEX_GAS/Assets/GAS/Editor/General/DockUtilities.cs
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
3
JEX_GAS/Assets/GAS/Editor/General/DockUtilities.cs.meta
Normal file
3
JEX_GAS/Assets/GAS/Editor/General/DockUtilities.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9059b568000240debb0aa94f5591af53
|
||||
timeCreated: 1708244074
|
148
JEX_GAS/Assets/GAS/Editor/General/EditorUtil.cs
Normal file
148
JEX_GAS/Assets/GAS/Editor/General/EditorUtil.cs
Normal 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
|
3
JEX_GAS/Assets/GAS/Editor/General/EditorUtil.cs.meta
Normal file
3
JEX_GAS/Assets/GAS/Editor/General/EditorUtil.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5a7695f4ec649ddb1c3ff7e94ce087c
|
||||
timeCreated: 1703128552
|
@@ -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
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47d11f3dd7df4b57a21eee56329efde0
|
||||
timeCreated: 1703823292
|
70
JEX_GAS/Assets/GAS/Editor/General/IndentedWriter.cs
Normal file
70
JEX_GAS/Assets/GAS/Editor/General/IndentedWriter.cs
Normal 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
|
3
JEX_GAS/Assets/GAS/Editor/General/IndentedWriter.cs.meta
Normal file
3
JEX_GAS/Assets/GAS/Editor/General/IndentedWriter.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 716f228d275f4854b4f4333ea421456c
|
||||
timeCreated: 1711437755
|
12
JEX_GAS/Assets/GAS/Editor/General/OdinEditorWithoutHeader.cs
Normal file
12
JEX_GAS/Assets/GAS/Editor/General/OdinEditorWithoutHeader.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
#if UNITY_EDITOR
|
||||
namespace GAS.Editor
|
||||
{
|
||||
using Sirenix.OdinInspector.Editor;
|
||||
public class OdinEditorWithoutHeader:OdinEditor
|
||||
{
|
||||
protected override void OnHeaderGUI()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86e334186c1d4a06a26ead33d3280da1
|
||||
timeCreated: 1710399644
|
95
JEX_GAS/Assets/GAS/Editor/General/ScriptableObjectCreator.cs
Normal file
95
JEX_GAS/Assets/GAS/Editor/General/ScriptableObjectCreator.cs
Normal 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
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73dc71a16c204bdc9e28adaefc1ab27f
|
||||
timeCreated: 1704189775
|
@@ -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
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75b207c326b54f1d979a1163b04fc13c
|
||||
timeCreated: 1703837998
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9055976068442439bd3318d8b42a345
|
||||
timeCreated: 1718347417
|
93
JEX_GAS/Assets/GAS/Editor/General/StringEditWindow.cs
Normal file
93
JEX_GAS/Assets/GAS/Editor/General/StringEditWindow.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a90c56b3129253245bd5989d0024df3d
|
||||
timeCreated: 1702285611
|
8
JEX_GAS/Assets/GAS/Editor/General/TreeDataModel.meta
Normal file
8
JEX_GAS/Assets/GAS/Editor/General/TreeDataModel.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b84e44e848a2cc54a9f86e88d1219512
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69be32fe4d27dde489209c5885c1e5dc
|
||||
timeCreated: 1472024155
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd65e8f324e17a344a97ddcf5a8d89d2
|
||||
timeCreated: 1471616285
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
238
JEX_GAS/Assets/GAS/Editor/General/TreeDataModel/ExTreeModel.cs
Normal file
238
JEX_GAS/Assets/GAS/Editor/General/TreeDataModel/ExTreeModel.cs
Normal 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
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f9fab1cf2636a6439c644bf08108abb
|
||||
timeCreated: 1472122507
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e2a183e6c386ab4686c62e7d53acafb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
3
JEX_GAS/Assets/GAS/Editor/General/VisualElement.meta
Normal file
3
JEX_GAS/Assets/GAS/Editor/General/VisualElement.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37dd96f4b0304fff9b928b5a1e787174
|
||||
timeCreated: 1708583340
|
13
JEX_GAS/Assets/GAS/Editor/General/VisualElement/SplitView.cs
Normal file
13
JEX_GAS/Assets/GAS/Editor/General/VisualElement/SplitView.cs
Normal 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
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb9caff7a0fc468dac3f71760bd49d28
|
||||
timeCreated: 1708583352
|
Reference in New Issue
Block a user