364 lines
15 KiB
C#
364 lines
15 KiB
C#
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
|
|
}
|
|
} |