CORE dashboard + a lot of changes
This commit is contained in:
@@ -0,0 +1,364 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using RPGCore.Core;
|
||||
using RPGCore.Core.Objects;
|
||||
using RPGCoreCommon.Helpers;
|
||||
using RPGCoreCommon.Helpers.CustomTypes;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCore.Editor.CoreManager
|
||||
{
|
||||
internal class DashboardWindow : EditorWindow
|
||||
{
|
||||
[SerializeField] private VisualTreeAsset _visualTreeAsset;
|
||||
|
||||
// CONST
|
||||
private const string IsFirstTimeOpenKey = "TheVVaS.RPGCore.isFirstTimeOpen";
|
||||
private const string PipeStart = "├─";
|
||||
private const string PipeGo = "│\u2007\u2007";
|
||||
private const string PipeEnd = "└─";
|
||||
private const string PipeNone = "\u2007\u2007\u2007";
|
||||
|
||||
// CACHE - check CreateCache()
|
||||
private static Type[] _moduleTypes;
|
||||
private static Type[] _objectTypes;
|
||||
private static Dictionary<Type, List<Type>> _objectTypesMap;
|
||||
private static Dictionary<Type, MonoScript> _moduleMonoScripts;
|
||||
|
||||
// TAB - VERIFY
|
||||
private Button _verifyCheckButton;
|
||||
private Button _verifyFixButton;
|
||||
private Button _verifyClearButton;
|
||||
private MultiColumnListView _verifyListMultiColumnListView;
|
||||
private List<Validation> _validations;
|
||||
|
||||
// TAB - MODULES
|
||||
private ToolbarSearchField _modulesFilterSearchFiled;
|
||||
private ScrollView _modulesListScrollView;
|
||||
|
||||
// TAB - CONFIG
|
||||
private PropertyField _configVerifyPathsProperty;
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void OnEditorLoad()
|
||||
{
|
||||
var isFirstTimeOpen = EditorPrefs.GetBool(IsFirstTimeOpenKey, true);
|
||||
|
||||
if (!isFirstTimeOpen) return;
|
||||
|
||||
ShowDashboard();
|
||||
EditorPrefs.SetBool(IsFirstTimeOpenKey, false);
|
||||
}
|
||||
|
||||
[MenuItem("TheVVaS/RPGCore/Dashboard", priority = -1)]
|
||||
public static void ShowDashboard()
|
||||
{
|
||||
var window = GetWindow<DashboardWindow>();
|
||||
window.titleContent = new GUIContent("RPGCore Dashboard");
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void CreateGUI()
|
||||
{
|
||||
CreateCache();
|
||||
UpdateConfigModules();
|
||||
|
||||
_visualTreeAsset.CloneTree(rootVisualElement);
|
||||
_verifyCheckButton = rootVisualElement.Q<Button>("db-verify-check");
|
||||
_verifyCheckButton.clicked += OnVerifyCheck;
|
||||
_verifyFixButton = rootVisualElement.Q<Button>("db-verify-fix");
|
||||
_verifyFixButton.clicked += OnVerifyFix;
|
||||
_verifyFixButton.SetEnabled(false);
|
||||
_verifyClearButton = rootVisualElement.Q<Button>("db-verify-clear");
|
||||
_verifyClearButton.clicked += OnVerifyClear;
|
||||
_verifyListMultiColumnListView = rootVisualElement.Q<MultiColumnListView>("db-verify-list");
|
||||
CreateVerifyList();
|
||||
|
||||
_modulesFilterSearchFiled = rootVisualElement.Q<ToolbarSearchField>("db-modules-filter");
|
||||
_modulesFilterSearchFiled.RegisterValueChangedCallback(ev => OnModuleFilter(ev.newValue));
|
||||
_modulesListScrollView = rootVisualElement.Q<ScrollView>("db-modules-list");
|
||||
CreateModulesList();
|
||||
|
||||
_configVerifyPathsProperty = rootVisualElement.Q<PropertyField>("db-config-verify-paths");
|
||||
_configVerifyPathsProperty.BindProperty(
|
||||
new SerializedObject(EditorConfigSO.instance).FindProperty(nameof(EditorConfigSO.verifyPaths)));
|
||||
}
|
||||
|
||||
private void CreateCache()
|
||||
{
|
||||
_moduleTypes = TypeCache.GetTypesDerivedFrom<ObjectModule>()
|
||||
.OrderBy(t => t.Assembly.GetName().Name)
|
||||
.Where(t => !t.IsAbstract && !t.IsInterface)
|
||||
.ToArray();
|
||||
_objectTypes = TypeCache.GetTypesDerivedFrom<BaseObject>()
|
||||
.Append(typeof(BaseObject))
|
||||
.Sort((t1, t2) => t1.IsAssignableFrom(t2) ? -1 : t2.IsAssignableFrom(t1) ? 1 : 0)
|
||||
.ToArray();
|
||||
_objectTypesMap = _objectTypes
|
||||
.GroupBy(t => t.BaseType)
|
||||
.Where(g => typeof(BaseObject).IsAssignableFrom(g.Key))
|
||||
.ToDictionary(k => k.Key, v => v.ToList());
|
||||
_moduleMonoScripts = MonoImporter.GetAllRuntimeMonoScripts()
|
||||
.Where(m => typeof(ObjectModule).IsAssignableFrom(m.GetClass()))
|
||||
.Where(m => !m.GetClass().IsAbstract && !m.GetClass().IsInterface)
|
||||
.ToDictionary(m => m.GetClass(), m => m);
|
||||
}
|
||||
|
||||
private void UpdateConfigModules()
|
||||
{
|
||||
var config = EditorConfigSO.instance;
|
||||
foreach (var moduleType in _moduleTypes)
|
||||
{
|
||||
var isRequired = moduleType.GetCustomAttribute<ObjectModuleAttribute>()?.required ?? false;
|
||||
var baseObjectType = moduleType.FindGenericDefinitionType(typeof(ObjectModule<>)).GetGenericArguments()[0];
|
||||
|
||||
config.modules.TryAdd(moduleType, new List<SerializableType>());
|
||||
|
||||
if (isRequired)
|
||||
{
|
||||
config.modules[moduleType] = new List<SerializableType> { baseObjectType };
|
||||
}
|
||||
else
|
||||
{
|
||||
config.modules[moduleType] = config.modules[moduleType]
|
||||
.Where(t => !config.modules[moduleType].Any(st => t.type.IsSubclassOf(st)))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
config.Save();
|
||||
}
|
||||
|
||||
#region VERIFY
|
||||
|
||||
private void CreateVerifyList()
|
||||
{
|
||||
_verifyListMultiColumnListView.columns["validator"].makeCell = () => new Label();
|
||||
_verifyListMultiColumnListView.columns["validator"].bindCell = (element, index) =>
|
||||
{
|
||||
((Label)element).text = element.tooltip = _validations[index].validator.GetType().Name;
|
||||
};
|
||||
_verifyListMultiColumnListView.columns["message"].makeCell = () => new Label();
|
||||
_verifyListMultiColumnListView.columns["message"].bindCell = (element, index) =>
|
||||
{
|
||||
((Label)element).text = element.tooltip = _validations[index].message;
|
||||
};
|
||||
_verifyListMultiColumnListView.columns["context"].makeCell = () => new Label();
|
||||
_verifyListMultiColumnListView.columns["context"].bindCell = (element, index) =>
|
||||
{
|
||||
((Label)element).text = element.tooltip = _validations[index].contextPath;
|
||||
element.RegisterCallback<PointerDownEvent>(ev =>
|
||||
{
|
||||
if (ev.clickCount != 2) return;
|
||||
PingObject(_validations[index].contextPath);
|
||||
});
|
||||
};
|
||||
_verifyListMultiColumnListView.columns["hierarchy"].makeCell = () => new Label();
|
||||
_verifyListMultiColumnListView.columns["hierarchy"].bindCell = (element, index) =>
|
||||
{
|
||||
((Label)element).text = element.tooltip = _validations[index].hierarchyPath;
|
||||
element.RegisterCallback<PointerDownEvent>(ev =>
|
||||
{
|
||||
if (ev.clickCount != 2) return;
|
||||
PingObject(_validations[index].contextPath, _validations[index].hierarchyPath);
|
||||
});
|
||||
};
|
||||
_verifyListMultiColumnListView.columns["result"].makeCell = () => new Label();
|
||||
_verifyListMultiColumnListView.columns["result"].bindCell = (element, index) =>
|
||||
{
|
||||
((Label)element).text = element.tooltip = _validations[index].resultMessage;
|
||||
|
||||
element.EnableInClassList("verify-success", _validations[index].result == true);
|
||||
element.EnableInClassList("verify-error", _validations[index].result == false);
|
||||
};
|
||||
}
|
||||
|
||||
private void PingObject(string contextPath, string hierarchyPath = null)
|
||||
{
|
||||
var contextFile = AssetDatabase.LoadMainAssetAtPath(contextPath);
|
||||
GameObject hierarchyObjectRoot;
|
||||
|
||||
if (hierarchyPath == null)
|
||||
{
|
||||
EditorGUIUtility.PingObject(contextFile.GetInstanceID());
|
||||
return;
|
||||
}
|
||||
|
||||
var hierarchyPathArray = hierarchyPath.Split('/', 2);
|
||||
|
||||
if (contextFile is SceneAsset)
|
||||
{
|
||||
hierarchyObjectRoot = EditorSceneManager.OpenScene(contextPath)
|
||||
.GetRootGameObjects().FirstOrDefault(go => go.name == hierarchyPathArray[0]);
|
||||
}
|
||||
else if (contextFile is GameObject)
|
||||
{
|
||||
hierarchyObjectRoot = PrefabStageUtility.OpenPrefab(contextPath).prefabContentsRoot;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Given file '{contextPath}' is not prefab nor scene!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hierarchyObjectRoot)
|
||||
{
|
||||
Debug.LogError($"Can't find root game object '{hierarchyPathArray[0]}' in '{contextPath}'. Perhaps did you change something?");
|
||||
return;
|
||||
}
|
||||
|
||||
var hierarchyObject = hierarchyPathArray.Length > 1 ?
|
||||
hierarchyObjectRoot.transform.Find(hierarchyPathArray[1])?.gameObject : hierarchyObjectRoot;
|
||||
|
||||
if (!hierarchyObject)
|
||||
{
|
||||
Debug.LogError($"Can't find '{hierarchyPath}' in '{contextPath}'. Perhaps did you change something?");
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUIUtility.PingObject(hierarchyObject.GetInstanceID());
|
||||
}
|
||||
|
||||
private void OnVerifyCheck()
|
||||
{
|
||||
_validations = ValidationSolver.Check(EditorConfigSO.instance.verifyPaths);
|
||||
_verifyListMultiColumnListView.itemsSource = _validations;
|
||||
_verifyListMultiColumnListView.RefreshItems();
|
||||
|
||||
_verifyFixButton.SetEnabled(_validations.Any());
|
||||
}
|
||||
|
||||
private void OnVerifyFix()
|
||||
{
|
||||
ValidationSolver.FixAll(_validations);
|
||||
_verifyListMultiColumnListView.RefreshItems();
|
||||
}
|
||||
|
||||
private void OnVerifyClear()
|
||||
{
|
||||
_validations.Clear();
|
||||
_verifyListMultiColumnListView.RefreshItems();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MODULES
|
||||
|
||||
private void CreateModulesList()
|
||||
{
|
||||
_modulesListScrollView.Clear();
|
||||
_moduleTypes.Select(CreateModuleRow).ForEach(_modulesListScrollView.Add);
|
||||
}
|
||||
|
||||
private VisualElement CreateModuleRow(Type moduleType)
|
||||
{
|
||||
var objectType = moduleType.FindGenericDefinitionType(typeof(ObjectModule<>)).GetGenericArguments()[0];
|
||||
var moduleAttribute = moduleType.GetCustomAttribute<ObjectModuleAttribute>();
|
||||
var moduleMonoScript = _moduleMonoScripts.GetValueOrDefault(moduleType);
|
||||
|
||||
var foldout = new Foldout();
|
||||
foldout.AddToClassList("module-foldout");
|
||||
foldout.value = false;
|
||||
var moduleName = $"<b>{moduleAttribute?.name ?? moduleType.Name}</b>";
|
||||
var assemblyName = $"<alpha=\"#69\">asm: {moduleType.Assembly.GetName().Name}</alpha>";
|
||||
foldout.text = moduleName + " " + assemblyName;
|
||||
|
||||
var objectField = new ObjectField { objectType = typeof(MonoScript), value = moduleMonoScript, enabledSelf = false };
|
||||
foldout.Add(objectField);
|
||||
|
||||
var descriptionLabel = new Label(moduleAttribute?.description ?? $"// {nameof(ObjectModuleAttribute)} not found on {moduleType.Name} //");
|
||||
descriptionLabel.style.whiteSpace = WhiteSpace.Normal;
|
||||
foldout.Add(descriptionLabel);
|
||||
|
||||
var usageTreeElement = CreateObjectTreeUsageForModule(moduleType, objectType, moduleAttribute?.required ?? false);
|
||||
foldout.Add(usageTreeElement);
|
||||
|
||||
return foldout;
|
||||
}
|
||||
|
||||
private VisualElement CreateObjectTreeUsageForModule(Type moduleType, Type objectType, bool isRequired, string prefix = "")
|
||||
{
|
||||
var config = EditorConfigSO.instance;
|
||||
var isEnabled = config.modules?.GetValueOrDefault(moduleType)?.Any(st => st.type == objectType) ?? false;
|
||||
|
||||
var section = new VisualElement();
|
||||
|
||||
if (prefix == "")
|
||||
{
|
||||
var sectionTitle = new Label();
|
||||
sectionTitle.style.marginTop = 8;
|
||||
sectionTitle.text = "<u>Modules enabled by default:</u>";
|
||||
section.Add(sectionTitle);
|
||||
}
|
||||
|
||||
var row = new VisualElement();
|
||||
row.style.flexDirection = FlexDirection.Row;
|
||||
section.Add(row);
|
||||
|
||||
var rowPrefixPipe = new Label(prefix);
|
||||
rowPrefixPipe.style.whiteSpace = WhiteSpace.Pre;
|
||||
row.Add(rowPrefixPipe);
|
||||
|
||||
var rowModuleToggle = new Toggle(objectType.Name);
|
||||
rowModuleToggle.name = $"module-{moduleType.Name}-{objectType.Name}";
|
||||
rowModuleToggle.value = isRequired || isEnabled;
|
||||
rowModuleToggle.SetEnabled(!isRequired);
|
||||
rowModuleToggle.RegisterValueChangedCallback(ev => UpdateModuleUsage(moduleType, objectType, ev.newValue));
|
||||
row.Add(rowModuleToggle);
|
||||
|
||||
if (!_objectTypesMap.TryGetValue(objectType, out var subTypes)) return section;
|
||||
|
||||
prefix = prefix.Replace(PipeStart, PipeGo).Replace(PipeEnd, PipeNone);
|
||||
|
||||
for (var i = 0; i < subTypes.Count; i++)
|
||||
{
|
||||
var subPrefix = prefix + (i == subTypes.Count-1 ? PipeEnd : PipeStart);
|
||||
var subType = subTypes[i];
|
||||
var subSection = CreateObjectTreeUsageForModule(moduleType, subType, isRequired || isEnabled, subPrefix);
|
||||
section.Add(subSection);
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
private void UpdateModuleUsage(Type moduleType, Type objectType, bool isRequired)
|
||||
{
|
||||
var toggleName = $"module-{moduleType.Name}-{objectType.Name}";
|
||||
var toggle = _modulesListScrollView.Q<Toggle>(toggleName);
|
||||
toggle.parent.parent
|
||||
.Query<Toggle>()
|
||||
.ToList()
|
||||
.Where(subToggle => subToggle != toggle)
|
||||
.ForEach(subToggle =>
|
||||
{
|
||||
subToggle.SetValueWithoutNotify(false);
|
||||
subToggle.SetEnabled(!isRequired);
|
||||
});
|
||||
|
||||
var config = EditorConfigSO.instance;
|
||||
config.modules[moduleType].RemoveAll(t => objectType.IsAssignableFrom(t));
|
||||
if (isRequired) config.modules[moduleType].Add(objectType);
|
||||
config.Save();
|
||||
}
|
||||
|
||||
private void OnModuleFilter(string filter)
|
||||
{
|
||||
var foldouts = _modulesListScrollView.Query<Foldout>(className: "module-foldout").ToList();
|
||||
|
||||
foreach (var foldout in foldouts)
|
||||
{
|
||||
var matchFilter = foldout.text.Contains(filter, StringComparison.InvariantCultureIgnoreCase);
|
||||
foldout.style.display = matchFilter ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
foldout.value = !string.IsNullOrEmpty(filter) && matchFilter;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user